veyron/services/mgmt/node/impl: Add Glob implementation

This change adds a basic Glob implementation for the app invoker. It scans
the config directory to find all the app installations and instances,
and then matches the glob pattern against the result.

Change-Id: I8bb2828eff7429fc005edbfc03787f353200f6ed
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index 5922123..35a8268 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -729,14 +729,117 @@
 	return updateLink(prevVersionDir, currLink)
 }
 
+type treeNode struct {
+	children map[string]*treeNode
+}
+
+func newTreeNode() *treeNode {
+	return &treeNode{make(map[string]*treeNode)}
+}
+
+func (n *treeNode) find(names []string, create bool) *treeNode {
+	for {
+		if len(names) == 0 {
+			return n
+		}
+		if next, ok := n.children[names[0]]; ok {
+			n = next
+			names = names[1:]
+			continue
+		}
+		if create {
+			nn := newTreeNode()
+			n.children[names[0]] = nn
+			n = nn
+			names = names[1:]
+			continue
+		}
+		return nil
+	}
+}
+
+// scanConfigDir scans the config directory to build tree representation of all
+// the valid object names.
+func (i *appInvoker) scanConfigDir() *treeNode {
+	tree := newTreeNode()
+
+	// appIDMap[appID]title
+	appIDMap := make(map[string]string)
+
+	// Find all envelopes, extract appID and installID.
+	envGlob := []string{i.config.Root, "app-*", "installation-*", "*", "envelope"}
+	envelopes, err := filepath.Glob(filepath.Join(envGlob...))
+	if err != nil {
+		vlog.Errorf("unexpected error: %v", err)
+		return nil
+	}
+	for _, path := range envelopes {
+		env, err := loadEnvelope(filepath.Dir(path))
+		if err != nil {
+			continue
+		}
+		relpath, _ := filepath.Rel(i.config.Root, path)
+		elems := strings.Split(relpath, string(filepath.Separator))
+		if len(elems) != len(envGlob)-1 {
+			vlog.Errorf("unexpected number of path components: %q (%q)", elems, path)
+			continue
+		}
+		appID := strings.TrimPrefix(elems[0], "app-")
+		installID := strings.TrimPrefix(elems[1], "installation-")
+		appIDMap[appID] = env.Title
+		tree.find([]string{env.Title, installID}, true)
+	}
+
+	// Find all instances.
+	infoGlob := []string{i.config.Root, "app-*", "installation-*", "instances", "instance-*", "info"}
+	instances, err := filepath.Glob(filepath.Join(infoGlob...))
+	if err != nil {
+		vlog.Errorf("unexpected error: %v", err)
+		return nil
+	}
+	for _, path := range instances {
+		if _, err := loadInstanceInfo(filepath.Dir(path)); err != nil {
+			continue
+		}
+		relpath, _ := filepath.Rel(i.config.Root, path)
+		elems := strings.Split(relpath, string(filepath.Separator))
+		if len(elems) != len(infoGlob)-1 {
+			vlog.Errorf("unexpected number of path components: %q (%q)", elems, path)
+			continue
+		}
+		appID := strings.TrimPrefix(elems[0], "app-")
+		installID := strings.TrimPrefix(elems[1], "installation-")
+		instanceID := strings.TrimPrefix(elems[3], "instance-")
+		if title, ok := appIDMap[appID]; ok {
+			tree.find([]string{title, installID, instanceID}, true)
+		}
+	}
+	return tree
+}
+
 func (i *appInvoker) Glob(ctx ipc.ServerContext, pattern string, stream mounttable.GlobbableServiceGlobStream) error {
-	// TODO(rthellend): Finish implementing Glob
 	g, err := glob.Parse(pattern)
 	if err != nil {
 		return err
 	}
-	if g.Len() == 0 {
-		return stream.SendStream().Send(types.MountEntry{Name: ""})
+	n := i.scanConfigDir().find(i.suffix, false)
+	if n == nil {
+		return errInvalidSuffix
 	}
+	i.globStep("", g, n, stream)
 	return nil
 }
+
+func (i *appInvoker) globStep(prefix string, g *glob.Glob, n *treeNode, stream mounttable.GlobbableServiceGlobStream) {
+	if g.Len() == 0 {
+		stream.SendStream().Send(types.MountEntry{Name: prefix})
+	}
+	if g.Finished() {
+		return
+	}
+	for name, child := range n.children {
+		if ok, _, left := g.MatchInitialSegment(name); ok {
+			i.globStep(naming.Join(prefix, name), left, child, stream)
+		}
+	}
+}