Merge branch 'master' into proxyflow
diff --git a/cmd/mounttable/doc.go b/cmd/mounttable/doc.go
index 81aa724..d88a7a0 100644
--- a/cmd/mounttable/doc.go
+++ b/cmd/mounttable/doc.go
@@ -59,7 +59,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Mounttable glob - returns all matching entries in the mount table
 
diff --git a/cmd/namespace/doc.go b/cmd/namespace/doc.go
index 407a1c8..b274389 100644
--- a/cmd/namespace/doc.go
+++ b/cmd/namespace/doc.go
@@ -67,7 +67,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Namespace glob - Returns all matching entries from the namespace
 
diff --git a/cmd/principal/doc.go b/cmd/principal/doc.go
index cbc209c..061e7a3 100644
--- a/cmd/principal/doc.go
+++ b/cmd/principal/doc.go
@@ -75,7 +75,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Principal create - Create a new principal and persist it into a directory
 
diff --git a/cmd/vomtestgen/doc.go b/cmd/vomtestgen/doc.go
index 08a4f97..2b0dd52 100644
--- a/cmd/vomtestgen/doc.go
+++ b/cmd/vomtestgen/doc.go
@@ -78,6 +78,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/cmd/vrpc/doc.go b/cmd/vrpc/doc.go
index 7210979..e3ceef5 100644
--- a/cmd/vrpc/doc.go
+++ b/cmd/vrpc/doc.go
@@ -59,7 +59,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Vrpc signature - Describe the interfaces of a Vanadium server
 
diff --git a/cmd/vrun/doc.go b/cmd/vrun/doc.go
index 52398a5..b164f06 100644
--- a/cmd/vrun/doc.go
+++ b/cmd/vrun/doc.go
@@ -61,6 +61,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/examples/rps/rpsbot/doc.go b/examples/rps/rpsbot/doc.go
index d30af8e..cb35c51 100644
--- a/examples/rps/rpsbot/doc.go
+++ b/examples/rps/rpsbot/doc.go
@@ -66,6 +66,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/examples/rps/rpsplayer/doc.go b/examples/rps/rpsplayer/doc.go
index 139c943..f511a8c 100644
--- a/examples/rps/rpsplayer/doc.go
+++ b/examples/rps/rpsplayer/doc.go
@@ -63,6 +63,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/examples/rps/rpsscorekeeper/doc.go b/examples/rps/rpsscorekeeper/doc.go
index 9a529f0..af17ea0 100644
--- a/examples/rps/rpsscorekeeper/doc.go
+++ b/examples/rps/rpsscorekeeper/doc.go
@@ -62,6 +62,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/examples/tunnel/tunneld/doc.go b/examples/tunnel/tunneld/doc.go
index d300fab..9282410 100644
--- a/examples/tunnel/tunneld/doc.go
+++ b/examples/tunnel/tunneld/doc.go
@@ -60,6 +60,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/examples/tunnel/vsh/doc.go b/examples/tunnel/vsh/doc.go
index 8039cbe..45a0c0e 100644
--- a/examples/tunnel/vsh/doc.go
+++ b/examples/tunnel/vsh/doc.go
@@ -90,6 +90,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/runtime/internal/rpc/benchmark/benchmark/doc.go b/runtime/internal/rpc/benchmark/benchmark/doc.go
index e9eaa9a..7688db4 100644
--- a/runtime/internal/rpc/benchmark/benchmark/doc.go
+++ b/runtime/internal/rpc/benchmark/benchmark/doc.go
@@ -100,6 +100,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/runtime/internal/rpc/benchmark/benchmarkd/doc.go b/runtime/internal/rpc/benchmark/benchmarkd/doc.go
index b00942c..34df4bc 100644
--- a/runtime/internal/rpc/benchmark/benchmarkd/doc.go
+++ b/runtime/internal/rpc/benchmark/benchmarkd/doc.go
@@ -86,6 +86,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/runtime/internal/rpc/stress/mtstress/doc.go b/runtime/internal/rpc/stress/mtstress/doc.go
index 1ec86f7..f2273de 100644
--- a/runtime/internal/rpc/stress/mtstress/doc.go
+++ b/runtime/internal/rpc/stress/mtstress/doc.go
@@ -62,7 +62,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Mtstress mount - Measure latency of the Mount RPC at a fixed request rate
 
diff --git a/runtime/internal/rpc/stress/stress/doc.go b/runtime/internal/rpc/stress/stress/doc.go
index 05105dd..a96c86e 100644
--- a/runtime/internal/rpc/stress/stress/doc.go
+++ b/runtime/internal/rpc/stress/stress/doc.go
@@ -64,7 +64,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Stress stress - Run stress test
 
diff --git a/runtime/internal/rpc/stress/stressd/doc.go b/runtime/internal/rpc/stress/stressd/doc.go
index 29e8a4c..55c49a3 100644
--- a/runtime/internal/rpc/stress/stressd/doc.go
+++ b/runtime/internal/rpc/stress/stressd/doc.go
@@ -60,6 +60,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/agent/agentd/doc.go b/services/agent/agentd/doc.go
index 58fce80..0919094 100644
--- a/services/agent/agentd/doc.go
+++ b/services/agent/agentd/doc.go
@@ -73,6 +73,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/agent/internal/pingpong/doc.go b/services/agent/internal/pingpong/doc.go
index 606ad9a..ec503f5 100644
--- a/services/agent/internal/pingpong/doc.go
+++ b/services/agent/internal/pingpong/doc.go
@@ -55,6 +55,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/agent/internal/test_principal/doc.go b/services/agent/internal/test_principal/doc.go
index d99fe3f..6fda666 100644
--- a/services/agent/internal/test_principal/doc.go
+++ b/services/agent/internal/test_principal/doc.go
@@ -51,6 +51,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/agent/vbecome/doc.go b/services/agent/vbecome/doc.go
index 02e0b6d..ee6264d 100644
--- a/services/agent/vbecome/doc.go
+++ b/services/agent/vbecome/doc.go
@@ -61,6 +61,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/application/application/doc.go b/services/application/application/doc.go
index c0fecd0..8ffa992 100644
--- a/services/application/application/doc.go
+++ b/services/application/application/doc.go
@@ -59,7 +59,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Application match
 
diff --git a/services/application/applicationd/doc.go b/services/application/applicationd/doc.go
index f86ff46..44e79f0 100644
--- a/services/application/applicationd/doc.go
+++ b/services/application/applicationd/doc.go
@@ -63,6 +63,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/binary/binary/doc.go b/services/binary/binary/doc.go
index 7e8f4f2..03e639a 100644
--- a/services/binary/binary/doc.go
+++ b/services/binary/binary/doc.go
@@ -58,7 +58,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Binary delete - Delete a binary
 
diff --git a/services/binary/binaryd/doc.go b/services/binary/binaryd/doc.go
index ab358a6..d2bd36b 100644
--- a/services/binary/binaryd/doc.go
+++ b/services/binary/binaryd/doc.go
@@ -65,6 +65,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/build/build/doc.go b/services/build/build/doc.go
index bd5ed2f..6cbd8f0 100644
--- a/services/build/build/doc.go
+++ b/services/build/build/doc.go
@@ -55,7 +55,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Build build - Build vanadium Go packages
 
diff --git a/services/build/buildd/doc.go b/services/build/buildd/doc.go
index 2113099..baf91c3 100644
--- a/services/build/buildd/doc.go
+++ b/services/build/buildd/doc.go
@@ -66,6 +66,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/debug/debug/doc.go b/services/debug/debug/doc.go
index 9c9c6cf..41105ba 100644
--- a/services/debug/debug/doc.go
+++ b/services/debug/debug/doc.go
@@ -59,7 +59,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Debug glob
 
diff --git a/services/device/device/debug.go b/services/device/device/debug.go
index 1ba6fc4..176b46e 100644
--- a/services/device/device/debug.go
+++ b/services/device/device/debug.go
@@ -15,7 +15,6 @@
 )
 
 var cmdDebug = &cmdline.Command{
-	Runner:   globRunner(runDebug),
 	Name:     "debug",
 	Short:    "Debug the device.",
 	Long:     "Get internal debug information about application installations and instances.",
@@ -24,7 +23,11 @@
 <app name patterns...> are vanadium object names or glob name patterns corresponding to application installations and instances.`,
 }
 
-func runDebug(entry globResult, ctx *context.T, stdout, _ io.Writer) error {
+func init() {
+	globify(cmdDebug, runDebug, new(GlobSettings))
+}
+
+func runDebug(entry GlobResult, ctx *context.T, stdout, _ io.Writer) error {
 	if description, err := device.DeviceClient(entry.name).Debug(ctx); err != nil {
 		return fmt.Errorf("Debug failed: %v", err)
 	} else {
diff --git a/services/device/device/doc.go b/services/device/device/doc.go
index aedb75e..b42ff11 100644
--- a/services/device/device/doc.go
+++ b/services/device/device/doc.go
@@ -46,14 +46,6 @@
    user specified by this flag
  -dryrun=false
    Elides root-requiring systemcalls.
- -installation-state=
-   If non-empty, specifies allowed installation states (all others installations
-   get filtered out). The value of the flag is a comma-separated list of values
-   from among: [Active Uninstalled].
- -instance-state=
-   If non-empty, specifies allowed instance states (all other instances get
-   filtered out). The value of the flag is a comma-separated list of values from
-   among: [Launching Running Dying NotRunning Updating Deleted].
  -kill=false
    Kill process ids given as command-line arguments.
  -log_backtrace_at=:0
@@ -68,13 +60,6 @@
    max size in bytes of the buffer to use for logging stack traces
  -minuid=501
    UIDs cannot be less than this number.
- -only-installations=false
-   If set, only consider installations.
- -only-instances=false
-   If set, only consider instances.
- -parallelism=BYKIND
-   Specifies the level of parallelism for the handler execution. One of:
-   BYKIND,FULL,NONE
  -progname=unnamed_app
    Visible name of the application, used in argv[0]
  -rm=false
@@ -112,7 +97,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
  -workspace=
    Path to the application's workspace directory.
 
@@ -287,41 +274,109 @@
 previous version of their current version
 
 Usage:
-   device revert <name patterns...>
+   device revert [flags] <name patterns...>
 
 <name patterns...> are vanadium object names or glob name patterns corresponding
 to the device manager service, or to application installations and instances.
 
+The device revert flags are:
+ -installation-state=
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled].
+ -instance-state=
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted].
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=BYKIND
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
 Device update
 
 Update the device manager or application instances and installations
 
 Usage:
-   device update <name patterns...>
+   device update [flags] <name patterns...>
 
 <name patterns...> are vanadium object names or glob name patterns corresponding
 to the device manager service, or to application installations and instances.
 
+The device update flags are:
+ -installation-state=
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled].
+ -instance-state=
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted].
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=BYKIND
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
 Device status
 
 Get the status of the device manager or application instances and installations.
 
 Usage:
-   device status <name patterns...>
+   device status [flags] <name patterns...>
 
 <name patterns...> are vanadium object names or glob name patterns corresponding
 to the device manager service, or to application installations and instances.
 
+The device status flags are:
+ -installation-state=
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled].
+ -instance-state=
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted].
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=FULL
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
 Device debug
 
 Get internal debug information about application installations and instances.
 
 Usage:
-   device debug <app name patterns...>
+   device debug [flags] <app name patterns...>
 
 <app name patterns...> are vanadium object names or glob name patterns
 corresponding to application installations and instances.
 
+The device debug flags are:
+ -installation-state=
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled].
+ -instance-state=
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted].
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=FULL
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
 Device acl - Tool for setting device manager Permissions
 
 The acl tool manages Permissions on the device manger, installations and
@@ -404,11 +459,28 @@
 List application installations or instances.
 
 Usage:
-   device ls <app name patterns...>
+   device ls [flags] <app name patterns...>
 
 <app name patterns...> are vanadium object names or glob name patterns
 corresponding to application installations and instances.
 
+The device ls flags are:
+ -installation-state=
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled].
+ -instance-state=
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted].
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=FULL
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
 Device help - Display help for commands or topics
 
 Help with no args displays the usage of the parent command.
diff --git a/services/device/device/glob.go b/services/device/device/glob.go
index 63a0e4c..2ad6a6a 100644
--- a/services/device/device/glob.go
+++ b/services/device/device/glob.go
@@ -28,14 +28,17 @@
 	"v.io/x/ref/lib/v23cmd"
 )
 
-// globHandler is implemented by each command that wants to execute against name
+// GlobHandler is implemented by each command that wants to execute against name
 // patterns.  The handler is expected to be invoked against each glob result,
 // and can be run concurrently. The handler should direct its output to the
 // given stdout and stderr writers.
 //
-// Typical usage:
+// There are three usage patterns, depending on the desired level of control
+// over the execution flow and settings manipulation.
 //
-// func myCmdHandler(entry globResult, ctx *context.T, stdout, stderr io.Writer) error {
+// (1) Most control
+//
+// func myCmdHandler(entry GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
 //   output := myCmdProcessing(entry)
 //   fmt.Fprintf(stdout, output)
 //   ...
@@ -43,7 +46,7 @@
 //
 // func runMyCmd(ctx *context.T, env *cmdline.Env, args []string) error {
 //   ...
-//   err := run(ctx, env, args, myCmdHandler)
+//   err := Run(ctx, env, args, myCmdHandler, GlobSettings{})
 //   ...
 // }
 //
@@ -52,18 +55,37 @@
 //   ...
 // }
 //
-// Alternatively, if all runMyCmd does is to call run, you can use globRunner to
-// avoid having to define runMyCmd:
+// (2) Pre-packaged runner
+//
+// If all runMyCmd does is to call Run, you can use globRunner to avoid having
+// to define runMyCmd:
 //
 // var cmdMyCmd = &cmdline.Command {
-//   Runner: globRunner(myCmdHandler)
+//   Runner: globRunner(myCmdHandler, &GlobSettings{}),
+//   Name: "mycmd",
 //   ...
 // }
-type globHandler func(entry globResult, ctx *context.T, stdout, stderr io.Writer) error
+//
+// (3) Pre-packaged runner and glob settings flag configuration
+//
+// If, additionally, you want the GlobSettings to be configurable with
+// command-line flags, you can use globify instead:
+//
+// var cmdMyCmd = &cmdline.Command {
+//   Name: "mycmd",
+//   ...
+// }
+//
+// func init() {
+//   globify(cmdMyCmd, myCmdHandler, &GlobSettings{}),
+// }
+//
+// The GlobHandler identifier is exported for use in unit tests.
+type GlobHandler func(entry GlobResult, ctx *context.T, stdout, stderr io.Writer) error
 
-func globRunner(handler globHandler) cmdline.Runner {
+func globRunner(handler GlobHandler, s *GlobSettings) cmdline.Runner {
 	return v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
-		return run(ctx, env, args, handler)
+		return Run(ctx, env, args, handler, *s)
 	})
 }
 
@@ -89,13 +111,34 @@
 	}
 }
 
-type globResult struct {
+// GlobResult defines the input to a GlobHandler.
+// The identifier is exported for use in unit tests.
+type GlobResult struct {
 	name   string
 	status device.Status
 	kind   objectKind
 }
 
-type byTypeAndName []globResult
+func NewGlobResult(name string, status device.Status) (*GlobResult, error) {
+	var kind objectKind
+	switch status.(type) {
+	case device.StatusInstallation:
+		kind = applicationInstallationObject
+	case device.StatusInstance:
+		kind = applicationInstanceObject
+	case device.StatusDevice:
+		kind = deviceServiceObject
+	default:
+		return nil, fmt.Errorf("Status(%v) returned unrecognized status type %T\n", name, status)
+	}
+	return &GlobResult{
+		name:   name,
+		status: status,
+		kind:   kind,
+	}, nil
+}
+
+type byTypeAndName []*GlobResult
 
 func (a byTypeAndName) Len() int      { return len(a) }
 func (a byTypeAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
@@ -108,30 +151,31 @@
 	return r1.name < r2.name
 }
 
-// run runs the given handler in parallel against each of the results obtained
+// Run runs the given handler in parallel against each of the results obtained
 // by globbing args, after performing filtering based on type
 // (instance/installation) and state.  No de-duping of results is performed.
 // The outputs from each of the handlers are sorted: installations first, then
 // instances (and alphabetically by object name for each group).
-func run(ctx *context.T, env *cmdline.Env, args []string, handler globHandler) error {
+// The identifier is exported for use in unit tests.
+func Run(ctx *context.T, env *cmdline.Env, args []string, handler GlobHandler, s GlobSettings) error {
 	results := glob(ctx, env, args)
 	sort.Sort(byTypeAndName(results))
-	results = filterResults(results)
+	results = filterResults(results, s)
 	stdouts, stderrs := make([]bytes.Buffer, len(results)), make([]bytes.Buffer, len(results))
 	var errorCounter uint32 = 0
-	perResult := func(r globResult, index int) {
-		if err := handler(r, ctx, &stdouts[index], &stderrs[index]); err != nil {
+	perResult := func(r *GlobResult, index int) {
+		if err := handler(*r, ctx, &stdouts[index], &stderrs[index]); err != nil {
 			fmt.Fprintf(&stderrs[index], "ERROR for \"%s\": %v.\n", r.name, err)
 			atomic.AddUint32(&errorCounter, 1)
 		}
 	}
 	// TODO(caprita): Add unit test logic to cover all parallelism options.
-	switch handlerParallelism {
+	switch s.handlerParallelism {
 	case fullParallelism:
 		var wg sync.WaitGroup
 		for i, r := range results {
 			wg.Add(1)
-			go func(r globResult, i int) {
+			go func(r *GlobResult, i int) {
 				perResult(r, i)
 				wg.Done()
 			}(r, i)
@@ -151,7 +195,7 @@
 				}
 				wg.Add(1)
 				processed++
-				go func(r globResult, i int) {
+				go func(r *GlobResult, i int) {
 					perResult(r, i)
 					wg.Done()
 				}(r, i)
@@ -172,16 +216,16 @@
 	return nil
 }
 
-func filterResults(results []globResult) []globResult {
-	var ret []globResult
+func filterResults(results []*GlobResult, s GlobSettings) []*GlobResult {
+	var ret []*GlobResult
 	for _, r := range results {
-		switch s := r.status.(type) {
+		switch status := r.status.(type) {
 		case device.StatusInstance:
-			if onlyInstallations || !instanceStateFilter.apply(s.Value.State) {
+			if s.onlyInstallations || !s.instanceStateFilter.apply(status.Value.State) {
 				continue
 			}
 		case device.StatusInstallation:
-			if onlyInstances || !installationStateFilter.apply(s.Value.State) {
+			if s.onlyInstances || !s.installationStateFilter.apply(status.Value.State) {
 				continue
 			}
 		}
@@ -196,7 +240,7 @@
 // put them under a __debug suffix (like it works for services).
 var debugNameRE = regexp.MustCompile("/apps/[^/]+/[^/]+/[^/]+/(logs|stats|pprof)(/|$)")
 
-func getStatus(ctx *context.T, env *cmdline.Env, name string, resultsCh chan<- globResult) {
+func getStatus(ctx *context.T, env *cmdline.Env, name string, resultsCh chan<- *GlobResult) {
 	status, err := device.DeviceClient(name).Status(ctx)
 	// Skip non-instances/installations.
 	if verror.ErrorID(err) == deviceimpl.ErrInvalidSuffix.ID {
@@ -206,22 +250,14 @@
 		fmt.Fprintf(env.Stderr, "Status(%v) failed: %v\n", name, err)
 		return
 	}
-	var kind objectKind
-	switch status.(type) {
-	case device.StatusInstallation:
-		kind = applicationInstallationObject
-	case device.StatusInstance:
-		kind = applicationInstanceObject
-	case device.StatusDevice:
-		kind = deviceServiceObject
-	default:
-		fmt.Fprintf(env.Stderr, "Status(%v) returned unrecognized status type %T\n", name, status)
-		return
+	if r, err := NewGlobResult(name, status); err != nil {
+		fmt.Fprintf(env.Stderr, "%v\n", err)
+	} else {
+		resultsCh <- r
 	}
-	resultsCh <- globResult{name, status, kind}
 }
 
-func globOne(ctx *context.T, env *cmdline.Env, pattern string, resultsCh chan<- globResult) {
+func globOne(ctx *context.T, env *cmdline.Env, pattern string, resultsCh chan<- *GlobResult) {
 	globCh, err := v23.GetNamespace(ctx).Glob(ctx, pattern)
 	if err != nil {
 		fmt.Fprintf(env.Stderr, "Glob(%v) failed: %v\n", pattern, err)
@@ -250,11 +286,11 @@
 }
 
 // glob globs the given arguments and returns the results in arbitrary order.
-func glob(ctx *context.T, env *cmdline.Env, args []string) []globResult {
+func glob(ctx *context.T, env *cmdline.Env, args []string) []*GlobResult {
 	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	var wg sync.WaitGroup
-	resultsCh := make(chan globResult, 100)
+	resultsCh := make(chan *GlobResult, 100)
 	// For each pattern.
 	for _, a := range args {
 		wg.Add(1)
@@ -264,7 +300,7 @@
 		}(a)
 	}
 	// Collect the glob results into a slice.
-	var results []globResult
+	var results []*GlobResult
 	done := make(chan struct{})
 	go func() {
 		for r := range resultsCh {
@@ -361,8 +397,6 @@
 	kindParallelism: "BYKIND",
 }
 
-const defaultParallelism = kindParallelism
-
 func init() {
 	if len(parallelismStrings) != int(sentinelParallelismFlag) {
 		panic(fmt.Sprintf("broken invariant: mismatching number of parallelism types"))
@@ -387,35 +421,54 @@
 	return fmt.Errorf("unrecognized parallelism type: %v", s)
 }
 
-var (
+// GlobSettings specifies flag-settable options and filters for globbing.
+// The identifier is exported for use in unit tests.
+type GlobSettings struct {
 	instanceStateFilter     instanceStateFlag
 	installationStateFilter installationStateFlag
 	onlyInstances           bool
 	onlyInstallations       bool
-	handlerParallelism      parallelismFlag = defaultParallelism
-)
+	handlerParallelism      parallelismFlag
+	defaults                *GlobSettings
+}
 
-func init() {
-	// NOTE: When addind new flags or changing default values, remember to
-	// also update ResetGlobFlags below.
-	flag.Var(&instanceStateFilter, "instance-state", fmt.Sprintf("If non-empty, specifies allowed instance states (all other instances get filtered out). The value of the flag is a comma-separated list of values from among: %v.", device.InstanceStateAll))
-	flag.Var(&installationStateFilter, "installation-state", fmt.Sprintf("If non-empty, specifies allowed installation states (all others installations get filtered out). The value of the flag is a comma-separated list of values from among: %v.", device.InstallationStateAll))
-	flag.BoolVar(&onlyInstances, "only-instances", false, "If set, only consider instances.")
-	flag.BoolVar(&onlyInstallations, "only-installations", false, "If set, only consider installations.")
+func (s *GlobSettings) reset() {
+	d := s.defaults
+	*s = *d
+	s.defaults = d
+}
+
+func (s *GlobSettings) setDefaults(d GlobSettings) {
+	s.defaults = new(GlobSettings)
+	*s.defaults = d
+}
+
+var allGlobSettings []*GlobSettings
+
+// ResetGlobSettings is meant for tests to restore the values of flag-configured
+// variables when running multiple commands in the same process.
+func ResetGlobSettings() {
+	for _, s := range allGlobSettings {
+		s.reset()
+	}
+}
+
+func defineGlobFlags(fs *flag.FlagSet, s *GlobSettings) {
+	fs.Var(&s.instanceStateFilter, "instance-state", fmt.Sprintf("If non-empty, specifies allowed instance states (all other instances get filtered out). The value of the flag is a comma-separated list of values from among: %v.", device.InstanceStateAll))
+	fs.Var(&s.installationStateFilter, "installation-state", fmt.Sprintf("If non-empty, specifies allowed installation states (all others installations get filtered out). The value of the flag is a comma-separated list of values from among: %v.", device.InstallationStateAll))
+	fs.BoolVar(&s.onlyInstances, "only-instances", false, "If set, only consider instances.")
+	fs.BoolVar(&s.onlyInstallations, "only-installations", false, "If set, only consider installations.")
 	var parallelismValues []string
 	for _, v := range parallelismStrings {
 		parallelismValues = append(parallelismValues, v)
 	}
 	sort.Strings(parallelismValues)
-	flag.Var(&handlerParallelism, "parallelism", "Specifies the level of parallelism for the handler execution. One of: "+strings.Join(parallelismValues, ","))
+	fs.Var(&s.handlerParallelism, "parallelism", fmt.Sprintf("Specifies the level of parallelism for the handler execution. One of: %v.", parallelismValues))
 }
 
-// ResetGlobFlags is meant for tests to restore the values of flag-configured
-// variables when running multiple commands in the same process.
-func ResetGlobFlags() {
-	instanceStateFilter = make(instanceStateFlag)
-	installationStateFilter = make(installationStateFlag)
-	onlyInstances = false
-	onlyInstallations = false
-	handlerParallelism = defaultParallelism
+func globify(c *cmdline.Command, handler GlobHandler, s *GlobSettings) {
+	s.setDefaults(*s)
+	defineGlobFlags(&c.Flags, s)
+	c.Runner = globRunner(handler, s)
+	allGlobSettings = append(allGlobSettings, s)
 }
diff --git a/services/device/device/glob_test.go b/services/device/device/glob_test.go
new file mode 100644
index 0000000..1a2a395
--- /dev/null
+++ b/services/device/device/glob_test.go
@@ -0,0 +1,126 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"strings"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/device"
+
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+)
+
+func simplePrintHandler(entry cmd_device.GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
+	fmt.Fprintf(stdout, "%v\n", entry)
+	return nil
+}
+
+func TestGlob(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	tapes := newTapeMap()
+	rootTape := tapes.forSuffix("")
+	server, endpoint, err := startServer(t, ctx, tapes)
+	if err != nil {
+		return
+	}
+	appName := naming.JoinAddressName(endpoint.String(), "app")
+	defer stopServer(t, server)
+
+	allGlobArgs := []string{"glob1", "glob2"}
+	allGlobResponses := [][]string{
+		[]string{"app/3", "app/4", "app/6", "app/5", "app/9", "app/7"},
+		[]string{"app/2", "app/1", "app/8"},
+	}
+	allStatusResponses := map[string][]interface{}{
+		"app/1": []interface{}{instanceRunning},
+		"app/2": []interface{}{installationUninstalled},
+		"app/3": []interface{}{instanceUpdating},
+		"app/4": []interface{}{installationActive},
+		"app/5": []interface{}{instanceNotRunning},
+		"app/6": []interface{}{deviceService},
+		"app/7": []interface{}{installationActive},
+		"app/8": []interface{}{deviceUpdating},
+		"app/9": []interface{}{instanceUpdating},
+	}
+	outLine := func(suffix string, s device.Status) string {
+		r, err := cmd_device.NewGlobResult(appName+"/"+suffix, s)
+		if err != nil {
+			t.Errorf("NewGlobResult failed: %v", err)
+			return ""
+		}
+		return fmt.Sprintf("%v", *r)
+	}
+	var (
+		app1Out = outLine("1", instanceRunning)
+		app2Out = outLine("2", installationUninstalled)
+		app3Out = outLine("3", instanceUpdating)
+		app4Out = outLine("4", installationActive)
+		app5Out = outLine("5", instanceNotRunning)
+		app6Out = outLine("6", deviceService)
+		app7Out = outLine("7", installationActive)
+		app8Out = outLine("8", deviceUpdating)
+		app9Out = outLine("9", instanceUpdating)
+	)
+
+	for _, c := range []struct {
+		handler         cmd_device.GlobHandler
+		globResponses   [][]string
+		statusResponses map[string][]interface{}
+		gs              cmd_device.GlobSettings
+		globPatterns    []string
+		expected        string
+	}{
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{},
+			allGlobArgs,
+			// First installations, then instances, then device services.
+			joinLines(app2Out, app4Out, app7Out, app1Out, app3Out, app5Out, app9Out, app6Out, app8Out),
+		},
+		// TODO(caprita): Test the following cases:
+		// Various filters.
+		// Parallelism options.
+		// No glob arguments.
+		// No glob results.
+		// Error in glob.
+		// Error in status.
+		// Error in handler.
+	} {
+		tapes.rewind()
+		var rootTapeResponses []interface{}
+		for _, r := range c.globResponses {
+			rootTapeResponses = append(rootTapeResponses, GlobResponse{r})
+		}
+		rootTape.SetResponses(rootTapeResponses...)
+		for n, r := range c.statusResponses {
+			tapes.forSuffix(n).SetResponses(r...)
+		}
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		var args []string
+		for _, p := range c.globPatterns {
+			args = append(args, naming.JoinAddressName(endpoint.String(), p))
+		}
+		err := cmd_device.Run(ctx, env, args, c.handler, c.gs)
+		if err != nil {
+			t.Errorf("%v", err)
+		}
+		if expected, got := c.expected, strings.TrimSpace(stdout.String()); got != expected {
+			t.Errorf("Unexpected output. Got:\n%v\nExpected:\n%v\n", got, expected)
+		}
+	}
+}
diff --git a/services/device/device/ls.go b/services/device/device/ls.go
index 80939da..3ed7b7a 100644
--- a/services/device/device/ls.go
+++ b/services/device/device/ls.go
@@ -14,7 +14,6 @@
 )
 
 var cmdLs = &cmdline.Command{
-	Runner:   globRunner(runLs),
 	Name:     "ls",
 	Short:    "List applications.",
 	Long:     "List application installations or instances.",
@@ -23,7 +22,11 @@
 <app name patterns...> are vanadium object names or glob name patterns corresponding to application installations and instances.`,
 }
 
-func runLs(entry globResult, _ *context.T, stdout, _ io.Writer) error {
+func init() {
+	globify(cmdLs, runLs, new(GlobSettings))
+}
+
+func runLs(entry GlobResult, _ *context.T, stdout, _ io.Writer) error {
 	fmt.Fprintf(stdout, "%v\n", entry.name)
 	return nil
 }
diff --git a/services/device/device/ls_test.go b/services/device/device/ls_test.go
index 420dd05..975239d 100644
--- a/services/device/device/ls_test.go
+++ b/services/device/device/ls_test.go
@@ -6,7 +6,6 @@
 
 import (
 	"bytes"
-	"fmt"
 	"strings"
 	"testing"
 
@@ -46,9 +45,6 @@
 		"app/5": []interface{}{instanceNotRunning},
 		"app/6": []interface{}{installationActive},
 	}
-	joinLines := func(args ...string) string {
-		return strings.Join(args, "\n")
-	}
 	for _, c := range []struct {
 		globResponses   [][]string
 		statusResponses map[string][]interface{}
@@ -152,13 +148,12 @@
 			args = append(args, naming.JoinAddressName(endpoint.String(), p))
 		}
 		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, args); err != nil {
-			fmt.Println("run test case error", err)
 			t.Errorf("%v", err)
 		}
 
 		if expected, got := c.expected, strings.TrimSpace(stdout.String()); got != expected {
 			t.Errorf("Unexpected output from ls. Got %q, expected %q", got, expected)
 		}
-		cmd_device.ResetGlobFlags()
+		cmd_device.ResetGlobSettings()
 	}
 }
diff --git a/services/device/device/status.go b/services/device/device/status.go
index d32430e..34a17d2 100644
--- a/services/device/device/status.go
+++ b/services/device/device/status.go
@@ -14,7 +14,6 @@
 )
 
 var cmdStatus = &cmdline.Command{
-	Runner:   globRunner(runStatus),
 	Name:     "status",
 	Short:    "Get device manager or application status.",
 	Long:     "Get the status of the device manager or application instances and installations.",
@@ -23,7 +22,11 @@
 <name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
 }
 
-func runStatus(entry globResult, _ *context.T, stdout, _ io.Writer) error {
+func init() {
+	globify(cmdStatus, runStatus, new(GlobSettings))
+}
+
+func runStatus(entry GlobResult, _ *context.T, stdout, _ io.Writer) error {
 	switch s := entry.status.(type) {
 	case device.StatusInstance:
 		fmt.Fprintf(stdout, "Instance %v [State:%v,Version:%v]\n", entry.name, s.Value.State, s.Value.Version)
diff --git a/services/device/device/status_test.go b/services/device/device/status_test.go
index 4284226..0f2d8e0 100644
--- a/services/device/device/status_test.go
+++ b/services/device/device/status_test.go
@@ -69,5 +69,6 @@
 		if got, expected := appTape.Play(), []interface{}{"Status"}; !reflect.DeepEqual(expected, got) {
 			t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
 		}
+		cmd_device.ResetGlobSettings()
 	}
 }
diff --git a/services/device/device/update.go b/services/device/device/update.go
index e2d1f9a..0f83e23 100644
--- a/services/device/device/update.go
+++ b/services/device/device/update.go
@@ -20,7 +20,6 @@
 )
 
 var cmdUpdate = &cmdline.Command{
-	Runner:   globRunner(runUpdate),
 	Name:     "update",
 	Short:    "Update the device manager or applications.",
 	Long:     "Update the device manager or application instances and installations",
@@ -29,8 +28,11 @@
 <name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
 }
 
+func init() {
+	globify(cmdUpdate, runUpdate, &GlobSettings{handlerParallelism: kindParallelism})
+}
+
 var cmdRevert = &cmdline.Command{
-	Runner:   globRunner(runRevert),
 	Name:     "revert",
 	Short:    "Revert the device manager or applications.",
 	Long:     "Revert the device manager or application instances and installations to a previous version of their current version",
@@ -39,6 +41,10 @@
 <name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
 }
 
+func init() {
+	globify(cmdRevert, runRevert, &GlobSettings{handlerParallelism: kindParallelism})
+}
+
 func instanceIsRunning(ctx *context.T, von string) (bool, error) {
 	status, err := device.ApplicationClient(von).Status(ctx)
 	if err != nil {
@@ -125,7 +131,7 @@
 	}
 }
 
-func changeVersion(entry globResult, ctx *context.T, stdout, stderr io.Writer, revert bool) error {
+func changeVersion(entry GlobResult, ctx *context.T, stdout, stderr io.Writer, revert bool) error {
 	switch entry.kind {
 	case applicationInstanceObject:
 		return changeVersionInstance(ctx, stdout, stderr, entry.name, entry.status.(device.StatusInstance), revert)
@@ -138,10 +144,10 @@
 	}
 }
 
-func runUpdate(entry globResult, ctx *context.T, stdout, stderr io.Writer) error {
+func runUpdate(entry GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
 	return changeVersion(entry, ctx, stdout, stderr, false)
 }
 
-func runRevert(entry globResult, ctx *context.T, stdout, stderr io.Writer) error {
+func runRevert(entry GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
 	return changeVersion(entry, ctx, stdout, stderr, true)
 }
diff --git a/services/device/device/update_test.go b/services/device/device/update_test.go
index a6f0a40..07f7616 100644
--- a/services/device/device/update_test.go
+++ b/services/device/device/update_test.go
@@ -145,7 +145,7 @@
 					t.Errorf("Unexpected stimuli for %v. Want: %v, got %v.", n, want, got)
 				}
 			}
-			cmd_device.ResetGlobFlags()
+			cmd_device.ResetGlobSettings()
 		}
 	}
 }
diff --git a/services/device/device/util_test.go b/services/device/device/util_test.go
index 01bd228..c1e67c6 100644
--- a/services/device/device/util_test.go
+++ b/services/device/device/util_test.go
@@ -37,7 +37,7 @@
 	}}
 	instanceRunning = device.StatusInstance{device.InstanceStatus{
 		State:   device.InstanceStateRunning,
-		Version: "special edition",
+		Version: "tv version",
 	}}
 	instanceNotRunning = device.StatusInstance{device.InstanceStatus{
 		State:   device.InstanceStateNotRunning,
@@ -47,6 +47,10 @@
 		State:   device.InstanceStateRunning,
 		Version: "han shot first",
 	}}
+	deviceUpdating = device.StatusDevice{device.DeviceStatus{
+		State:   device.InstanceStateUpdating,
+		Version: "international release",
+	}}
 )
 
 func testHelper(t *testing.T, lower, upper string) {
@@ -113,3 +117,7 @@
 		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
 	}
 }
+
+func joinLines(args ...string) string {
+	return strings.Join(args, "\n")
+}
diff --git a/services/device/deviced/doc.go b/services/device/deviced/doc.go
index 673e5be..22142a1 100644
--- a/services/device/deviced/doc.go
+++ b/services/device/deviced/doc.go
@@ -104,7 +104,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
  -workspace=
    Path to the application's workspace directory.
 
diff --git a/services/device/internal/impl/app_service.go b/services/device/internal/impl/app_service.go
index 8b1ce68..00ceb43 100644
--- a/services/device/internal/impl/app_service.go
+++ b/services/device/internal/impl/app_service.go
@@ -190,15 +190,15 @@
 	keyMgrAgent *keymgr.Agent
 }
 
-// appStart is the subset of the appService object needed to
+// appRunner is the subset of the appService object needed to
 // (re)start an application.
-type appStartState struct {
-	systemName  string
-	instanceDir string
-	callback    *callbackState
+type appRunner struct {
+	callback *callbackState
 	// securityAgent holds state related to the security agent (nil if not
 	// using the agent).
 	securityAgent *securityAgentState
+	// reap is the app process monitoring subsystem.
+	reap *reaper
 	// mtAddress is the address of the local mounttable.
 	mtAddress string
 }
@@ -214,9 +214,8 @@
 	permsStore *pathperms.PathStore
 	// Reference to the devicemanager top-level AccessList list.
 	deviceAccessList access.Permissions
-	// reap is the app process monitoring subsystem.
-	reap     reaper
-	appStart *appStartState
+	// State needed to (re)start an application.
+	runner *appRunner
 }
 
 func saveEnvelope(ctx *context.T, dir string, envelope *application.Envelope) error {
@@ -242,6 +241,15 @@
 	return envelope, nil
 }
 
+func loadEnvelopeForInstance(ctx *context.T, instanceDir string) (*application.Envelope, error) {
+	versionLink := filepath.Join(instanceDir, "version")
+	versionDir, err := filepath.EvalSymlinks(versionLink)
+	if err != nil {
+		return nil, verror.New(ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", versionLink, err))
+	}
+	return loadEnvelope(ctx, versionDir)
+}
+
 func saveConfig(ctx *context.T, dir string, config device.Config) error {
 	jsonConfig, err := json.Marshal(config)
 	if err != nil {
@@ -712,7 +720,7 @@
 		return instanceDir, instanceID, verror.New(ErrOperationFailed, ctx, fmt.Sprintf("Symlink(%v, %v) failed: %v", packagesDir, packagesLink, err))
 	}
 	instanceInfo := new(instanceInfo)
-	if err := setupPrincipal(ctx, instanceDir, call, i.appStart.securityAgent, instanceInfo); err != nil {
+	if err := setupPrincipal(ctx, instanceDir, call, i.runner.securityAgent, instanceInfo); err != nil {
 		return instanceDir, instanceID, err
 	}
 	if err := saveInstanceInfo(ctx, instanceDir, instanceInfo); err != nil {
@@ -735,10 +743,11 @@
 	return instanceDir, instanceID, nil
 }
 
-func (i appStartState) genCmd(ctx *context.T) (*exec.Cmd, error) {
-	instanceDir := i.instanceDir
-	systemName := i.systemName
-	nsRoot := i.mtAddress
+func genCmd(ctx *context.T, instanceDir string, nsRoot string) (*exec.Cmd, error) {
+	systemName, err := readSystemNameForInstance(instanceDir)
+	if err != nil {
+		return nil, err
+	}
 
 	versionLink := filepath.Join(instanceDir, "version")
 	versionDir, err := filepath.EvalSymlinks(versionLink)
@@ -802,8 +811,7 @@
 	return suidHelper.getAppCmd(&saArgs)
 }
 
-func (i appStartState) startCmd(ctx *context.T, cmd *exec.Cmd) (int, error) {
-	instanceDir := i.instanceDir
+func (i *appRunner) startCmd(ctx *context.T, instanceDir string, cmd *exec.Cmd) (int, error) {
 	info, err := loadInstanceInfo(ctx, instanceDir)
 	if err != nil {
 		return 0, err
@@ -905,27 +913,51 @@
 	return pid, nil
 }
 
-func (i appStartState) run(ctx *context.T, reap reaper) error {
-	if err := transitionInstance(i.instanceDir, device.InstanceStateNotRunning, device.InstanceStateLaunching); err != nil {
+func (i *appRunner) run(ctx *context.T, instanceDir string) error {
+	if err := transitionInstance(instanceDir, device.InstanceStateNotRunning, device.InstanceStateLaunching); err != nil {
 		return err
 	}
 	var pid int
 
-	cmd, err := i.genCmd(ctx)
+	cmd, err := genCmd(ctx, instanceDir, i.mtAddress)
 	if err == nil {
-		pid, err = i.startCmd(ctx, cmd)
+		pid, err = i.startCmd(ctx, instanceDir, cmd)
 	}
 	if err != nil {
-		transitionInstance(i.instanceDir, device.InstanceStateLaunching, device.InstanceStateNotRunning)
+		transitionInstance(instanceDir, device.InstanceStateLaunching, device.InstanceStateNotRunning)
 		return err
 	}
-	if err := transitionInstance(i.instanceDir, device.InstanceStateLaunching, device.InstanceStateRunning); err != nil {
+	if err := transitionInstance(instanceDir, device.InstanceStateLaunching, device.InstanceStateRunning); err != nil {
 		return err
 	}
-	reap.startWatching(i.instanceDir, pid)
+	i.reap.startWatching(instanceDir, pid)
 	return nil
 }
 
+func (i *appRunner) restartAppIfNecessary(instanceDir string) {
+	info, err := loadInstanceInfo(nil, instanceDir)
+	if err != nil {
+		vlog.Error(err)
+		return
+	}
+
+	envelope, err := loadEnvelopeForInstance(nil, instanceDir)
+	if err != nil {
+		vlog.Error(err)
+		return
+	}
+
+	// Determine if we should restart.
+	if !neverStart().decide(envelope, info) {
+		return
+	}
+
+	// TODO(rjkroege): Implement useful restart policy.
+	if err := i.run(nil, instanceDir); err != nil {
+		vlog.Error(err)
+	}
+}
+
 func (i *appService) Instantiate(ctx *context.T, call device.ApplicationInstantiateServerCall) (string, error) {
 	helper := i.config.Helper
 	instanceDir, instanceID, err := i.newInstance(ctx, call)
@@ -976,10 +1008,7 @@
 	if startSystemName != systemName {
 		return verror.New(verror.ErrNoAccess, ctx, "Not allowed to resume an application under a different system name.")
 	}
-
-	i.appStart.instanceDir = instanceDir
-	i.appStart.systemName = systemName
-	return i.appStart.run(ctx, i.reap)
+	return i.runner.run(ctx, instanceDir)
 }
 
 func stopAppRemotely(ctx *context.T, appVON string, deadline time.Duration) error {
@@ -1003,7 +1032,7 @@
 	return nil
 }
 
-func stop(ctx *context.T, instanceDir string, reap reaper, deadline time.Duration) error {
+func stop(ctx *context.T, instanceDir string, reap *reaper, deadline time.Duration) error {
 	info, err := loadInstanceInfo(ctx, instanceDir)
 	if err != nil {
 		return err
@@ -1032,7 +1061,7 @@
 	if err := transitionInstance(instanceDir, device.InstanceStateRunning, device.InstanceStateDying); err != nil {
 		return err
 	}
-	if err := stop(ctx, instanceDir, i.reap, deadline); err != nil {
+	if err := stop(ctx, instanceDir, i.runner.reap, deadline); err != nil {
 		transitionInstance(instanceDir, device.InstanceStateDying, device.InstanceStateRunning)
 		return err
 	}
@@ -1484,21 +1513,19 @@
 	} else {
 		debugInfo.StartSystemName = startSystemName
 	}
-	as := *i.appStart
-	as.instanceDir = instanceDir
-	as.systemName = debugInfo.SystemName
-	if cmd, err := as.genCmd(ctx); err != nil {
-		return "", err
-	} else {
-		debugInfo.Cmd = cmd
-	}
+
 	if info, err := loadInstanceInfo(ctx, instanceDir); err != nil {
 		return "", err
 	} else {
 		debugInfo.Info = info
 	}
+	if cmd, err := genCmd(ctx, instanceDir, i.runner.mtAddress); err != nil {
+		return "", err
+	} else {
+		debugInfo.Cmd = cmd
+	}
 
-	if sa := i.appStart.securityAgent; sa != nil {
+	if sa := i.runner.securityAgent; sa != nil {
 		file, err := sa.keyMgrAgent.NewConnection(debugInfo.Info.SecurityAgentHandle)
 		if err != nil {
 			vlog.Errorf("NewConnection(%v) failed: %v", debugInfo.Info.SecurityAgentHandle, err)
diff --git a/services/device/internal/impl/dispatcher.go b/services/device/internal/impl/dispatcher.go
index 86f57f4..62d4787 100644
--- a/services/device/internal/impl/dispatcher.go
+++ b/services/device/internal/impl/dispatcher.go
@@ -41,6 +41,8 @@
 	securityAgent  *securityAgentState
 	restartHandler func()
 	testMode       bool
+	// reap is the app process monitoring subsystem.
+	reap *reaper
 }
 
 // dispatcher holds the state of the device manager dispatcher.
@@ -57,8 +59,6 @@
 	permsStore *pathperms.PathStore
 	// Namespace
 	mtAddress string // The address of the local mounttable.
-	// reap is the app process monitoring subsystem.
-	reap reaper
 }
 
 var _ rpc.Dispatcher = (*dispatcher)(nil)
@@ -118,10 +118,6 @@
 	if err != nil {
 		return nil, verror.New(errCantCreateAccountStore, ctx, err)
 	}
-	reap, err := newReaper(ctx, config.Root)
-	if err != nil {
-		return nil, verror.New(errCantCreateAppWatcher, ctx, err)
-	}
 	initSuidHelper(config.Helper)
 	d := &dispatcher{
 		internal: &internalState{
@@ -134,7 +130,6 @@
 		uat:        uat,
 		permsStore: permStore,
 		mtAddress:  mtAddress,
-		reap:       reap,
 	}
 
 	// If we're in 'security agent mode', set up the key manager agent.
@@ -147,6 +142,15 @@
 			}
 		}
 	}
+	reap, err := newReaper(ctx, config.Root, &appRunner{
+		callback:      d.internal.callback,
+		securityAgent: d.internal.securityAgent,
+	})
+	if err != nil {
+		return nil, verror.New(errCantCreateAppWatcher, ctx, err)
+	}
+	d.internal.reap = reap
+
 	if testMode {
 		return &testModeDispatcher{d}, nil
 	}
@@ -157,7 +161,7 @@
 func Shutdown(rpcd rpc.Dispatcher) {
 	switch d := rpcd.(type) {
 	case *dispatcher:
-		d.reap.shutdown()
+		d.internal.reap.shutdown()
 	case *testModeDispatcher:
 		Shutdown(d.realDispatcher)
 	default:
@@ -326,8 +330,8 @@
 			suffix:     components[1:],
 			uat:        d.uat,
 			permsStore: d.permsStore,
-			reap:       d.reap,
-			appStart: &appStartState{
+			runner: &appRunner{
+				reap:          d.internal.reap,
 				callback:      d.internal.callback,
 				securityAgent: d.internal.securityAgent,
 				mtAddress:     d.mtAddress,
diff --git a/services/device/internal/impl/instance_reaping.go b/services/device/internal/impl/instance_reaping.go
index 59ae4b0..e300283 100644
--- a/services/device/internal/impl/instance_reaping.go
+++ b/services/device/internal/impl/instance_reaping.go
@@ -43,11 +43,14 @@
 	pid         int
 }
 
-type reaper chan pidInstanceDirPair
+type reaper struct {
+	c          chan pidInstanceDirPair
+	startState *appRunner
+}
 
 var stashedPidMap map[string]int
 
-func newReaper(ctx *context.T, root string) (reaper, error) {
+func newReaper(ctx *context.T, root string, startState *appRunner) (*reaper, error) {
 	pidMap, err := findAllTheInstances(ctx, root)
 
 	// Used only by the testing code that verifies that all processes
@@ -57,9 +60,13 @@
 		return nil, err
 	}
 
-	c := make(reaper)
-	go processStatusPolling(c, pidMap)
-	return c, nil
+	r := &reaper{
+		c:          make(chan pidInstanceDirPair),
+		startState: startState,
+	}
+	r.startState.reap = r
+	go r.processStatusPolling(pidMap)
+	return r, nil
 }
 
 func markNotRunning(idir string) {
@@ -77,7 +84,7 @@
 // functionality. For example, use the kevent facility in darwin or
 // replace init. See http://www.incenp.org/dvlpt/wait4.html for
 // inspiration.
-func processStatusPolling(r reaper, trackedPids map[string]int) {
+func (r *reaper) processStatusPolling(trackedPids map[string]int) {
 	poll := func() {
 		for idir, pid := range trackedPids {
 			switch err := syscall.Kill(pid, 0); err {
@@ -85,6 +92,7 @@
 				// No such PID.
 				vlog.VI(2).Infof("processStatusPolling discovered pid %d ended", pid)
 				markNotRunning(idir)
+				r.startState.restartAppIfNecessary(idir)
 				delete(trackedPids, idir)
 			case nil, syscall.EPERM:
 				vlog.VI(2).Infof("processStatusPolling saw live pid: %d", pid)
@@ -99,6 +107,11 @@
 				// TODO(rjkroege): Probe the appcycle service of the app
 				// to confirm that its pid is valid iff v23PIDMgmt
 				// is false.
+
+				// TODO(rjkroege): if we can't connect to the app here via
+				// the appcycle manager, the app was probably started under
+				// a different agent and cannot be managed. Perhaps we should
+				// then kill the app and restart it?
 			default:
 				// The kill system call manpage says that this can only happen
 				// if the kernel claims that 0 is an invalid signal.
@@ -111,7 +124,7 @@
 
 	for {
 		select {
-		case p, ok := <-r:
+		case p, ok := <-r.c:
 			switch {
 			case !ok:
 				return
@@ -139,22 +152,22 @@
 // startWatching begins watching process pid's state. This routine
 // assumes that pid already exists. Since pid is delivered to the device
 // manager by RPC callback, this seems reasonable.
-func (r reaper) startWatching(idir string, pid int) {
-	r <- pidInstanceDirPair{instanceDir: idir, pid: pid}
+func (r *reaper) startWatching(idir string, pid int) {
+	r.c <- pidInstanceDirPair{instanceDir: idir, pid: pid}
 }
 
 // stopWatching stops watching process pid's state.
-func (r reaper) stopWatching(idir string) {
-	r <- pidInstanceDirPair{instanceDir: idir, pid: -1}
+func (r *reaper) stopWatching(idir string) {
+	r.c <- pidInstanceDirPair{instanceDir: idir, pid: -1}
 }
 
 // forciblySuspend terminates the process pid
-func (r reaper) forciblySuspend(idir string) {
-	r <- pidInstanceDirPair{instanceDir: idir, pid: -2}
+func (r *reaper) forciblySuspend(idir string) {
+	r.c <- pidInstanceDirPair{instanceDir: idir, pid: -2}
 }
 
-func (r reaper) shutdown() {
-	close(r)
+func (r *reaper) shutdown() {
+	close(r.c)
 }
 
 type pidErrorTuple struct {
diff --git a/services/device/internal/impl/restart_policy.go b/services/device/internal/impl/restart_policy.go
new file mode 100644
index 0000000..57c6cff
--- /dev/null
+++ b/services/device/internal/impl/restart_policy.go
@@ -0,0 +1,36 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package impl
+
+import (
+	"v.io/v23/services/application"
+)
+
+// RestartPolicy instances provide a policy for deciding if an
+// application should be restarted on failure.
+type restartPolicy interface {
+	// decide determines if this application instance should be (re)started, returning
+	// true if the application should be be (re)started.
+	decide(envelope *application.Envelope, instance *instanceInfo) bool
+}
+
+// startStub implements a stub RestartPolicy.
+type startStub struct {
+	start bool
+}
+
+// alwaysStart returns a RestartPolicy that always (re)starts the application.
+func alwaysStart() restartPolicy {
+	return startStub{true}
+}
+
+// neverStart returns a RestartPolicy that never (re)starts the application.
+func neverStart() restartPolicy {
+	return startStub{false}
+}
+
+func (s startStub) decide(envelope *application.Envelope, instance *instanceInfo) bool {
+	return s.start
+}
diff --git a/services/identity/identityd/doc.go b/services/identity/identityd/doc.go
index 76b91c1..7f250bd 100644
--- a/services/identity/identityd/doc.go
+++ b/services/identity/identityd/doc.go
@@ -106,6 +106,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/identity/internal/identityd_test/doc.go b/services/identity/internal/identityd_test/doc.go
index d7d7b6e..187dc33 100644
--- a/services/identity/internal/identityd_test/doc.go
+++ b/services/identity/internal/identityd_test/doc.go
@@ -81,6 +81,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/mounttable/mounttabled/doc.go b/services/mounttable/mounttabled/doc.go
index 8c86b66..ca4b4bf 100644
--- a/services/mounttable/mounttabled/doc.go
+++ b/services/mounttable/mounttabled/doc.go
@@ -72,6 +72,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
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)
 }
diff --git a/services/profile/profile/doc.go b/services/profile/profile/doc.go
index 01e753d..753f3a0 100644
--- a/services/profile/profile/doc.go
+++ b/services/profile/profile/doc.go
@@ -59,7 +59,9 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 
 Profile label
 
diff --git a/services/profile/profiled/doc.go b/services/profile/profiled/doc.go
index 07c2b63..b53f596 100644
--- a/services/profile/profiled/doc.go
+++ b/services/profile/profiled/doc.go
@@ -63,6 +63,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/proxy/proxyd/doc.go b/services/proxy/proxyd/doc.go
index 7fd0915..9fd249d 100644
--- a/services/proxy/proxyd/doc.go
+++ b/services/proxy/proxyd/doc.go
@@ -72,6 +72,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/services/role/roled/doc.go b/services/role/roled/doc.go
index 7f0ea03..9667aeb 100644
--- a/services/role/roled/doc.go
+++ b/services/role/roled/doc.go
@@ -62,6 +62,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/test/hello/helloclient/doc.go b/test/hello/helloclient/doc.go
index b5467e0..63971ac 100644
--- a/test/hello/helloclient/doc.go
+++ b/test/hello/helloclient/doc.go
@@ -55,6 +55,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main
diff --git a/test/hello/helloserver/doc.go b/test/hello/helloserver/doc.go
index 4029e67..c729180 100644
--- a/test/hello/helloserver/doc.go
+++ b/test/hello/helloserver/doc.go
@@ -55,6 +55,8 @@
  -v23.vtrace.sample-rate=0
    Rate (from 0.0 to 1.0) to sample vtrace traces.
  -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
 */
 package main