Merge "services/device/device: make glob flags per-subcommand instead of global"
diff --git a/services/mounttable/mounttablelib/mounttable.go b/services/mounttable/mounttablelib/mounttable.go
index a3582ff..2885228 100644
--- a/services/mounttable/mounttablelib/mounttable.go
+++ b/services/mounttable/mounttablelib/mounttable.go
@@ -70,7 +70,6 @@
 	serverCounter      *stats.Integer
 	perUserNodeCounter *stats.Map
 	maxNodesPerUser    int64
-	userPrefixes       []string
 }
 
 var _ rpc.Dispatcher = (*mountTable)(nil)
@@ -360,7 +359,7 @@
 // findNode finds a node in the table and optionally creates a path to it.
 //
 // If a node is found, on return it and its parent are locked.
-func (mt *mountTable) findNode(ctx *context.T, call security.Call, elems []string, create bool, tags []mounttable.Tag) (*node, error) {
+func (mt *mountTable) findNode(ctx *context.T, call security.Call, elems []string, create bool, tags, ptags []mounttable.Tag) (*node, error) {
 	n, nelems, err := mt.traverse(ctx, call, elems, create)
 	if err != nil {
 		return nil, err
@@ -373,10 +372,18 @@
 		n.Unlock()
 		return nil, nil
 	}
+	// Either the node has to satisfy tags or the parent has to satisfy ptags.
 	if err := n.satisfies(mt, ctx, call, tags); err != nil {
-		n.parent.Unlock()
-		n.Unlock()
-		return nil, err
+		if ptags == nil {
+			n.parent.Unlock()
+			n.Unlock()
+			return nil, err
+		}
+		if err := n.parent.satisfies(mt, ctx, call, ptags); err != nil {
+			n.parent.Unlock()
+			n.Unlock()
+			return nil, err
+		}
 	}
 	return n, nil
 }
@@ -486,7 +493,7 @@
 	}
 
 	// Find/create node in namespace and add the mount.
-	n, werr := mt.findNode(ctx, call.Security(), ms.elems, true, mountTags)
+	n, werr := mt.findNode(ctx, call.Security(), ms.elems, true, mountTags, nil)
 	if werr != nil {
 		return werr
 	}
@@ -579,7 +586,7 @@
 func (ms *mountContext) Unmount(ctx *context.T, call rpc.ServerCall, server string) error {
 	vlog.VI(2).Infof("*********************Unmount %q, %s", ms.name, server)
 	mt := ms.mt
-	n, err := mt.findNode(ctx, call.Security(), ms.elems, false, mountTags)
+	n, err := mt.findNode(ctx, call.Security(), ms.elems, false, mountTags, nil)
 	if err != nil {
 		return err
 	}
@@ -612,8 +619,9 @@
 		return verror.New(errCantDeleteRoot, ctx)
 	}
 	mt := ms.mt
-	// Find and lock the parent node.
-	n, err := mt.findNode(ctx, call.Security(), ms.elems, false, removeTags)
+	// Find and lock the parent node and parent node.  Either the node or its parent has
+	// to satisfy removeTags.
+	n, err := mt.findNode(ctx, call.Security(), ms.elems, false, removeTags, removeTags)
 	if err != nil {
 		return err
 	}
@@ -758,7 +766,7 @@
 	go func() {
 		defer close(ch)
 		// If there was an access error, just ignore the entry, i.e., make it invisible.
-		n, err := mt.findNode(ctx, scall, ms.elems, false, nil)
+		n, err := mt.findNode(ctx, scall, ms.elems, false, nil, nil)
 		if err != nil {
 			return
 		}
@@ -793,7 +801,7 @@
 	mt := ms.mt
 
 	// Find/create node in namespace and add the mount.
-	n, err := mt.findNode(ctx, call.Security(), ms.elems, true, setTags)
+	n, err := mt.findNode(ctx, call.Security(), ms.elems, true, setTags, nil)
 	if err != nil {
 		return err
 	}
@@ -839,7 +847,7 @@
 	mt := ms.mt
 
 	// Find node in namespace and add the mount.
-	n, err := mt.findNode(ctx, call.Security(), ms.elems, false, getTags)
+	n, err := mt.findNode(ctx, call.Security(), ms.elems, false, getTags, nil)
 	if err != nil {
 		return nil, "", err
 	}
@@ -864,8 +872,7 @@
 	if !ok {
 		return "", verror.New(errTooManyNodes, ctx)
 	}
-	// If we have no prefixes defining users, don't bother with checking per user limits.
-	if len(mt.userPrefixes) != 0 && count > mt.maxNodesPerUser {
+	if count > mt.maxNodesPerUser {
 		mt.perUserNodeCounter.Incr(creator, -1)
 		return "", verror.New(errTooManyNodes, ctx)
 	}
diff --git a/services/mounttable/mounttablelib/mounttable_test.go b/services/mounttable/mounttablelib/mounttable_test.go
index a8f9e7f..21a2f29 100644
--- a/services/mounttable/mounttablelib/mounttable_test.go
+++ b/services/mounttable/mounttablelib/mounttable_test.go
@@ -17,6 +17,7 @@
 
 	"v.io/v23"
 	"v.io/v23/context"
+	"v.io/v23/conventions"
 	"v.io/v23/naming"
 	"v.io/v23/options"
 	"v.io/v23/rpc"
@@ -483,7 +484,7 @@
 		{"alice", int64(defaultMaxNodesPerUser)},
 		{"bob", int64(0)},
 		{"root", int64(0)},
-		{localUser, int64(3)},
+		{conventions.ServerUser, int64(3)},
 	}
 	for _, tc := range testcases {
 		name := "testAccessListTemplate/num-nodes-per-user/" + tc.key
@@ -601,6 +602,12 @@
 	checkExists(t, rootCtx, estr, "one/bright/day", false)
 	doDeleteNode(t, bobCtx, estr, "one/bright", true)
 	checkExists(t, rootCtx, estr, "one/bright", false)
+
+	// Make sure directory admin can delete directory children.
+	perms := access.Permissions{"Admin": access.AccessList{In: []security.BlessingPattern{"bob"}}}
+	doSetPermissions(t, bobCtx, estr, "hoohaa", perms, "", false)
+	doDeleteNode(t, rootCtx, estr, "hoohaa", true)
+	checkExists(t, rootCtx, estr, "hoohaa", false)
 }
 
 func TestServerFormat(t *testing.T) {
diff --git a/services/mounttable/mounttablelib/persistentstore.go b/services/mounttable/mounttablelib/persistentstore.go
index 0cb614d..a96eb19 100644
--- a/services/mounttable/mounttablelib/persistentstore.go
+++ b/services/mounttable/mounttablelib/persistentstore.go
@@ -131,7 +131,7 @@
 		}
 
 		elems := strings.Split(e.N, "/")
-		n, err := mt.findNode(nil, nil, elems, true, nil)
+		n, err := mt.findNode(nil, nil, elems, true, nil, nil)
 		if n != nil || err == nil {
 			n.creator = e.C
 			if e.D {
diff --git a/services/mounttable/mounttablelib/versionedpermissions.go b/services/mounttable/mounttablelib/versionedpermissions.go
index 95a044a..d4da750 100644
--- a/services/mounttable/mounttablelib/versionedpermissions.go
+++ b/services/mounttable/mounttablelib/versionedpermissions.go
@@ -8,12 +8,12 @@
 	"encoding/json"
 	"io"
 	"os"
-	"reflect"
 	"sort"
 	"strconv"
 	"strings"
 
 	"v.io/v23/context"
+	"v.io/v23/conventions"
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/services/mounttable"
@@ -143,17 +143,11 @@
 			}
 
 			// Create name and add the Permissions map to it.
-			n, err := mt.findNode(nil, nil, elems, true, nil)
+			n, err := mt.findNode(nil, nil, elems, true, nil, nil)
 			if n != nil || err == nil {
 				vlog.VI(2).Infof("added perms %v to %s", perms, name)
 				if isPattern {
 					n.permsTemplate = perms
-					// Save the pattern prefix as a prefix describing a user.
-					prefix := strings.Join(elems[:len(elems)-1], "/")
-					if prefix != "" {
-						prefix += "/"
-					}
-					mt.userPrefixes = append(mt.userPrefixes, prefix)
 				} else {
 					n.vPerms, _ = n.vPerms.Set(nil, "", perms)
 					n.explicitPermissions = true
@@ -166,37 +160,9 @@
 	return nil
 }
 
-// pickCreator returns a string matching the blessing of the user performing the creation.  We do this using
-// the user prefixes found when parsing the config.  Eventually we may need a better way to define user
-// prefixes.
-//
-// TODO(p): readdress this argument after we have some experience with real users.
+// pickCreator returns a string matching the blessing of the user performing the creation.
 func (mt *mountTable) pickCreator(ctx *context.T, call security.Call) string {
-	// For each blessing, look for one that has a matching user prefix.  The creator is the perfix
-	// plus one more element.
-	//
-	// The prefixes themselves come from the templates in the config that constrain node names to
-	// match the user.
-	blessings, _ := security.RemoteBlessingNames(ctx, call)
-	for _, b := range blessings {
-		for _, p := range mt.userPrefixes {
-			sb := string(b)
-			if !strings.HasPrefix(sb, p) {
-				continue
-			}
-			suffix := strings.TrimPrefix(sb, p)
-			elems := strings.Split(suffix, "/")
-			return p + elems[0]
-		}
-	}
-	if ctx == nil || call == nil {
-		return localUser
-	}
-	if l, r := call.LocalBlessings().PublicKey(), call.RemoteBlessings().PublicKey(); l != nil && reflect.DeepEqual(l, r) {
-		return localUser
-	}
-	if len(blessings) > 0 {
-		return blessedUser
-	}
-	return unknownUser
+	ids := conventions.GetClientUserIds(ctx, call)
+	// Replace the slashes with something else or we'll confuse the stats package.
+	return strings.Replace(ids[0], "/", "\\", 0)
 }