Merge "veyron/lib/modules: Make modules parse flags using the veyron2.Init flag parsing mechanism."
diff --git a/services/mgmt/device/deviced/server.go b/services/mgmt/device/deviced/server.go
index 33d7919..7362b43 100644
--- a/services/mgmt/device/deviced/server.go
+++ b/services/mgmt/device/deviced/server.go
@@ -6,11 +6,13 @@
 
 	"v.io/lib/cmdline"
 
+	vexec "v.io/core/veyron/lib/exec"
 	"v.io/core/veyron/lib/signals"
 	_ "v.io/core/veyron/profiles/roaming"
 	"v.io/core/veyron/services/mgmt/device/config"
 	"v.io/core/veyron/services/mgmt/device/impl"
 	"v.io/core/veyron2"
+	"v.io/core/veyron2/mgmt"
 	"v.io/core/veyron2/vlog"
 )
 
@@ -25,6 +27,17 @@
 	ctx, shutdown := veyron2.Init()
 	defer shutdown()
 
+	var testMode bool
+	// If this device manager was started by another device manager, it must
+	// be part of a self update to test that this binary works. In that
+	// case, we need to disable a lot of functionality.
+	if handle, err := vexec.GetChildHandle(); err == nil {
+		if _, err := handle.Config.Get(mgmt.ParentNameConfigKey); err == nil {
+			testMode = true
+			vlog.Infof("TEST MODE")
+		}
+	}
+
 	server, err := veyron2.NewServer(ctx)
 	if err != nil {
 		vlog.Errorf("NewServer() failed: %v", err)
@@ -50,16 +63,20 @@
 	// implementation detail).
 
 	var exitErr error
-	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, func() { exitErr = cmdline.ErrExitCode(*restartExitCode) })
+	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, testMode, func() { exitErr = cmdline.ErrExitCode(*restartExitCode) })
 	if err != nil {
 		vlog.Errorf("Failed to create dispatcher: %v", err)
 		return err
 	}
-	if err := server.ServeDispatcher(*publishAs, dispatcher); err != nil {
-		vlog.Errorf("Serve(%v) failed: %v", *publishAs, err)
+	var publishName string
+	if testMode == false {
+		publishName = *publishAs
+	}
+	if err := server.ServeDispatcher(publishName, dispatcher); err != nil {
+		vlog.Errorf("Serve(%v) failed: %v", publishName, err)
 		return err
 	}
-	vlog.VI(0).Infof("Device manager published as: %v", *publishAs)
+	vlog.VI(0).Infof("Device manager published as: %v", publishName)
 	impl.InvokeCallback(ctx, name)
 
 	// Wait until shutdown.  Ignore duplicate signals (sent by agent and
diff --git a/services/mgmt/device/impl/device_service.go b/services/mgmt/device/impl/device_service.go
index 629a283..0ff8ead 100644
--- a/services/mgmt/device/impl/device_service.go
+++ b/services/mgmt/device/impl/device_service.go
@@ -43,15 +43,16 @@
 	"os/exec"
 	"path/filepath"
 	"reflect"
+	"strconv"
 	"strings"
 	"sync"
-	"time"
 
 	"v.io/core/veyron2"
 	"v.io/core/veyron2/context"
 	"v.io/core/veyron2/ipc"
 	"v.io/core/veyron2/mgmt"
 	"v.io/core/veyron2/naming"
+	"v.io/core/veyron2/security"
 	"v.io/core/veyron2/services/mgmt/application"
 	"v.io/core/veyron2/services/mgmt/binary"
 	"v.io/core/veyron2/services/mgmt/device"
@@ -60,7 +61,9 @@
 	"v.io/core/veyron2/vlog"
 
 	vexec "v.io/core/veyron/lib/exec"
+	"v.io/core/veyron/lib/flags/consts"
 	"v.io/core/veyron/lib/netstate"
+	vsecurity "v.io/core/veyron/security"
 	"v.io/core/veyron/services/mgmt/device/config"
 	"v.io/core/veyron/services/mgmt/profile"
 )
@@ -102,6 +105,7 @@
 	config         *config.State
 	disp           *dispatcher
 	uat            BlessingSystemAssociationStore
+	securityAgent  *securityAgentState
 }
 
 // managerInfo holds state about a running device manager.
@@ -216,6 +220,7 @@
 
 func (s *deviceService) revertDeviceManager(ctx *context.T) error {
 	if err := updateLink(s.config.Previous, s.config.CurrentLink); err != nil {
+		vlog.Errorf("updateLink failed: %v", err)
 		return err
 	}
 	if s.restartHandler != nil {
@@ -278,6 +283,61 @@
 	cfg.Set(mgmt.ParentNameConfigKey, listener.name())
 	cfg.Set(mgmt.ProtocolConfigKey, "tcp")
 	cfg.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
+
+	var p security.Principal
+	var agentHandle []byte
+	if s.securityAgent != nil {
+		// TODO(rthellend): Cleanup principal
+		handle, conn, err := s.securityAgent.keyMgrAgent.NewPrincipal(ctx, false)
+		if err != nil {
+			vlog.Errorf("NewPrincipal() failed %v", err)
+			return verror2.Make(ErrOperationFailed, nil)
+		}
+		agentHandle = handle
+		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()
+
+	} else {
+		credentialsDir := filepath.Join(workspace, "credentials")
+		var err error
+		if p, err = vsecurity.CreatePersistentPrincipal(credentialsDir, nil); err != nil {
+			vlog.Errorf("CreatePersistentPrincipal(%v, nil) failed: %v", credentialsDir, err)
+			return verror2.Make(ErrOperationFailed, nil)
+		}
+		cmd.Env = append(cmd.Env, consts.VeyronCredentials+"="+credentialsDir)
+	}
+	dmPrincipal := veyron2.GetPrincipal(ctx)
+	dmBlessings, err := dmPrincipal.Bless(p.PublicKey(), dmPrincipal.BlessingStore().Default(), "testdm", security.UnconstrainedUse())
+	if err := p.BlessingStore().SetDefault(dmBlessings); err != nil {
+		vlog.Errorf("BlessingStore.SetDefault() failed: %v", err)
+		return verror2.Make(ErrOperationFailed, nil)
+	}
+	if _, err := p.BlessingStore().Set(dmBlessings, security.AllPrincipals); err != nil {
+		vlog.Errorf("BlessingStore.Set() failed: %v", err)
+		return verror2.Make(ErrOperationFailed, nil)
+	}
+	if err := p.AddToRoots(dmBlessings); err != nil {
+		vlog.Errorf("AddToRoots() failed: %v", err)
+		return verror2.Make(ErrOperationFailed, nil)
+	}
+
+	if s.securityAgent != nil {
+		file, err := s.securityAgent.keyMgrAgent.NewConnection(agentHandle)
+		if err != nil {
+			vlog.Errorf("NewConnection(%v) failed: %v", agentHandle, err)
+			return err
+		}
+		defer file.Close()
+
+		fd := len(cmd.ExtraFiles) + vexec.FileOffset
+		cmd.ExtraFiles = append(cmd.ExtraFiles, file)
+		cfg.Set(mgmt.SecurityAgentFDConfigKey, strconv.Itoa(fd))
+	}
+
 	handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
 	// Start the child process.
 	if err := handle.Start(); err != nil {
@@ -296,35 +356,14 @@
 	}
 	childName, err := listener.waitForValue(childReadyTimeout)
 	if err != nil {
+		vlog.Errorf("waitForValue(%v) failed: %v", childReadyTimeout, err)
 		return verror2.Make(ErrOperationFailed, ctx)
 	}
-	// Check that invoking Revert() succeeds.
+	// Check that invoking Stop() succeeds.
 	childName = naming.Join(childName, "device")
 	dmClient := device.DeviceClient(childName)
-	linkOld, pathOld, err := s.getCurrentFileInfo()
-	if err != nil {
-		return verror2.Make(ErrOperationFailed, ctx)
-	}
-	// Since the resolution of mtime for files is seconds, the test sleeps
-	// for a second to make sure it can check whether the current symlink is
-	// updated.
-	time.Sleep(time.Second)
-	if err := dmClient.Revert(ctx); err != nil {
-		return verror2.Make(ErrOperationFailed, ctx)
-	}
-	linkNew, pathNew, err := s.getCurrentFileInfo()
-	if err != nil {
-		return verror2.Make(ErrOperationFailed, ctx)
-	}
-	// Check that the new device manager updated the current symbolic link.
-	if !linkOld.ModTime().Before(linkNew.ModTime()) {
-		vlog.Errorf("New device manager test failed")
-		return verror2.Make(ErrOperationFailed, ctx)
-	}
-	// Ensure that the current symbolic link points to the same script.
-	if pathNew != pathOld {
-		updateLink(pathOld, s.config.CurrentLink)
-		vlog.Errorf("New device manager test failed")
+	if err := dmClient.Stop(ctx, 0); err != nil {
+		vlog.Errorf("Stop() failed: %v", err)
 		return verror2.Make(ErrOperationFailed, ctx)
 	}
 	if err := handle.Wait(childWaitTimeout); err != nil {
@@ -432,11 +471,10 @@
 		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 := s.testDeviceManager(ctx, workspace, envelope); err != nil {
+		vlog.Errorf("testDeviceManager failed: %v", err)
+		return err
+	}
 
 	if err := updateLink(filepath.Join(workspace, "deviced.sh"), s.config.CurrentLink); err != nil {
 		return err
@@ -470,10 +508,12 @@
 
 func (s *deviceService) Revert(call ipc.ServerContext) error {
 	if s.config.Previous == "" {
+		vlog.Errorf("Revert failed: no previous version")
 		return verror2.Make(ErrUpdateNoOp, call.Context())
 	}
 	updatingState := s.updating
 	if updatingState.testAndSetUpdating() {
+		vlog.Errorf("Revert failed: already in progress")
 		return verror2.Make(ErrOperationInProgress, call.Context())
 	}
 	err := s.revertDeviceManager(call.Context())
diff --git a/services/mgmt/device/impl/dispatcher.go b/services/mgmt/device/impl/dispatcher.go
index 5632858..7dc317e 100644
--- a/services/mgmt/device/impl/dispatcher.go
+++ b/services/mgmt/device/impl/dispatcher.go
@@ -34,6 +34,7 @@
 	updating       *updatingState
 	securityAgent  *securityAgentState
 	restartHandler func()
+	testMode       bool
 }
 
 // dispatcher holds the state of the device manager dispatcher.
@@ -72,7 +73,7 @@
 )
 
 // NewDispatcher is the device manager dispatcher factory.
-func NewDispatcher(principal security.Principal, config *config.State, restartHandler func()) (*dispatcher, error) {
+func NewDispatcher(principal security.Principal, config *config.State, testMode bool, restartHandler func()) (ipc.Dispatcher, error) {
 	if err := config.Validate(); err != nil {
 		return nil, fmt.Errorf("invalid config %v: %v", config, err)
 	}
@@ -95,6 +96,7 @@
 			callback:       newCallbackState(config.Name),
 			updating:       newUpdatingState(),
 			restartHandler: restartHandler,
+			testMode:       testMode,
 		},
 		config:    config,
 		uat:       uat,
@@ -121,6 +123,9 @@
 			}
 		}
 	}
+	if testMode {
+		return &testModeDispatcher{d}, nil
+	}
 	return d, nil
 }
 
@@ -166,8 +171,17 @@
 
 // TODO(rjkroege): Consider refactoring authorizer implementations to
 // be shareable with other components.
-func newAuthorizer(principal security.Principal, dir string, locks *acls.Locks) (security.Authorizer, error) {
-	rootTam, _, err := locks.GetPathACL(principal, dir)
+func (d *dispatcher) newAuthorizer() (security.Authorizer, error) {
+	if d.internal.testMode {
+		// In test mode, the device manager will not be able to read
+		// the ACLs, because they were signed with the key of the real
+		// device manager. It's not a problem because the
+		// testModeDispatcher overrides the authorizer anyway.
+		return nil, nil
+	}
+
+	dir := d.getACLDir()
+	rootTam, _, err := d.locks.GetPathACL(d.principal, dir)
 
 	if err != nil && os.IsNotExist(err) {
 		vlog.VI(1).Infof("GetPathACL(%s) failed: %v", dir, err)
@@ -194,7 +208,8 @@
 			i--
 		}
 	}
-	auth, err := newAuthorizer(d.principal, d.getACLDir(), d.locks)
+
+	auth, err := d.newAuthorizer()
 	if err != nil {
 		return nil, nil, err
 	}
@@ -214,6 +229,7 @@
 			config:         d.config,
 			disp:           d,
 			uat:            d.uat,
+			securityAgent:  d.internal.securityAgent,
 		})
 		return receiver, auth, nil
 	case appsSuffix:
@@ -252,9 +268,6 @@
 				return invoker, auth, nil
 			}
 		}
-		if err != nil {
-			return nil, nil, err
-		}
 		receiver := device.ApplicationServer(&appService{
 			callback:      d.internal.callback,
 			config:        d.config,
@@ -289,6 +302,27 @@
 	}
 }
 
+// testModeDispatcher is a wrapper around the real dispatcher. It returns the
+// exact same object as the real dispatcher, but the authorizer only allows
+// calls to "device".Stop().
+type testModeDispatcher struct {
+	realDispatcher ipc.Dispatcher
+}
+
+func (d *testModeDispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
+	obj, _, err := d.realDispatcher.Lookup(suffix)
+	return obj, d, err
+}
+
+func (testModeDispatcher) Authorize(ctx security.Context) error {
+	if ctx.Suffix() == deviceSuffix && ctx.Method() == "Stop" {
+		vlog.Infof("testModeDispatcher.Authorize: Allow %q.%s()", ctx.Suffix(), ctx.Method())
+		return nil
+	}
+	vlog.Infof("testModeDispatcher.Authorize: Reject %q.%s()", ctx.Suffix(), ctx.Method())
+	return verror.Make(ErrInvalidSuffix, nil)
+}
+
 func newAppSpecificAuthorizer(sec security.Authorizer, config *config.State, suffix []string) (security.Authorizer, error) {
 	// TODO(rjkroege): This does not support <appname>.Start() to start all
 	// instances. Correct this.
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index 9a7e600..79957c6 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -164,11 +164,13 @@
 	// script prepared by a previous version of the device manager.
 	if len(args) > 0 {
 		if want, got := 4, len(args); want != got {
-			vlog.Fatalf("expected %d additional arguments, got %d instead", want, got)
+			vlog.Fatalf("expected %d additional arguments, got %d instead: %q", want, got, args)
 		}
 		configState.Root, configState.Helper, configState.Origin, configState.CurrentLink = args[0], args[1], args[2], args[3]
 	}
-	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, func() { fmt.Println("restart handler") })
+	blessings := fmt.Sprint(veyron2.GetPrincipal(ctx).BlessingStore().Default())
+	testMode := strings.HasSuffix(blessings, "/testdm")
+	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, testMode, func() { fmt.Println("restart handler") })
 	if err != nil {
 		vlog.Fatalf("Failed to create device manager dispatcher: %v", err)
 	}
@@ -184,7 +186,7 @@
 	if val, present := env["PAUSE_BEFORE_STOP"]; present && val == "1" {
 		modules.WaitForEOF(stdin)
 	}
-	if dispatcher.Leaking() {
+	if impl.DispatcherLeaking(dispatcher) {
 		vlog.Fatalf("device manager leaking resources")
 	}
 	return nil
@@ -366,9 +368,7 @@
 	// Set up a second version of the device manager. The information in the
 	// envelope will be used by the device manager to stage the next
 	// version.
-	crDir, crEnv := mgmttest.CredentialsForChild(ctx, "child")
-	defer os.RemoveAll(crDir)
-	*envelope = envelopeFromShell(sh, crEnv, deviceManagerCmd, application.DeviceManagerTitle, "v2DM")
+	*envelope = envelopeFromShell(sh, nil, deviceManagerCmd, application.DeviceManagerTitle, "v2DM")
 	updateDevice(t, ctx, "factoryDM")
 
 	// Current link should have been updated to point to v2.
@@ -410,9 +410,7 @@
 	}
 
 	// Create a third version of the device manager and issue an update.
-	crDir, crEnv = mgmttest.CredentialsForChild(ctx, "child")
-	defer os.RemoveAll(crDir)
-	*envelope = envelopeFromShell(sh, crEnv, deviceManagerCmd, application.DeviceManagerTitle, "v3DM")
+	*envelope = envelopeFromShell(sh, nil, deviceManagerCmd, application.DeviceManagerTitle, "v3DM")
 	updateDevice(t, ctx, "v2DM")
 
 	scriptPathV3 := evalLink()
diff --git a/services/mgmt/device/impl/only_for_test.go b/services/mgmt/device/impl/only_for_test.go
index 082fc25..6f92afc 100644
--- a/services/mgmt/device/impl/only_for_test.go
+++ b/services/mgmt/device/impl/only_for_test.go
@@ -2,9 +2,11 @@
 
 import (
 	"flag"
+	"fmt"
 	"os"
 	"path/filepath"
 
+	"v.io/core/veyron2/ipc"
 	"v.io/core/veyron2/services/mgmt/device"
 	"v.io/core/veyron2/vlog"
 )
@@ -20,8 +22,15 @@
 	return len(c.channels) > 0
 }
 
-func (d *dispatcher) Leaking() bool {
-	return d.internal.callback.leaking()
+func DispatcherLeaking(d ipc.Dispatcher) bool {
+	switch obj := d.(type) {
+	case *dispatcher:
+		return obj.internal.callback.leaking()
+	case *testModeDispatcher:
+		return obj.realDispatcher.(*dispatcher).internal.callback.leaking()
+	default:
+		panic(fmt.Sprintf("unexpected type: %T", d))
+	}
 }
 
 func init() {
diff --git a/services/mgmt/device/impl/util.go b/services/mgmt/device/impl/util.go
index 596ef71..54d90d2 100644
--- a/services/mgmt/device/impl/util.go
+++ b/services/mgmt/device/impl/util.go
@@ -29,7 +29,7 @@
 		vlog.Errorf("Download(%v) failed: %v", name, err)
 		return verror2.Make(ErrOperationFailed, nil)
 	}
-	path, perm := filepath.Join(workspace, fileName), os.FileMode(755)
+	path, perm := filepath.Join(workspace, fileName), os.FileMode(0755)
 	if err := ioutil.WriteFile(path, data, perm); err != nil {
 		vlog.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
 		return verror2.Make(ErrOperationFailed, nil)
diff --git a/tools/mgmt/test.sh b/tools/mgmt/test.sh
index 7a28463..7cf1f9e 100755
--- a/tools/mgmt/test.sh
+++ b/tools/mgmt/test.sh
@@ -116,14 +116,15 @@
   local -r APPLICATIOND_NAME="applicationd"
   local -r DEVICED_APP_NAME="${APPLICATIOND_NAME}/deviced/test"
 
-  BIN_STAGING_DIR=$(shell::tmp_dir)
+  BIN_STAGING_DIR="${WORKDIR}/bin"
+  mkdir -p "${BIN_STAGING_DIR}"
   cp "${AGENTD_BIN}" "${SUIDHELPER_BIN}" "${INITHELPER_BIN}" "${DEVICEMANAGER_BIN}" "${BIN_STAGING_DIR}"
   shell_test::setup_server_test
 
   # Install and start device manager.
-  DM_INSTALL_DIR=$(shell::tmp_dir)
+  DM_INSTALL_DIR="${WORKDIR}/dm"
 
-  export VANADIUM_DEVICE_DIR="${DM_INSTALL_DIR}/dm"
+  export VANADIUM_DEVICE_DIR="${DM_INSTALL_DIR}"
 
   if [[ "${WITH_SUID}" == "--with_suid" ]]; then
     "${DEVICE_SCRIPT}" install "${BIN_STAGING_DIR}" --origin="${DEVICED_APP_NAME}" -- --veyron.tcp.address=127.0.0.1:0
@@ -170,7 +171,7 @@
   # the device ("alice/myworkstation") can talk to it.
   local -r BINARYD_NAME="binaryd"
   shell_test::start_server "${VRUN}" --name=myworkstation/binaryd "${BINARYD_BIN}" --name="${BINARYD_NAME}" \
-    --root_dir="$(shell::tmp_dir)/binstore" --veyron.tcp.address=127.0.0.1:0 --http=127.0.0.1:0 \
+    --root_dir="${WORKDIR}/binstore" --veyron.tcp.address=127.0.0.1:0 --http=127.0.0.1:0 \
     || shell_test::fail "line ${LINENO} failed to start binaryd"
 
   # Upload a binary to the binary server.  The binary we upload is binaryd
@@ -185,8 +186,9 @@
 
   # Start an application server under the blessing "alice/myworkstation/applicationd" so that
   # the device ("alice/myworkstation") can talk to it.
+  mkdir -p "${WORKDIR}/appstore"
   shell_test::start_server "${VRUN}" --name=myworkstation/applicationd "${APPLICATIOND_BIN}" --name="${APPLICATIOND_NAME}" \
-    --store="$(shell::tmp_dir)" --veyron.tcp.address=127.0.0.1:0 \
+    --store="${WORKDIR}/appstore" --veyron.tcp.address=127.0.0.1:0 \
     || shell_test::fail "line ${LINENO} failed to start applicationd"
 
   # Upload an envelope for our test app.