Merge "veyron/profiles/roaming: Move ListenSpec initialization"
diff --git a/lib/glob/glob.go b/lib/glob/glob.go
index b1356c1..a69be73 100644
--- a/lib/glob/glob.go
+++ b/lib/glob/glob.go
@@ -75,42 +75,49 @@
 }
 
 // MatchInitialSegment tries to match segment against the initial element of g.
-// Returns a boolean indicating whether the match was successful and the
-// Glob representing the unmatched remainder of g.
-func (g *Glob) MatchInitialSegment(segment string) (bool, *Glob) {
+// Returns:
+// matched, a boolean indicating whether the match was successful;
+// exact, a boolean indicating whether segment matched a fixed string pattern;
+// remainder, a Glob representing the unmatched remainder of g.
+func (g *Glob) MatchInitialSegment(segment string) (matched bool, exact bool, remainder *Glob) {
 	if len(g.elems) == 0 {
 		if !g.recursive {
-			return false, nil
+			return false, false, nil
 		}
-		return true, g
+		return true, true, g
 	}
 
 	if matches, err := filepath.Match(g.elems[0], segment); err != nil {
 		panic("Error in glob pattern found.")
 	} else if matches {
-		return true, g.Split(1)
+		_, fixed := isFixed(g.elems[0])
+		return true, fixed, g.Split(1)
 	}
-	return false, nil
+	return false, false, nil
 }
 
 // PartialMatch tries matching elems against part of a glob pattern.
-// The first return value is true if each element e_i of elems matches
-// the (start + i)th element of the glob pattern.  If the first return
-// value is true, the second return value returns the unmatched suffix
-// of the pattern.  It will be empty if the pattern is completely
-// matched.
+// Returns:
+// matched, a boolean indicating whether each element e_i of elems matches the
+// (start + i)th element of the glob pattern;
+// exact, a boolean indicating whether elems matched a fixed string pattern;
+// remainder, a Glob representing the unmatched remainder of g. remainder will
+// be empty if the pattern is completely matched.
 //
 // Note that if the glob is recursive elems can have more elements then
 // the glob pattern and still get a true result.
-func (g *Glob) PartialMatch(start int, elems []string) (bool, *Glob) {
+func (g *Glob) PartialMatch(start int, elems []string) (matched bool, exact bool, remainder *Glob) {
 	g = g.Split(start)
-	for ; len(elems) > 0; elems = elems[1:] {
-		var matched bool
-		if matched, g = g.MatchInitialSegment(elems[0]); !matched {
-			return false, nil
+	allExact := true
+	for i := 0; i < len(elems); i++ {
+		var matched, exact bool
+		if matched, exact, g = g.MatchInitialSegment(elems[i]); !matched {
+			return false, false, nil
+		} else if !exact {
+			allExact = false
 		}
 	}
-	return true, g
+	return true, allExact, g
 }
 
 // isFixed returns the unescaped string and true if 's' is a pattern specifying
diff --git a/lib/glob/glob_test.go b/lib/glob/glob_test.go
index bbef347..785a1eb 100644
--- a/lib/glob/glob_test.go
+++ b/lib/glob/glob_test.go
@@ -38,3 +38,63 @@
 		}
 	}
 }
+
+func TestExactMatch(t *testing.T) {
+	tests := []struct {
+		pattern string
+		elems   []string
+		matched bool
+		exact   bool
+	}{
+		// Test one element, fixed.
+		{"a", []string{"a"}, true, true},
+		{"a", []string{"b"}, false, false},
+		{"\\\\", []string{"\\"}, true, true},
+		// Test one element, containing *.
+		{"*", []string{"*"}, true, false},
+		{"*", []string{"abc"}, true, false},
+		{"\\*", []string{"*"}, true, true},
+		{"\\*", []string{"abc"}, false, false},
+		{"\\\\*", []string{"\\"}, true, false},
+		{"\\\\*", []string{"\\*"}, true, false},
+		{"\\\\*", []string{"\\abc"}, true, false},
+		// Test one element, containing ?.
+		{"?", []string{"?"}, true, false},
+		{"?", []string{"a"}, true, false},
+		{"\\?", []string{"?"}, true, true},
+		{"\\?", []string{"a"}, false, false},
+		{"\\\\?", []string{"\\"}, false, false},
+		{"\\\\?", []string{"\\?"}, true, false},
+		{"\\\\?", []string{"\\a"}, true, false},
+		// Test one element, containing [].
+		{"[abc]", []string{"c"}, true, false},
+		{"\\[abc\\]", []string{"[abc]"}, true, true},
+		{"\\[abc\\]", []string{"a"}, false, false},
+		{"\\\\[abc]", []string{"\\a"}, true, false},
+		{"\\\\[abc]", []string{"a"}, false, false},
+		{"[\\\\]", []string{"\\"}, true, false},
+		{"[\\\\]", []string{"a"}, false, false},
+		// Test multiple elements.
+		{"a/b", []string{"a", "b"}, true, true},
+		{"a/\\*", []string{"a", "*"}, true, true},
+		{"a/\\?", []string{"a", "?"}, true, true},
+		{"a/\\[b\\]", []string{"a", "[b]"}, true, true},
+		{"a/*", []string{"a", "bc"}, true, false},
+		{"a/?", []string{"a", "b"}, true, false},
+		{"a/[bc]", []string{"a", "b"}, true, false},
+		{"a/*/c", []string{"a", "b", "c"}, true, false},
+		{"a/?/c", []string{"a", "b", "c"}, true, false},
+		{"a/[bc]/d", []string{"a", "b", "d"}, true, false},
+	}
+	for _, test := range tests {
+		g, err := Parse(test.pattern)
+		if err != nil {
+			t.Fatalf("parsing %q: %q", test.pattern, err.Error())
+		}
+		matched, exact, _ := g.PartialMatch(0, test.elems)
+		if matched != test.matched || exact != test.exact {
+			t.Fatalf("%v.PartialMatch(0, %v) got (%v, %v), expected (%v, %v)",
+				test.pattern, test.elems, matched, exact, test.matched, test.exact)
+		}
+	}
+}
diff --git a/lib/stats/glob.go b/lib/stats/glob.go
index f2db758..1390853 100644
--- a/lib/stats/glob.go
+++ b/lib/stats/glob.go
@@ -50,7 +50,7 @@
 		return
 	}
 	for name, child := range n.children {
-		if ok, left := g.MatchInitialSegment(name); ok {
+		if ok, _, left := g.MatchInitialSegment(name); ok {
 			globStepLocked(path.Join(prefix, name), left, child, updatedSince, includeValues, result)
 		}
 	}
diff --git a/runtimes/google/naming/namespace/all_test.go b/runtimes/google/naming/namespace/all_test.go
index 479b5bd..bbac581 100644
--- a/runtimes/google/naming/namespace/all_test.go
+++ b/runtimes/google/naming/namespace/all_test.go
@@ -91,7 +91,7 @@
 	if g.Finished() || len(tree) == 0 {
 		return nil
 	}
-	if ok, left := g.MatchInitialSegment(tree[0]); ok {
+	if ok, _, left := g.MatchInitialSegment(tree[0]); ok {
 		if err := t.globLoop(call, naming.Join(prefix, tree[0]), left, tree[1:]); err != nil {
 			return err
 		}
diff --git a/services/mgmt/debug/dispatcher.go b/services/mgmt/debug/dispatcher.go
index a0c86cc..9c6e3b8 100644
--- a/services/mgmt/debug/dispatcher.go
+++ b/services/mgmt/debug/dispatcher.go
@@ -62,7 +62,7 @@
 		return err
 	}
 	for _, leaf := range i.leaves {
-		if ok, left := g.MatchInitialSegment(leaf); ok {
+		if ok, _, left := g.MatchInitialSegment(leaf); ok {
 			if err := i.leafGlob(call, leaf, left.String()); err != nil {
 				return err
 			}
diff --git a/services/mgmt/logreader/impl/logdir_invoker.go b/services/mgmt/logreader/impl/logdir_invoker.go
index 5beb139..ef7a60b 100644
--- a/services/mgmt/logreader/impl/logdir_invoker.go
+++ b/services/mgmt/logreader/impl/logdir_invoker.go
@@ -78,7 +78,7 @@
 		if fileName == "." || fileName == ".." {
 			continue
 		}
-		if ok, left := g.MatchInitialSegment(fileName); ok {
+		if ok, _, left := g.MatchInitialSegment(fileName); ok {
 			if err := i.globStep(path.Join(name, fileName), left, file.IsDir(), call); err != nil {
 				return err
 			}
diff --git a/services/mounttable/lib/mounttable.go b/services/mounttable/lib/mounttable.go
index 922f4c7..85936ec 100644
--- a/services/mounttable/lib/mounttable.go
+++ b/services/mounttable/lib/mounttable.go
@@ -357,7 +357,7 @@
 
 	// Recurse through the children.
 	for k, c := range n.children {
-		if ok, suffix := pattern.MatchInitialSegment(k); ok {
+		if ok, _, suffix := pattern.MatchInitialSegment(k); ok {
 			mt.globStep(c, naming.Join(name, k), suffix, context, reply)
 		}
 	}
diff --git a/services/mounttable/lib/neighborhood.go b/services/mounttable/lib/neighborhood.go
index ad66bfb..acdb46c 100644
--- a/services/mounttable/lib/neighborhood.go
+++ b/services/mounttable/lib/neighborhood.go
@@ -244,7 +244,7 @@
 	switch len(ns.elems) {
 	case 0:
 		for k, n := range nh.neighbors() {
-			if ok, _ := g.MatchInitialSegment(k); !ok {
+			if ok, _, _ := g.MatchInitialSegment(k); !ok {
 				continue
 			}
 			if err := sender.Send(types.MountEntry{Name: k, Servers: n}); err != nil {