Merge "veyron/runtimes/google/ipc: Pass client to server to allow discharge client to use client from context."
diff --git a/lib/modules/shell.go b/lib/modules/shell.go
index c6cd898..cc14137 100644
--- a/lib/modules/shell.go
+++ b/lib/modules/shell.go
@@ -136,7 +136,7 @@
 	return sh, nil
 }
 
-func (sh *Shell) getChildCredentials() (*os.File, error) {
+func (sh *Shell) getChildCredentials() (c *os.File, err error) {
 	if sh.ctx == nil {
 		return nil, nil
 	}
@@ -146,11 +146,16 @@
 	if err != nil {
 		return nil, err
 	}
+	defer func() {
+		if err != nil {
+			conn.Close()
+		}
+	}()
 	ctx, cancel := context.WithCancel(sh.ctx)
+	defer cancel()
 	if ctx, _, err = veyron2.SetNewStreamManager(ctx); err != nil {
 		return nil, err
 	}
-	defer cancel()
 	syscall.ForkLock.RLock()
 	fd, err := syscall.Dup(int(conn.Fd()))
 	if err != nil {
@@ -161,6 +166,7 @@
 	syscall.ForkLock.RUnlock()
 	p, err := agent.NewAgentPrincipal(ctx, fd, veyron2.GetClient(ctx))
 	if err != nil {
+		syscall.Close(fd)
 		return nil, err
 	}
 	blessingForChild, err := root.Bless(p.PublicKey(), rootBlessing, childBlessingExtension, security.UnconstrainedUse())
diff --git a/lib/unixfd/unixfd.go b/lib/unixfd/unixfd.go
index fd17a93..fa0fe85 100644
--- a/lib/unixfd/unixfd.go
+++ b/lib/unixfd/unixfd.go
@@ -231,12 +231,16 @@
 	// This is to work around a race on OS X where it appears we can close
 	// the file descriptor before it gets transfered over the socket.
 	f := local.releaseFile()
+	syscall.ForkLock.Lock()
 	fd, err := syscall.Dup(int(f.Fd()))
 	if err != nil {
+		syscall.ForkLock.Unlock()
 		f.Close()
 		rfile.Close()
 		return nil, err
 	}
+	syscall.CloseOnExec(fd)
+	syscall.ForkLock.Unlock()
 	newConn, err := net.FileConn(f)
 	f.Close()
 	if err != nil {
@@ -290,11 +294,15 @@
 		return nil, n, nil, nil
 	}
 	result := Addr(uintptr(fd))
+	syscall.ForkLock.Lock()
 	fd, err = syscall.Dup(fd)
 	if err != nil {
+		syscall.ForkLock.Unlock()
 		CloseUnixAddr(result)
 		return nil, n, nil, err
 	}
+	syscall.CloseOnExec(fd)
+	syscall.ForkLock.Unlock()
 	file := os.NewFile(uintptr(fd), "newconn")
 	newconn, err := net.FileConn(file)
 	file.Close()
diff --git a/security/agent/client.go b/security/agent/client.go
index f3128f9..77c6dde 100644
--- a/security/agent/client.go
+++ b/security/agent/client.go
@@ -57,7 +57,8 @@
 // NewAgentPrincipal returns a security.Pricipal using the PrivateKey held in a remote agent process.
 // 'fd' is the socket for connecting to the agent, typically obtained from
 // os.GetEnv(agent.FdVarName).
-// 'ctx' should not have a deadline, and should never be cancelled.
+// 'ctx' should not have a deadline, and should never be cancelled while the
+// principal is in use.
 func NewAgentPrincipal(ctx *context.T, fd int, insecureClient ipc.Client) (security.Principal, error) {
 	f := os.NewFile(uintptr(fd), "agent_client")
 	defer f.Close()
diff --git a/security/blessingroots_test.go b/security/blessingroots_test.go
index 49b599f..9e06948 100644
--- a/security/blessingroots_test.go
+++ b/security/blessingroots_test.go
@@ -29,7 +29,7 @@
 	}{
 		{t[0], "veyron/..."},
 		{t[1], "google/foo/..."},
-		{t[0], "google"},
+		{t[0], "google/$"},
 	}
 	for _, d := range testdata {
 		if err := br.Add(d.root, d.pattern); err != nil {
diff --git a/security/blessingstore_test.go b/security/blessingstore_test.go
index d328da4..a9b2906 100644
--- a/security/blessingstore_test.go
+++ b/security/blessingstore_test.go
@@ -22,13 +22,12 @@
 		wantErr   string
 	}{
 		{t.forAll, "...", ""},
+		{t.forAll, "$", ""},
 		{t.forFoo, "foo/...", ""},
-		{t.forBar, "bar", ""},
+		{t.forBar, "bar/$", ""},
 		{t.other, "...", "public key does not match"},
 		{t.forAll, "", "invalid BlessingPattern"},
-		{t.forAll, "foo...", "invalid BlessingPattern"},
-		{t.forAll, "...foo", "invalid BlessingPattern"},
-		{t.forAll, "foo/.../bar", "invalid BlessingPattern"},
+		{t.forAll, "foo/$/bar", "invalid BlessingPattern"},
 	}
 	added := make(map[security.BlessingPattern]security.Blessings)
 	for _, d := range testdata {
@@ -193,7 +192,7 @@
 	// Set(alice, "alice")
 	// Set(bob, "alice/...")
 	// So, {alice, bob} is shared with "alice", whilst {bob} is shared with "alice/tv"
-	if _, err := s.Set(alice, "alice"); err != nil {
+	if _, err := s.Set(alice, "alice/$"); err != nil {
 		t.Fatal(err)
 	}
 	if _, err := s.Set(bob, "alice/..."); err != nil {
@@ -208,7 +207,7 @@
 
 	// Clear out the blessing associated with "alice".
 	// Now, bob should be shared with both alice and alice/friend.
-	if _, err := s.Set(nil, "alice"); err != nil {
+	if _, err := s.Set(nil, "alice/$"); err != nil {
 		t.Fatal(err)
 	}
 	if got, want := s.ForPeer("alice"), bob; !reflect.DeepEqual(got, want) {
@@ -219,7 +218,7 @@
 	}
 
 	// Clearing out an association that doesn't exist should have no effect.
-	if _, err := s.Set(nil, "alice/enemy"); err != nil {
+	if _, err := s.Set(nil, "alice/enemy/$"); err != nil {
 		t.Fatal(err)
 	}
 	if got, want := s.ForPeer("alice"), bob; !reflect.DeepEqual(got, want) {
diff --git a/security/flag/flag_test.go b/security/flag/flag_test.go
index fbf46bc..2416001 100644
--- a/security/flag/flag_test.go
+++ b/security/flag/flag_test.go
@@ -25,10 +25,10 @@
 	acl1 = access.TaggedACLMap{}
 	acl2 = access.TaggedACLMap{
 		string(access.Read): access.ACL{
-			In: []security.BlessingPattern{"veyron/alice", "veyron/bob"},
+			In: []security.BlessingPattern{"veyron/alice/$", "veyron/bob"},
 		},
 		string(access.Write): access.ACL{
-			In: []security.BlessingPattern{"veyron/alice"},
+			In: []security.BlessingPattern{"veyron/alice/$"},
 		},
 	}
 
@@ -94,7 +94,7 @@
 		},
 		{
 			cmd:   "tamFromFlag",
-			flags: []string{"--veyron.acl.literal", `{"Read": {"In":["veyron/alice", "veyron/bob"]}, "Write": {"In":["veyron/alice"]}}`},
+			flags: []string{"--veyron.acl.literal", `{"Read": {"In":["veyron/alice/$", "veyron/bob"]}, "Write": {"In":["veyron/alice/$"]}}`},
 			auth:  "acl2",
 		},
 	}
diff --git a/services/mgmt/binary/impl/acl_test.go b/services/mgmt/binary/impl/acl_test.go
index 8962d52..6c16291 100644
--- a/services/mgmt/binary/impl/acl_test.go
+++ b/services/mgmt/binary/impl/acl_test.go
@@ -20,7 +20,6 @@
 	"v.io/core/veyron/lib/signals"
 	"v.io/core/veyron/lib/testutil"
 	tsecurity "v.io/core/veyron/lib/testutil/security"
-	vsecurity "v.io/core/veyron/security"
 	"v.io/core/veyron/services/mgmt/binary/impl"
 	mgmttest "v.io/core/veyron/services/mgmt/lib/testutil"
 )
@@ -95,27 +94,15 @@
 	defer cleanup()
 	prepDirectory(t, storedir)
 
-	otherPrincipal, err := vsecurity.NewPrincipal()
-	if err != nil {
-		t.Fatalf("NewPrincipal() failed: %v", err)
+	otherPrincipal := tsecurity.NewPrincipal("other")
+	if err := otherPrincipal.AddToRoots(selfPrincipal.BlessingStore().Default()); err != nil {
+		t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err)
 	}
 	otherCtx, err := veyron2.SetPrincipal(selfCtx, otherPrincipal)
 	if err != nil {
 		t.Fatalf("SetPrincipal() failed: %v", err)
 	}
 
-	selfBlessing := selfPrincipal.BlessingStore().Default()
-	otherBlessing, err := selfPrincipal.Bless(otherPrincipal.PublicKey(), selfBlessing, "other", security.UnconstrainedUse())
-	if err != nil {
-		t.Fatalf("selfPrincipal.Bless() failed: %v", err)
-	}
-	if _, err := otherPrincipal.BlessingStore().Set(otherBlessing, "self/child"); err != nil {
-		t.Fatalf("otherPrincipal.BlessingStore() failed: %v", err)
-	}
-	if err := otherPrincipal.AddToRoots(otherBlessing); err != nil {
-		t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err)
-	}
-
 	_, nms := mgmttest.RunShellCommand(t, sh, nil, binaryCmd, "bini", storedir)
 	pid := mgmttest.ReadPID(t, nms)
 	defer syscall.Kill(pid, syscall.SIGINT)
@@ -139,7 +126,11 @@
 		t.Fatalf("invokeUpload() failed %v, %v", err, streamErr)
 	}
 
-	vlog.VI(2).Infof("Verify that other can't access bini/private")
+	vlog.VI(2).Infof("Verify that in the beginning other can't access bini/private or bini/shared")
+	binary = repository.BinaryClient("bini/private")
+	if _, _, err := binary.Stat(otherCtx); !verror.Is(err, verror.NoAccess) {
+		t.Fatalf("Stat() should have failed but didn't: %v", err)
+	}
 	binary = repository.BinaryClient("bini/shared")
 	if _, _, err := binary.Stat(otherCtx); !verror.Is(err, verror.NoAccess) {
 		t.Fatalf("Stat() should have failed but didn't: %v", err)
@@ -153,36 +144,47 @@
 	}
 	expected := access.TaggedACLMap{"Admin": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}, "Read": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}, "Write": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}, "Debug": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}, "Resolve": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}}
 	if got, want := acl.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
-		t.Errorf("got %#v, exected %#v ", got, want)
+		t.Errorf("got %#v, expected %#v ", got, want)
 	}
 
-	vlog.VI(2).Infof("Validate the ACL file on bini/shared.")
-	binary = repository.BinaryClient("bini/shared")
+	vlog.VI(2).Infof("Validate the ACL file on bini/private.")
+	binary = repository.BinaryClient("bini/private")
 	acl, etag, err := binary.GetACL(selfCtx)
 	if err != nil {
 		t.Fatalf("GetACL failed: %v", err)
 	}
 	if got, want := acl.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
-		t.Errorf("got %#v, exected %#v ", got, want)
+		t.Errorf("got %#v, expected %#v ", got, want)
 	}
 
-	vlog.VI(2).Infof("Self modifies the ACL file on bini/shared.")
+	vlog.VI(2).Infof("self blesses other as self/other and locks the bini/private binary to itself.")
+	selfBlessing := selfPrincipal.BlessingStore().Default()
+	otherBlessing, err := selfPrincipal.Bless(otherPrincipal.PublicKey(), selfBlessing, "other", security.UnconstrainedUse())
+	if err != nil {
+		t.Fatalf("selfPrincipal.Bless() failed: %v", err)
+	}
+	if _, err := otherPrincipal.BlessingStore().Set(otherBlessing, security.AllPrincipals); err != nil {
+		t.Fatalf("otherPrincipal.BlessingStore() failed: %v", err)
+	}
+
+	vlog.VI(2).Infof("Self modifies the ACL file on bini/private.")
 	for _, tag := range access.AllTypicalTags() {
-		acl.Add("self/other", string(tag))
+		acl.Clear("self", string(tag))
+		acl.Add("self/$", string(tag))
 	}
 	if err := binary.SetACL(selfCtx, acl, etag); err != nil {
 		t.Fatalf("SetACL failed: %v", err)
 	}
 
-	vlog.VI(2).Infof(" Verify that bini/shared's acls are updated.")
-	binary = repository.BinaryClient("bini/shared")
-	updated := access.TaggedACLMap{"Admin": access.ACL{In: []security.BlessingPattern{"self", "self/other"}, NotIn: []string{}}, "Read": access.ACL{In: []security.BlessingPattern{"self", "self/other"}, NotIn: []string{}}, "Write": access.ACL{In: []security.BlessingPattern{"self", "self/other"}, NotIn: []string{}}, "Debug": access.ACL{In: []security.BlessingPattern{"self", "self/other"}, NotIn: []string{}}, "Resolve": access.ACL{In: []security.BlessingPattern{"self", "self/other"}, NotIn: []string{}}}
+	vlog.VI(2).Infof(" Verify that bini/private's acls are updated.")
+	binary = repository.BinaryClient("bini/private")
+	updated := access.TaggedACLMap{"Admin": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}, "Read": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}, "Write": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}, "Debug": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}, "Resolve": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}}
 	acl, _, err = binary.GetACL(selfCtx)
 	if err != nil {
 		t.Fatalf("GetACL failed: %v", err)
 	}
 	if got, want := acl.Normalize(), updated.Normalize(); !reflect.DeepEqual(got, want) {
-		t.Errorf("got %#v, exected %#v ", got, want)
+		t.Errorf("got %#v, expected %#v ", got, want)
 	}
 
 	// Other still can't access bini/shared because there's no ACL file at the
@@ -198,7 +200,7 @@
 	binary = repository.BinaryClient("bini")
 	newRootACL := make(access.TaggedACLMap)
 	for _, tag := range access.AllTypicalTags() {
-		newRootACL.Add("self", string(tag))
+		newRootACL.Add("self/$", string(tag))
 	}
 	if err := binary.SetACL(selfCtx, newRootACL, ""); err != nil {
 		t.Fatalf("SetACL failed: %v", err)
@@ -220,7 +222,7 @@
 	if err != nil {
 		t.Fatalf("GetACL() failed: %v", err)
 	}
-	acl.Add("self/other", string("Write"))
+	acl.Add("self", string("Write"))
 	err = binary.SetACL(selfCtx, acl, tag)
 	if err != nil {
 		t.Fatalf("SetACL() failed: %v", err)
@@ -256,7 +258,7 @@
 		t.Fatalf("GetACL failed: %v", err)
 	}
 	if got, want := acl.Normalize(), updated.Normalize(); !reflect.DeepEqual(want, got) {
-		t.Errorf("got %#v, exected %#v ", got, want)
+		t.Errorf("got %#v, expected %#v ", got, want)
 	}
 
 	vlog.VI(2).Infof("Other tries to exclude self by adding self to Read's notin")
diff --git a/services/mgmt/device/deviced/commands.go b/services/mgmt/device/deviced/commands.go
index 90372cc..aeb3b5b 100644
--- a/services/mgmt/device/deviced/commands.go
+++ b/services/mgmt/device/deviced/commands.go
@@ -16,6 +16,7 @@
 	suidHelper  string
 	agent       string
 	initHelper  string
+	origin      string
 	singleUser  bool
 	sessionMode bool
 	initMode    bool
@@ -50,6 +51,7 @@
 	cmdInstall.Flags.StringVar(&suidHelper, "suid_helper", "", "path to suid helper")
 	cmdInstall.Flags.StringVar(&agent, "agent", "", "path to security agent")
 	cmdInstall.Flags.StringVar(&initHelper, "init_helper", "", "path to sysinit helper")
+	cmdInstall.Flags.StringVar(&origin, "origin", "", "if specified, self-updates will use this origin")
 	cmdInstall.Flags.BoolVar(&singleUser, "single_user", false, "if set, performs the installation assuming a single-user system")
 	cmdInstall.Flags.BoolVar(&sessionMode, "session_mode", false, "if set, installs the device manager to run a single session. Otherwise, the device manager is configured to get restarted upon exit")
 	cmdInstall.Flags.BoolVar(&initMode, "init_mode", false, "if set, installs the device manager with the system init service manager")
@@ -73,7 +75,7 @@
 	if initMode && initHelper == "" {
 		return cmd.UsageErrorf("--init_helper must be set")
 	}
-	if err := impl.SelfInstall(installationDir(), suidHelper, agent, initHelper, singleUser, sessionMode, initMode, args, os.Environ(), cmd.Stderr(), cmd.Stdout()); err != nil {
+	if err := impl.SelfInstall(installationDir(), suidHelper, agent, initHelper, origin, singleUser, sessionMode, initMode, args, os.Environ(), cmd.Stderr(), cmd.Stdout()); err != nil {
 		vlog.Errorf("SelfInstall failed: %v", err)
 		return err
 	}
diff --git a/services/mgmt/device/impl/app_service.go b/services/mgmt/device/impl/app_service.go
index 1538c98..2d40769 100644
--- a/services/mgmt/device/impl/app_service.go
+++ b/services/mgmt/device/impl/app_service.go
@@ -108,6 +108,7 @@
 // refine that later.
 
 import (
+	"bytes"
 	"crypto/md5"
 	"crypto/rand"
 	"encoding/base64"
@@ -124,7 +125,7 @@
 	"reflect"
 	"strconv"
 	"strings"
-	"sync"
+	"text/template"
 	"time"
 
 	"v.io/core/veyron2"
@@ -189,12 +190,6 @@
 type securityAgentState struct {
 	// Security agent key manager client.
 	keyMgrAgent *keymgr.Agent
-	// Ensures only one security agent connection socket is created
-	// at any time, preventing fork/exec from potentially passing
-	// down sockets meant for other children (as per ribrdb@, Go's
-	// exec implementation does not prune the set of files passed
-	// down to only include those specified in cmd.ExtraFiles).
-	startLock sync.Mutex
 }
 
 // appService implements the Device manager's Application interface.
@@ -472,6 +467,29 @@
 	return installationDir, nil
 }
 
+// agentPrincipal creates a Principal backed by the given agent connection,
+// taking ownership of the connection.  The returned cancel function is to be
+// called when the Principal is no longer in use.
+func agentPrincipal(ctx *context.T, conn *os.File) (security.Principal, func(), error) {
+	agentctx, cancel := context.WithCancel(ctx)
+	var err error
+	if agentctx, _, err = veyron2.SetNewStreamManager(agentctx); err != nil {
+		cancel()
+		conn.Close()
+		return nil, nil, err
+	}
+	p, err := agent.NewAgentPrincipal(agentctx, int(conn.Fd()), veyron2.GetClient(agentctx))
+	if err != nil {
+		cancel()
+		conn.Close()
+		return nil, nil, err
+	}
+	// conn will be closed when the connection to the agent is shut down, as
+	// a result of cancel shutting down the stream manager.  No need to
+	// explicitly call conn.Close() with cancel.
+	return p, cancel, nil
+}
+
 // setupPrincipal sets up the instance's principal, with the right blessings.
 func setupPrincipal(ctx *context.T, instanceDir, versionDir string, call ipc.ServerContext, securityAgent *securityAgentState, info *instanceInfo) error {
 	var p security.Principal
@@ -483,14 +501,16 @@
 			vlog.Errorf("NewPrincipal() failed %v", err)
 			return verror2.Make(ErrOperationFailed, nil)
 		}
-		defer conn.Close()
-
-		// TODO(caprita): release the socket created by NewAgentPrincipal.
-		if p, err = agent.NewAgentPrincipal(ctx, int(conn.Fd()), veyron2.GetClient(ctx)); err != nil {
-			vlog.Errorf("NewAgentPrincipal() failed: %v", err)
+		var cancel func()
+		if p, cancel, err = agentPrincipal(ctx, conn); err != nil {
+			vlog.Errorf("agentPrincipal failed: %v", err)
 			return verror2.Make(ErrOperationFailed, nil)
 		}
+		defer cancel()
 		info.SecurityAgentHandle = handle
+		// conn will be closed when the connection to the agent is shut
+		// down, as a result of cancel() shutting down the stream
+		// manager.  No need to call conn.Close().
 	} else {
 		credentialsDir := filepath.Join(instanceDir, "credentials")
 		// TODO(caprita): The app's system user id needs access to this dir.
@@ -771,24 +791,16 @@
 	cfg.Set(mgmt.ParentBlessingConfigKey, info.DeviceManagerPeerPattern)
 
 	// Set up any agent-specific state.
-	// NOTE(caprita): This ought to belong in genCmd, but we do it here
-	// to avoid holding on to the lock for too long.
-	//
-	// TODO(caprita): We need to take care to grab/release the lock
-	// excluding concurrent start operations.  See if we can make this more
-	// robust.
+	// NOTE(caprita): This ought to belong in genCmd.
 	var agentCleaner func()
 	if sa := i.securityAgent; sa != nil {
-		sa.startLock.Lock()
 		file, err := sa.keyMgrAgent.NewConnection(info.SecurityAgentHandle)
 		if err != nil {
-			sa.startLock.Unlock()
 			vlog.Errorf("NewConnection(%v) failed: %v", info.SecurityAgentHandle, err)
 			return err
 		}
 		agentCleaner = func() {
 			file.Close()
-			sa.startLock.Unlock()
 		}
 		// We need to account for the file descriptors corresponding to
 		// std{err|out|in} as well as the implementation-specific pipes
@@ -1254,3 +1266,142 @@
 	}
 	return i.locks.GetPathACL(ctx.LocalPrincipal(), path.Join(dir, "acls"))
 }
+
+func (i *appService) Debug(ctx ipc.ServerContext) (string, error) {
+	switch len(i.suffix) {
+	case 2:
+		return i.installationDebug(ctx)
+	case 3:
+		return i.instanceDebug(ctx)
+	default:
+		return "", verror2.Make(ErrInvalidSuffix, nil)
+	}
+}
+
+func (i *appService) installationDebug(ctx ipc.ServerContext) (string, error) {
+	const installationDebug = `Installation dir: {{.InstallationDir}}
+
+Origin: {{.Origin}}
+
+Envelope: {{printf "%+v" .Envelope}}
+
+Config: {{printf "%+v" .Config}}
+`
+	installationDebugTemplate, err := template.New("installation-debug").Parse(installationDebug)
+	if err != nil {
+		return "", err
+	}
+
+	installationDir, err := i.installationDir()
+	if err != nil {
+		return "", err
+	}
+	debugInfo := struct {
+		InstallationDir, Origin string
+		Envelope                *application.Envelope
+		Config                  device.Config
+	}{}
+	debugInfo.InstallationDir = installationDir
+
+	if origin, err := loadOrigin(installationDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.Origin = origin
+	}
+
+	currLink := filepath.Join(installationDir, "current")
+	if envelope, err := loadEnvelope(currLink); err != nil {
+		return "", err
+	} else {
+		debugInfo.Envelope = envelope
+	}
+
+	if config, err := loadConfig(installationDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.Config = config
+	}
+
+	var buf bytes.Buffer
+	if err := installationDebugTemplate.Execute(&buf, debugInfo); err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+
+}
+
+func (i *appService) instanceDebug(ctx ipc.ServerContext) (string, error) {
+	const instanceDebug = `Instance dir: {{.InstanceDir}}
+
+System name / start system name: {{.SystemName}} / {{.StartSystemName}}
+
+Cmd: {{printf "%+v" .Cmd}}
+
+Info: {{printf "%+v" .Info}}
+
+Principal: {{.PrincipalType}}
+Public Key: {{.Principal.PublicKey}}
+Blessing Store: {{.Principal.BlessingStore.DebugString}}
+Roots: {{.Principal.Roots.DebugString}}
+`
+	instanceDebugTemplate, err := template.New("instance-debug").Parse(instanceDebug)
+	if err != nil {
+		return "", err
+	}
+
+	instanceDir, err := i.instanceDir()
+	if err != nil {
+		return "", err
+	}
+	debugInfo := struct {
+		InstanceDir, SystemName, StartSystemName string
+		Cmd                                      *exec.Cmd
+		Info                                     *instanceInfo
+		Principal                                security.Principal
+		PrincipalType                            string
+	}{}
+	debugInfo.InstanceDir = instanceDir
+
+	debugInfo.SystemName = suidHelper.usernameForPrincipal(ctx, i.uat)
+	if startSystemName, err := readSystemNameForInstance(instanceDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.StartSystemName = startSystemName
+	}
+	if cmd, err := genCmd(instanceDir, i.config.Helper, debugInfo.SystemName, veyron2.GetNamespace(ctx.Context()).Roots()); err != nil {
+		return "", err
+	} else {
+		debugInfo.Cmd = cmd
+	}
+	if info, err := loadInstanceInfo(instanceDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.Info = info
+	}
+
+	if sa := i.securityAgent; sa != nil {
+		file, err := sa.keyMgrAgent.NewConnection(debugInfo.Info.SecurityAgentHandle)
+		if err != nil {
+			vlog.Errorf("NewConnection(%v) failed: %v", debugInfo.Info.SecurityAgentHandle, err)
+			return "", err
+		}
+		var cancel func()
+		if debugInfo.Principal, cancel, err = agentPrincipal(ctx.Context(), file); err != nil {
+			return "", err
+		}
+		defer cancel()
+		debugInfo.PrincipalType = "Agent-based"
+	} else {
+		credentialsDir := filepath.Join(instanceDir, "credentials")
+		var err error
+		if debugInfo.Principal, err = vsecurity.LoadPersistentPrincipal(credentialsDir, nil); err != nil {
+			return "", err
+		}
+		debugInfo.PrincipalType = fmt.Sprintf("Credentials dir-based (%v)", credentialsDir)
+	}
+	var buf bytes.Buffer
+	if err := instanceDebugTemplate.Execute(&buf, debugInfo); err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
diff --git a/services/mgmt/device/impl/device_installer.go b/services/mgmt/device/impl/device_installer.go
index 1057bfe..ee59788 100644
--- a/services/mgmt/device/impl/device_installer.go
+++ b/services/mgmt/device/impl/device_installer.go
@@ -39,6 +39,7 @@
 	"strings"
 
 	"v.io/core/veyron2/context"
+	"v.io/core/veyron2/naming"
 	"v.io/core/veyron2/services/mgmt/application"
 	"v.io/core/veyron2/services/mgmt/device"
 
@@ -119,7 +120,7 @@
 
 // SelfInstall installs the device manager and configures it using the
 // environment and the supplied command-line flags.
-func SelfInstall(installDir, suidHelper, agent, initHelper string, singleUser, sessionMode, init bool, args, env []string, stderr, stdout io.Writer) error {
+func SelfInstall(installDir, suidHelper, agent, initHelper, origin string, singleUser, sessionMode, init bool, args, env []string, stderr, stdout io.Writer) error {
 	root := filepath.Join(installDir, dmRoot)
 	if _, err := os.Stat(root); err == nil || !os.IsNotExist(err) {
 		return fmt.Errorf("%v already exists", root)
@@ -133,6 +134,7 @@
 	configState := &config.State{
 		Name:        "dummy", // So that Validate passes.
 		Root:        root,
+		Origin:      origin,
 		CurrentLink: currLink,
 		Helper:      suidHelper,
 	}
@@ -141,7 +143,7 @@
 	}
 	var extraArgs []string
 	if name, err := os.Hostname(); err == nil {
-		extraArgs = append(extraArgs, fmt.Sprintf("--name=%q", name))
+		extraArgs = append(extraArgs, fmt.Sprintf("--name=%q", naming.Join("devices", name)))
 	}
 	if !sessionMode {
 		extraArgs = append(extraArgs, fmt.Sprintf("--restart_exit_code=%d", restartExitCode))
diff --git a/services/mgmt/device/impl/device_service.go b/services/mgmt/device/impl/device_service.go
index d53a10d..d570d3e 100644
--- a/services/mgmt/device/impl/device_service.go
+++ b/services/mgmt/device/impl/device_service.go
@@ -352,6 +352,7 @@
 		return err
 	}
 	if envelope.Title != application.DeviceManagerTitle {
+		vlog.Errorf("app title mismatch. Got %q, expected %q.", envelope.Title, application.DeviceManagerTitle)
 		return verror2.Make(ErrAppTitleMismatch, ctx)
 	}
 	if s.config.Envelope != nil && reflect.DeepEqual(envelope, s.config.Envelope) {
@@ -399,9 +400,11 @@
 		return err
 	}
 
-	if err := s.testDeviceManager(ctx, workspace, envelope); err != nil {
-		return err
-	}
+	// TODO(rthellend): testDeviceManager always fails due to https://github.com/veyron/release-issues/issues/714
+	// Uncomment when the bug is fixed.
+	//if err := s.testDeviceManager(ctx, workspace, envelope); err != nil {
+	//	return err
+	//}
 
 	if err := updateLink(filepath.Join(workspace, "deviced.sh"), s.config.CurrentLink); err != nil {
 		return err
@@ -535,3 +538,7 @@
 	}
 	return s.uat.AllBlessingSystemAssociations()
 }
+
+func (*deviceService) Debug(ipc.ServerContext) (string, error) {
+	return "Not implemented", nil
+}
diff --git a/services/mgmt/device/impl/dispatcher.go b/services/mgmt/device/impl/dispatcher.go
index 30cf651..1ad57b6 100644
--- a/services/mgmt/device/impl/dispatcher.go
+++ b/services/mgmt/device/impl/dispatcher.go
@@ -152,7 +152,9 @@
 	acl := make(access.TaggedACLMap)
 	for _, n := range names {
 		for _, tag := range access.AllTypicalTags() {
-			acl.Add(security.BlessingPattern(n), string(tag))
+			// TODO(caprita, ataly, ashankar): Do we really need the NonExtendable restriction
+			// below?
+			acl.Add(security.BlessingPattern(n).MakeNonExtendable(), string(tag))
 		}
 	}
 	if err := d.locks.SetPathACL(principal, d.getACLDir(), acl, ""); err != nil {
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index 4a6fee9..7ec36ed 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -593,6 +593,19 @@
 	// Install the app.  The config-specified flag value for testFlagName
 	// should override the value specified in the envelope above.
 	appID := installApp(t, ctx, device.Config{testFlagName: "flag-val-install"})
+	installationDebug := debug(t, ctx, appID)
+	// We spot-check a couple pieces of information we expect in the debug
+	// output.
+	// TODO(caprita): Is there a way to verify more without adding brittle
+	// logic that assumes too much about the format?  This may be one
+	// argument in favor of making the output of Debug a struct instead of
+	// free-form string.
+	if !strings.Contains(installationDebug, "Origin: ar") {
+		t.Fatalf("debug response doesn't contain expected info: %v", installationDebug)
+	}
+	if !strings.Contains(installationDebug, "Config: map[random_test_flag:flag-val-install]") {
+		t.Fatalf("debug response doesn't contain expected info: %v", installationDebug)
+	}
 
 	// Start requires the caller to grant a blessing for the app instance.
 	if _, err := startAppImpl(t, ctx, appID, ""); err == nil || !verror.Is(err, impl.ErrInvalidBlessing.ID) {
@@ -602,6 +615,11 @@
 	// Start an instance of the app.
 	instance1ID := startApp(t, ctx, appID)
 
+	instanceDebug := debug(t, ctx, appID, instance1ID)
+	if !strings.Contains(instanceDebug, "Blessing Store: Default blessings: test-principal/forapp/google naps") {
+		t.Fatalf("debug response doesn't contain expected info: %v", instanceDebug)
+	}
+
 	// Wait until the app pings us that it's ready.
 	verifyPingArgs(t, pingCh, userName(t), "flag-val-install", "env-val-envelope")
 
@@ -917,7 +935,7 @@
 	}
 	expectedACL := make(access.TaggedACLMap)
 	for _, tag := range access.AllTypicalTags() {
-		expectedACL[string(tag)] = access.ACL{In: []security.BlessingPattern{"root/self/mydevice"}}
+		expectedACL[string(tag)] = access.ACL{In: []security.BlessingPattern{"root/self/mydevice/$"}}
 	}
 	var b bytes.Buffer
 	if err := expectedACL.WriteTo(&b); err != nil {
@@ -993,7 +1011,7 @@
 	dmDir := filepath.Join(testDir, "dm")
 	// TODO(caprita): Add test logic when initMode = true.
 	singleUser, sessionMode, initMode := true, true, false
-	if err := impl.SelfInstall(dmDir, suidHelperPath, agentPath, initHelperPath, singleUser, sessionMode, initMode, dmargs[1:], dmenv, os.Stderr, os.Stdout); err != nil {
+	if err := impl.SelfInstall(dmDir, suidHelperPath, agentPath, initHelperPath, "", singleUser, sessionMode, initMode, dmargs[1:], dmenv, os.Stderr, os.Stdout); err != nil {
 		t.Fatalf("SelfInstall failed: %v", err)
 	}
 
diff --git a/services/mgmt/device/impl/util_test.go b/services/mgmt/device/impl/util_test.go
index 0ac22f6..732ca47 100644
--- a/services/mgmt/device/impl/util_test.go
+++ b/services/mgmt/device/impl/util_test.go
@@ -241,6 +241,14 @@
 	}
 }
 
+func debug(t *testing.T, ctx *context.T, nameComponents ...string) string {
+	dbg, err := appStub(nameComponents...).Debug(ctx)
+	if err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Debug(%v) failed: %v", nameComponents, err))
+	}
+	return dbg
+}
+
 // Code to make Association lists sortable.
 type byIdentity []device.Association
 
diff --git a/services/mgmt/suidhelper/impl/system.go b/services/mgmt/suidhelper/impl/system.go
index 8a93d91..c634b59 100644
--- a/services/mgmt/suidhelper/impl/system.go
+++ b/services/mgmt/suidhelper/impl/system.go
@@ -43,11 +43,12 @@
 	}
 	attr.Env = hw.envv
 
+	attr.Sys = new(syscall.SysProcAttr)
+	attr.Sys.Setsid = true
 	if hw.dryrun {
 		log.Printf("[dryrun] syscall.Setgid(%d)", hw.gid)
 		log.Printf("[dryrun] syscall.Setuid(%d)", hw.uid)
 	} else {
-		attr.Sys = new(syscall.SysProcAttr)
 		attr.Sys.Credential = new(syscall.Credential)
 		attr.Sys.Credential.Gid = uint32(hw.gid)
 		attr.Sys.Credential.Uid = uint32(hw.uid)
diff --git a/tools/mgmt/device/impl/devicemanager_mock_test.go b/tools/mgmt/device/impl/devicemanager_mock_test.go
index 3777043..62cf94a 100644
--- a/tools/mgmt/device/impl/devicemanager_mock_test.go
+++ b/tools/mgmt/device/impl/devicemanager_mock_test.go
@@ -68,9 +68,11 @@
 func (*mockDeviceInvoker) Describe(ipc.ServerContext) (device.Description, error) {
 	return device.Description{}, nil
 }
+
 func (*mockDeviceInvoker) IsRunnable(_ ipc.ServerContext, description binary.Description) (bool, error) {
 	return false, nil
 }
+
 func (*mockDeviceInvoker) Reset(call ipc.ServerContext, deadline uint64) error { return nil }
 
 // Mock Install
@@ -92,11 +94,13 @@
 }
 
 func (*mockDeviceInvoker) Refresh(ipc.ServerContext) error { return nil }
+
 func (*mockDeviceInvoker) Restart(ipc.ServerContext) error { return nil }
 
 func (mni *mockDeviceInvoker) Resume(_ ipc.ServerContext) error {
 	return mni.simpleCore("Resume", "Resume")
 }
+
 func (i *mockDeviceInvoker) Revert(call ipc.ServerContext) error { return nil }
 
 type StartResponse struct {
@@ -122,8 +126,11 @@
 func (mni *mockDeviceInvoker) Suspend(_ ipc.ServerContext) error {
 	return mni.simpleCore("Suspend", "Suspend")
 }
-func (*mockDeviceInvoker) Uninstall(ipc.ServerContext) error        { return nil }
-func (i *mockDeviceInvoker) Update(ipc.ServerContext) error         { return nil }
+
+func (*mockDeviceInvoker) Uninstall(ipc.ServerContext) error { return nil }
+
+func (i *mockDeviceInvoker) Update(ipc.ServerContext) error { return nil }
+
 func (*mockDeviceInvoker) UpdateTo(ipc.ServerContext, string) error { return nil }
 
 // Mock ACL getting and setting
@@ -149,6 +156,12 @@
 	return r.acl, r.etag, r.err
 }
 
+func (mni *mockDeviceInvoker) Debug(ipc.ServerContext) (string, error) {
+	ir := mni.tape.Record("Debug")
+	r := ir.(string)
+	return r, nil
+}
+
 type dispatcher struct {
 	tape *Tape
 	t    *testing.T
diff --git a/tools/mgmt/device/impl/impl.go b/tools/mgmt/device/impl/impl.go
index 3a60782..8762b8a 100644
--- a/tools/mgmt/device/impl/impl.go
+++ b/tools/mgmt/device/impl/impl.go
@@ -94,7 +94,7 @@
 	Long:     "Claim the device.",
 	ArgsName: "<device> <grant extension>",
 	ArgsLong: `
-<device> is the veyron object name of the device manager's app service.
+<device> is the veyron object name of the device manager's device service.
 
 <grant extension> is used to extend the default blessing of the
 current principal when blessing the app instance.`,
@@ -120,7 +120,7 @@
 	Long:     "Describe the device.",
 	ArgsName: "<device>",
 	ArgsLong: `
-<device> is the veyron object name of the device manager's app service.`,
+<device> is the veyron object name of the device manager's device service.`,
 }
 
 func runDescribe(cmd *cmdline.Command, args []string) error {
@@ -135,3 +135,72 @@
 	}
 	return nil
 }
+
+var cmdUpdate = &cmdline.Command{
+	Run:      runUpdate,
+	Name:     "update",
+	Short:    "Update the device manager or application",
+	Long:     "Update the device manager or application",
+	ArgsName: "<object>",
+	ArgsLong: `
+<object> is the veyron object name of the device manager or application
+installation to update.`,
+}
+
+func runUpdate(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.UsageErrorf("update: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	deviceName := args[0]
+	if err := device.ApplicationClient(deviceName).Update(gctx); err != nil {
+		return err
+	}
+	fmt.Fprintln(cmd.Stdout(), "Update successful.")
+	return nil
+}
+
+var cmdRevert = &cmdline.Command{
+	Run:      runRevert,
+	Name:     "revert",
+	Short:    "Revert the device manager or application",
+	Long:     "Revert the device manager or application to its previous version",
+	ArgsName: "<object>",
+	ArgsLong: `
+<object> is the veyron object name of the device manager or application
+installation to revert.`,
+}
+
+func runRevert(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.UsageErrorf("revert: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	deviceName := args[0]
+	if err := device.ApplicationClient(deviceName).Revert(gctx); err != nil {
+		return err
+	}
+	fmt.Fprintln(cmd.Stdout(), "Revert successful.")
+	return nil
+}
+
+var cmdDebug = &cmdline.Command{
+	Run:      runDebug,
+	Name:     "debug",
+	Short:    "Debug the device.",
+	Long:     "Debug the device.",
+	ArgsName: "<device>",
+	ArgsLong: `
+<device> is the veyron object name of an app installation or instance.`,
+}
+
+func runDebug(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.UsageErrorf("debug: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	deviceName := args[0]
+	if description, err := device.DeviceClient(deviceName).Debug(gctx); err != nil {
+		return fmt.Errorf("Debug failed: %v", err)
+	} else {
+		fmt.Fprintf(cmd.Stdout(), "%v\n", description)
+	}
+	return nil
+}
diff --git a/tools/mgmt/device/impl/impl_test.go b/tools/mgmt/device/impl/impl_test.go
index dc53e15..b841ee6 100644
--- a/tools/mgmt/device/impl/impl_test.go
+++ b/tools/mgmt/device/impl/impl_test.go
@@ -418,3 +418,31 @@
 	stdout.Reset()
 	stderr.Reset()
 }
+
+func TestDebugCommand(t *testing.T) {
+	shutdown := initTest()
+	defer shutdown()
+	tape := NewTape()
+	server, endpoint, err := startServer(t, gctx, tape)
+	if err != nil {
+		return
+	}
+	defer stopServer(t, server)
+	// Setup the command-line.
+	cmd := impl.Root()
+	var stdout, stderr bytes.Buffer
+	cmd.Init(nil, &stdout, &stderr)
+	appName := naming.JoinAddressName(endpoint.String(), "")
+
+	debugMessage := "the secrets of the universe, revealed"
+	tape.SetResponses([]interface{}{debugMessage})
+	if err := cmd.Execute([]string{"debug", appName}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := debugMessage, strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from debug. Got %q, expected %q", got, expected)
+	}
+	if got, expected := tape.Play(), []interface{}{"Debug"}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+}
diff --git a/tools/mgmt/device/impl/root.go b/tools/mgmt/device/impl/root.go
index 9228247..db04609 100644
--- a/tools/mgmt/device/impl/root.go
+++ b/tools/mgmt/device/impl/root.go
@@ -19,6 +19,6 @@
 		Long: `
 The device tool facilitates interaction with the veyron device manager.
 `,
-		Children: []*cmdline.Command{cmdInstall, cmdStart, associateRoot(), cmdDescribe, cmdClaim, cmdStop, cmdSuspend, cmdResume, aclRoot()},
+		Children: []*cmdline.Command{cmdInstall, cmdStart, associateRoot(), cmdDescribe, cmdClaim, cmdStop, cmdSuspend, cmdResume, cmdRevert, cmdUpdate, cmdDebug, aclRoot()},
 	}
 }
diff --git a/tools/mgmt/test.sh b/tools/mgmt/test.sh
index 0628c47..6441eb7 100755
--- a/tools/mgmt/test.sh
+++ b/tools/mgmt/test.sh
@@ -129,7 +129,7 @@
   fi
 
   "${VRUN}" "${DEVICE_SCRIPT}" start
-  local -r DM_NAME=$(hostname)
+  local -r DM_NAME=devices/$(hostname)
   DM_EP=$(wait_for_mountentry "${NAMESPACE_BIN}" 5 "${DM_NAME}")
 
   # Verify that device manager is published under the expected name (hostname).
diff --git a/tools/servicerunner/main.go b/tools/servicerunner/main.go
index 225e19a..3e0b345 100644
--- a/tools/servicerunner/main.go
+++ b/tools/servicerunner/main.go
@@ -33,7 +33,7 @@
 	}
 	numLeft := len(varsToAdd)
 
-	s := expect.NewSession(nil, h.Stdout(), 10*time.Second)
+	s := expect.NewSession(nil, h.Stdout(), 30*time.Second)
 	for {
 		l := s.ReadLine()
 		if err := s.OriginalError(); err != nil {