services/device/mgmt_v23_test et al.: support for multiuser tests

Update the device manager's Go integration tests with support to run
the device manager in multiuser mode.

Change-Id: I0c81ae428fcf61d2414b1f84c7460d50d1bf9f59
diff --git a/cmd/principal/principal_v23_test.go b/cmd/principal/principal_v23_test.go
index 04f9fa3..ad0d319 100644
--- a/cmd/principal/principal_v23_test.go
+++ b/cmd/principal/principal_v23_test.go
@@ -43,7 +43,7 @@
 
 func V23TestBlessSelf(t *v23tests.T) {
 	var (
-		outputDir         = t.NewTempDir()
+		outputDir         = t.NewTempDir("")
 		aliceDir          = filepath.Join(outputDir, "alice")
 		aliceBlessingFile = filepath.Join(outputDir, "aliceself")
 	)
@@ -67,7 +67,7 @@
 
 func V23TestStore(t *v23tests.T) {
 	var (
-		outputDir   = t.NewTempDir()
+		outputDir   = t.NewTempDir("")
 		bin         = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		aliceDir    = filepath.Join(outputDir, "alice")
 		aliceFriend = filepath.Join(outputDir, "alice.bless")
@@ -128,7 +128,7 @@
 
 func V23TestDump(t *v23tests.T) {
 	var (
-		outputDir = t.NewTempDir()
+		outputDir = t.NewTempDir("")
 		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		aliceDir  = filepath.Join(outputDir, "alice")
 	)
@@ -153,7 +153,7 @@
 
 func V23TestGetRecognizedRoots(t *v23tests.T) {
 	var (
-		outputDir = t.NewTempDir()
+		outputDir = t.NewTempDir("")
 		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		aliceDir  = filepath.Join(outputDir, "alice")
 	)
@@ -172,7 +172,7 @@
 
 func V23TestGetPeermap(t *v23tests.T) {
 	var (
-		outputDir = t.NewTempDir()
+		outputDir = t.NewTempDir("")
 		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		aliceDir  = filepath.Join(outputDir, "alice")
 	)
@@ -209,7 +209,7 @@
 
 func V23TestRecvBlessings(t *v23tests.T) {
 	var (
-		outputDir    = t.NewTempDir()
+		outputDir    = t.NewTempDir("")
 		bin          = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		aliceDir     = filepath.Join(outputDir, "alice")
 		bobDir       = filepath.Join(outputDir, "bob")
@@ -305,7 +305,7 @@
 
 func V23TestFork(t *v23tests.T) {
 	var (
-		outputDir             = t.NewTempDir()
+		outputDir             = t.NewTempDir("")
 		bin                   = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		aliceDir              = filepath.Join(outputDir, "alice")
 		alicePhoneDir         = filepath.Join(outputDir, "alice-phone")
@@ -392,7 +392,7 @@
 
 func V23TestCreate(t *v23tests.T) {
 	var (
-		outputDir = t.NewTempDir()
+		outputDir = t.NewTempDir("")
 		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		aliceDir  = filepath.Join(outputDir, "alice")
 	)
@@ -411,7 +411,7 @@
 
 func V23TestCaveats(t *v23tests.T) {
 	var (
-		outputDir         = t.NewTempDir()
+		outputDir         = t.NewTempDir("")
 		aliceDir          = filepath.Join(outputDir, "alice")
 		aliceBlessingFile = filepath.Join(outputDir, "aliceself")
 	)
@@ -443,21 +443,21 @@
 
 func V23TestForkWithoutVDLPATH(t *v23tests.T) {
 	var (
-		parent = t.NewTempDir()
+		parent = t.NewTempDir("")
 		bin    = t.BuildGoPkg("v.io/x/ref/cmd/principal").WithEnv("V23_ROOT=''", "VDLPATH=''")
 	)
 	if err := bin.Start("create", parent, "parent").Wait(os.Stdout, os.Stderr); err != nil {
 		t.Fatalf("create %q failed: %v", parent, err)
 	}
-	if err := bin.Start("--v23.credentials="+parent, "fork", "--for=1s", t.NewTempDir(), "child").Wait(os.Stdout, os.Stderr); err != nil {
+	if err := bin.Start("--v23.credentials="+parent, "fork", "--for=1s", t.NewTempDir(""), "child").Wait(os.Stdout, os.Stderr); err != nil {
 		t.Errorf("fork failed: %v", err)
 	}
 }
 
 func V23TestForkWithoutCaveats(t *v23tests.T) {
 	var (
-		parent = t.NewTempDir()
-		child  = t.NewTempDir()
+		parent = t.NewTempDir("")
+		child  = t.NewTempDir("")
 		bin    = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		buf    bytes.Buffer
 	)
@@ -482,7 +482,7 @@
 func V23TestBless(t *v23tests.T) {
 	var (
 		bin      = t.BuildGoPkg("v.io/x/ref/cmd/principal")
-		dir      = t.NewTempDir()
+		dir      = t.NewTempDir("")
 		aliceDir = filepath.Join(dir, "alice")
 		bobDir   = filepath.Join(dir, "bob")
 		tmpfile  = filepath.Join(dir, "tmpfile")
@@ -549,9 +549,9 @@
 func V23TestAddBlessingsToRoots(t *v23tests.T) {
 	var (
 		bin          = t.BuildGoPkg("v.io/x/ref/cmd/principal")
-		aliceDir     = t.NewTempDir()
-		bobDir       = t.NewTempDir()
-		blessingFile = filepath.Join(t.NewTempDir(), "bobfile")
+		aliceDir     = t.NewTempDir("")
+		bobDir       = t.NewTempDir("")
+		blessingFile = filepath.Join(t.NewTempDir(""), "bobfile")
 
 		// Extract the public key from the first line of output from
 		// "principal dump", which is formatted as:
@@ -585,7 +585,7 @@
 func V23TestAddKeyToRoots(t *v23tests.T) {
 	var (
 		bin      = t.BuildGoPkg("v.io/x/ref/cmd/principal")
-		aliceDir = t.NewTempDir()
+		aliceDir = t.NewTempDir("")
 	)
 	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
 	// The second argument and the "want" line below were generated by:
diff --git a/cmd/vrun/vrun_v23_test.go b/cmd/vrun/vrun_v23_test.go
index b064186..cb43c0a 100644
--- a/cmd/vrun/vrun_v23_test.go
+++ b/cmd/vrun/vrun_v23_test.go
@@ -19,7 +19,7 @@
 func V23TestAgentd(t *v23tests.T) {
 	var (
 		clientAgent, serverAgent = createClientAndServerAgents(t)
-		tmpdir                   = t.NewTempDir()
+		tmpdir                   = t.NewTempDir("")
 		vrun                     = t.BuildGoPkg("v.io/x/ref/cmd/vrun").Path()
 		pingpong                 = t.BuildGoPkg("v.io/x/ref/services/agent/internal/pingpong").Path()
 		serverName               = serverAgent.Start(pingpong).ExpectVar("NAME")
@@ -62,8 +62,8 @@
 func createClientAndServerAgents(i *v23tests.T) (client, server *v23tests.Binary) {
 	var (
 		agentd    = i.BuildGoPkg("v.io/x/ref/services/agent/agentd")
-		clientDir = i.NewTempDir()
-		serverDir = i.NewTempDir()
+		clientDir = i.NewTempDir("")
+		serverDir = i.NewTempDir("")
 	)
 	pserver, err := vsecurity.CreatePersistentPrincipal(serverDir, nil)
 	if err != nil {
diff --git a/examples/tunnel/tunneld/tunneld_v23_test.go b/examples/tunnel/tunneld/tunneld_v23_test.go
index e153e8a..cf8ef05 100644
--- a/examples/tunnel/tunneld/tunneld_v23_test.go
+++ b/examples/tunnel/tunneld/tunneld_v23_test.go
@@ -41,7 +41,7 @@
 	}
 
 	// And again with a file redirection this time.
-	outDir := t.NewTempDir()
+	outDir := t.NewTempDir("")
 	outPath := filepath.Join(outDir, "hello.txt")
 
 	// TODO(sjr): instead of using Output() here, we'd really rather do
diff --git a/services/agent/agentlib/agent_v23_test.go b/services/agent/agentlib/agent_v23_test.go
index daa9185..cecdcc0 100644
--- a/services/agent/agentlib/agent_v23_test.go
+++ b/services/agent/agentlib/agent_v23_test.go
@@ -22,7 +22,7 @@
 //go:generate v23 test generate
 
 func V23TestTestPassPhraseUse(i *v23tests.T) {
-	bin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithEnv(envvar.Credentials + "=" + i.NewTempDir())
+	bin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithEnv(envvar.Credentials + "=" + i.NewTempDir(""))
 
 	// Create the passphrase
 	agent := bin.Start("echo", "Hello")
@@ -65,7 +65,7 @@
 	// (Errors are printed to STDERR)
 	testbin := i.BuildGoPkg("v.io/x/ref/services/agent/internal/test_principal").Path()
 	i.BuildGoPkg("v.io/x/ref/services/agent/agentd").
-		WithEnv(envvar.Credentials+"="+i.NewTempDir()).
+		WithEnv(envvar.Credentials+"="+i.NewTempDir("")).
 		Start(testbin).
 		WaitOrDie(nil, os.Stderr)
 }
@@ -95,7 +95,7 @@
 	// This only works with shells that propagate open file descriptors to
 	// children. POSIX-compliant shells do this as to many other commonly
 	// used ones like bash.
-	script := filepath.Join(i.NewTempDir(), "test.sh")
+	script := filepath.Join(i.NewTempDir(""), "test.sh")
 	if err := writeScript(
 		script,
 		`#!/bin/bash
@@ -126,7 +126,7 @@
 		pingpong                 = i.BuildGoPkg("v.io/x/ref/services/agent/internal/pingpong").Path()
 		serverName               = serverAgent.Start(pingpong).ExpectVar("NAME")
 
-		scriptDir = i.NewTempDir()
+		scriptDir = i.NewTempDir("")
 		counter   = filepath.Join(scriptDir, "counter")
 		script    = filepath.Join(scriptDir, "test.sh")
 	)
@@ -224,8 +224,8 @@
 func createClientAndServerAgents(i *v23tests.T) (client, server *v23tests.Binary) {
 	var (
 		agentd    = i.BuildGoPkg("v.io/x/ref/services/agent/agentd")
-		clientDir = i.NewTempDir()
-		serverDir = i.NewTempDir()
+		clientDir = i.NewTempDir("")
+		serverDir = i.NewTempDir("")
 	)
 	pserver, err := vsecurity.CreatePersistentPrincipal(serverDir, nil)
 	if err != nil {
diff --git a/services/application/applicationd/applicationd_v23_test.go b/services/application/applicationd/applicationd_v23_test.go
index 9c9e0fd..b76a389 100644
--- a/services/application/applicationd/applicationd_v23_test.go
+++ b/services/application/applicationd/applicationd_v23_test.go
@@ -59,7 +59,7 @@
 	appRepoName := "test-app-repo"
 	binaryWithCredentials(i, "applicationd", "v.io/x/ref/services/application/applicationd").Start(
 		"-name="+appRepoName,
-		"-store="+i.NewTempDir(),
+		"-store="+i.NewTempDir(""),
 		"-v=2",
 		"-v23.tcp.address=127.0.0.1:0")
 
diff --git a/services/build/buildd/buildd_v23_test.go b/services/build/buildd/buildd_v23_test.go
index 68381bb..e42767e 100644
--- a/services/build/buildd/buildd_v23_test.go
+++ b/services/build/buildd/buildd_v23_test.go
@@ -51,7 +51,7 @@
 		"-v23.tcp.address=127.0.0.1:0")
 
 	// Create and build a test source file.
-	testGoPath := i.NewTempDir()
+	testGoPath := i.NewTempDir("")
 	testBinDir := filepath.Join(testGoPath, "bin")
 	if err := os.MkdirAll(testBinDir, os.FileMode(0700)); err != nil {
 		i.Fatalf("MkdirAll(%v) failed: %v", testBinDir, err)
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index 742439c..1b09af6 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -9,20 +9,27 @@
 // manager so that all device manager components run as the same user and
 // no user input (such as an agent pass phrase) is needed.
 //
-// When this script is invoked with the --with_suid <user> flag, it
-// installs the device manager in its more secure multi-account
-// configuration where the device manager runs under the account of the
-// invoker and test apps will be executed as <user>. This mode will
-// require root permisisons to install and may require configuring an
-// agent passphrase.
+// This script can exercise the device manager in two different modes. It
+// can be executed like so:
 //
-// For exanple:
+// v23 go test -v . --v23.tests
 //
-//   v23 go test -v . --v23.tests --with_suid vanaguest
+// This will exercise the device manager's single user mode where all
+// processes run as the same invoking user.
 //
-// to test a device manager with multi-account support enabled for app
-// account vanaguest.
+// Alternatively, the device manager can be executed in multiple account
+// mode by providing the --deviceuser <deviceuser> and --appuser
+// <appuser> flags. In this case, the device manager will run as user
+// <devicemgr> and the test will run applications as user <appuser>. If
+// executed in this fashion, root permissions will be required to install
+// and it may require configuring an agent passphrase. For example:
 //
+//   v23 go test -v . --v23.tests --deviceuser devicemanager --appuser  vana
+//
+// NB: the accounts provided as arguments to this test must already exist.
+// Also, the --v23.tests.shell-on-fail flag is useful to enable debugging
+// output.
+
 package device_test
 
 //go:generate v23 test generate .
@@ -34,7 +41,9 @@
 	"io/ioutil"
 	"math/rand"
 	"os"
+	"os/user"
 	"path/filepath"
+	"regexp"
 	"strings"
 	"time"
 
@@ -44,13 +53,15 @@
 )
 
 var (
-	suidUserFlag string
-	hostname     string
-	errTimeout   = errors.New("timeout")
+	appUserFlag    string
+	deviceUserFlag string
+	hostname       string
+	errTimeout     = errors.New("timeout")
 )
 
 func init() {
-	flag.StringVar(&suidUserFlag, "with_suid", "", "run the device manager as the specified user")
+	flag.StringVar(&appUserFlag, "appuser", "", "launch apps as the specified user")
+	flag.StringVar(&deviceUserFlag, "deviceuser", "", "run the device manager as the specified user")
 	name, err := os.Hostname()
 	if err != nil {
 		panic(fmt.Sprintf("Hostname() failed: %v", err))
@@ -62,14 +73,30 @@
 	defer fmt.Fprintf(os.Stderr, "--------------- SHUTDOWN ---------------\n")
 	userFlag := "--single_user"
 	withSuid := false
-	if len(suidUserFlag) > 0 {
-		userFlag = "--with_suid=" + suidUserFlag
+	tempDir := ""
+	if len(deviceUserFlag) > 0 && len(appUserFlag) > 0 {
 		withSuid = true
+		i.Logf("device user %s, app user %s", deviceUserFlag, appUserFlag)
+		// When running --with_suid, TMPDIR must grant the
+		// invoking user rwx permissions and world x permissions for
+		// all parent directories back to /. Otherwise, the
+		// with_suid user will not be able to use absolute paths.
+		// On Darwin, TMPDIR defaults to a directory hieararchy
+		// in /var that is 0700. This is unworkable so force
+		// TMPDIR to /tmp in this case.
+		tempDir = "/tmp"
+	} else if len(deviceUserFlag) > 0 || len(appUserFlag) > 0 {
+		i.Fatalf("Running in multiuser mode requires --appuser and --deviceuser")
+	} else {
+		u, err := user.Current()
+		if err != nil {
+			i.Fatalf("couldn't get the current user: %v", err)
+		}
+		appUserFlag = u.Username
 	}
-	i.Logf("user flag: %q", userFlag)
 
 	var (
-		workDir       = i.NewTempDir()
+		workDir       = i.NewTempDir(tempDir)
 		binStagingDir = mkSubdir(i, workDir, "bin")
 		dmInstallDir  = filepath.Join(workDir, "dm")
 
@@ -103,6 +130,14 @@
 		mtName = "devices/" + hostname // Name under which the device manager will publish itself.
 	)
 
+	if withSuid {
+		// In multiuser mode, deviceUserFlag needs execute access to
+		// tempDir.
+		if err := os.Chmod(workDir, 0711); err != nil {
+			i.Fatalf("os.Chmod() failed: %v", err)
+		}
+	}
+
 	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
 	buildAndCopyBinaries(
 		i,
@@ -115,15 +150,25 @@
 	appDName := "applicationd"
 	devicedAppName := filepath.Join(appDName, "deviced", "test")
 
-	deviceScript.Start(
+	deviceScriptArguments := []string{
 		"install",
 		binStagingDir,
-		userFlag,
-		"--origin="+devicedAppName,
+	}
+
+	if withSuid {
+		deviceScriptArguments = append(deviceScriptArguments, "--devuser="+deviceUserFlag)
+	} else {
+		deviceScriptArguments = append(deviceScriptArguments, userFlag)
+	}
+
+	deviceScriptArguments = append(deviceScriptArguments, []string{
+		"--origin=" + devicedAppName,
 		"--",
 		"--v23.tcp.address=127.0.0.1:0",
-		"--neighborhood-name="+fmt.Sprintf("%s-%d-%d", hostname, os.Getpid(), rand.Int())).
-		WaitOrDie(os.Stdout, os.Stderr)
+		"--neighborhood-name=" + fmt.Sprintf("%s-%d-%d", hostname, os.Getpid(), rand.Int()),
+	}...)
+
+	deviceScript.Start(deviceScriptArguments...).WaitOrDie(os.Stdout, os.Stderr)
 	deviceScript.Start("start").WaitOrDie(os.Stdout, os.Stderr)
 
 	resolve := func(name string) string {
@@ -168,11 +213,12 @@
 	mtEP = resolveChange(mtName, mtEP)
 
 	if withSuid {
-		/*
-		   		   "${DEVICE_BIN}" associate add "${MT_NAME}/devmgr/device" "${SUID_USER}"  "alice"
-		        shell_test::assert_eq   "$("${DEVICE_BIN}" associate list "${MT_NAME}/devmgr/device")" \
-		          "alice ${SUID_USER}" "${LINENO}"
-		*/
+		deviceBin.Start("associate", "add", mtName+"/devmgr/device", appUserFlag, "root/alice")
+
+		aai := deviceBin.Start("associate", "list", mtName+"/devmgr/device")
+		if got, expected := strings.Trim(aai.Output(), "\n "), "root/alice "+appUserFlag; got != expected {
+			i.Fatalf("association test, got %v, expected %v", got, expected)
+		}
 	}
 
 	// Verify the device's default blessing is as expected.
@@ -269,7 +315,15 @@
 		i.Fatalf("got %q, want %q", got, want)
 	}
 
-	// TODO(rjkroege): Verify that the app is actually running as ${SUID_USER}
+	inv = debugBin.Start("stats", "read", instanceName+"/stats/system/pid")
+	pid := inv.ExpectRE("[0-9]+$", 1)[0][0]
+	uname, err := getUserForPid(i, pid)
+	if err != nil {
+		i.Errorf("getUserForPid could not determine the user running pid %v", pid)
+	}
+	if uname != appUserFlag {
+		i.Errorf("app expected to be running as %v but is running as %v", appUserFlag, uname)
+	}
 
 	// Verify the app's default blessing.
 	inv = debugBin.Start("stats", "read", instanceName+"/stats/security/principal/*/blessingstore")
@@ -357,9 +411,16 @@
 	}
 	resolveGone(mtName)
 
-	fi, err := ioutil.ReadDir(dmInstallDir)
-	if err != nil {
-		i.Fatalf("failed to readdir for %q: %v", dmInstallDir, err)
+	var fi []os.FileInfo
+
+	// This doesn't work in multiuser mode because dmInstallDir is
+	// owned by the device manager user and unreadable by the user
+	// running this test.
+	if !withSuid {
+		fi, err = ioutil.ReadDir(dmInstallDir)
+		if err != nil {
+			i.Fatalf("failed to readdir for %q: %v", dmInstallDir, err)
+		}
 	}
 
 	deviceScript.Run("uninstall")
@@ -389,3 +450,17 @@
 	}
 	return dir
 }
+
+var re = regexp.MustCompile("[ \t]+")
+
+// getUserForPid determines the username running process pid.
+func getUserForPid(i *v23tests.T, pid string) (string, error) {
+	pidString := i.BinaryFromPath("/bin/ps").Start(psFlags).Output()
+	for _, line := range strings.Split(pidString, "\n") {
+		fields := re.Split(line, -1)
+		if len(fields) > 1 && pid == fields[1] {
+			return fields[0], nil
+		}
+	}
+	return "", fmt.Errorf("Couldn't determine the user for pid %s", pid)
+}
diff --git a/services/device/shell.sh b/services/device/shell.sh
deleted file mode 100755
index d55c678..0000000
--- a/services/device/shell.sh
+++ /dev/null
@@ -1,217 +0,0 @@
-#!/bin/bash
-# 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.
-
-# The shell library is used to execute vanadium shell scripts.
-
-# IMPORTANT: If your script registers its own "trap" handler, that handler must
-# call shell::at_exit to clean up all temporary files and directories created by
-# this library.
-
-set -e
-set -u
-
-TMPDIR="${TMPDIR-/tmp}"
-V23_SHELL_TMP_DIRS=${V23_SHELL_TMP_DIRS-$(mktemp "${TMPDIR}/XXXXXXXX")}
-V23_SHELL_TMP_FILES=${V23_SHELL_TMP_FILES-$(mktemp "${TMPDIR}/XXXXXXXX")}
-V23_SHELL_TMP_PIDS=${V23_SHELL_TMP_PIDS-$(mktemp "${TMPDIR}/XXXXXXXX")}
-
-trap shell::at_exit INT TERM EXIT
-
-# When a user of this library needs cleanup to be performed by root
-# because different processes are running with different system
-# identities, it should set this variable to root to escalate
-# privilege appropriately.
-SUDO_USER="$(whoami)"
-
-# shell::at_exit is executed when the shell script exits. It is used,
-# for example, to garbage collect any temporary files and directories
-# created by invocations of shell::tmp_file and shell:tmp_dir.
-shell::at_exit() {
-  # If the variable V23_SHELL_CMD_LOOP_AT_EXIT is non-empty, accept commands
-  # from the user in a command loop.  This can preserve the state in the logs
-  # directories while the user examines them.
-  case "${V23_SHELL_CMD_LOOP_AT_EXIT-}" in
-  ?*)
-    local cmdline
-    set +u
-    echo 'Entering debug shell.  Type control-D or "exit" to exit.' >&2
-    while echo -n "test> " >&2; read cmdline; do
-      eval "$cmdline"
-    done
-    set -u
-    ;;
-  esac
-  # Unset the trap so that it doesn't run again on exit.
-  trap - INT TERM EXIT
-  for pid in $(cat "${V23_SHELL_TMP_PIDS}"); do
-    sudo -u "${SUDO_USER}" kill "${pid}" &> /dev/null || true
-  done
-  for tmp_dir in $(cat "${V23_SHELL_TMP_DIRS}"); do
-     sudo -u "${SUDO_USER}" rm -rf "${tmp_dir}" &>/dev/null
-  done
-  for tmp_file in $(cat "${V23_SHELL_TMP_FILES}"); do
-     sudo -u "${SUDO_USER}" rm -f "${tmp_file}" &>/dev/null
-  done
-   sudo -u "${SUDO_USER}" rm -f "${V23_SHELL_TMP_DIRS}" "${V23_SHELL_TMP_FILES}" "${V23_SHELL_TMP_PIDS}" &>/dev/null
-}
-
-# shell::kill_child_processes kills all child processes.
-# Note, child processes must set their own "at_exit: kill_child_processes"
-# signal handlers in order for this to kill their descendants.
-shell::kill_child_processes() {
-  # Attempt to shutdown child processes by sending the TERM signal.
-  if [[ -n "$(jobs -p -r)" ]]; then
-    shell::silence pkill -P $$
-    sleep 1
-    # Force shutdown of any remaining child processes using the KILL signal.
-    # Note, we only "sleep 1" and "kill -9" if there were child processes.
-    if [[ -n "$(jobs -p -r)" ]]; then
-      shell::silence sudo -u "${SUDO_USER}"  pkill -9 -P $$
-    fi
-  fi
-}
-
-# shell::check_deps can be used to check which dependencies are
-# missing on the host.
-#
-# Example:
-#   local -r DEPS=$(shell::check_deps git golang-go)
-#   if [[ -n "${DEPS} ]]; then
-#     sudo apt-get install $DEPS
-#   fi
-shell::check_deps() {
-  local RESULT=""
-  case $(uname -s) in
-    "Linux")
-      set +e
-      for pkg in "$@"; do
-        dpkg -s "${pkg}" &> /dev/null
-        if [[ "$?" -ne 0 ]]; then
-          RESULT+=" ${pkg}"
-        fi
-      done
-      set -e
-      ;;
-    "Darwin")
-      set +e
-      for pkg in "$@"; do
-        if [[ -z "$(brew ls --versions ${pkg})" ]]; then
-          RESULT+=" ${pkg}"
-        fi
-      done
-      set -e
-      ;;
-    *)
-      echo "Operating system $(uname -s) is not supported."
-      exit 1
-  esac
-  echo "${RESULT}"
-}
-
-# shell::check_result can be used to obtain the exit status of a bash
-# command. By the semantics of "set -e", a script exits immediately
-# upon encountering a command that fails. This function should be used
-# if instead, a script wants to take an action depending on the exit
-# status.
-#
-# Example:
-#   local -r RESULT=$(shell::check_result <command>)
-#   if [[ "${RESULT}" -ne 0 ]]; then
-#     <handle failure>
-#   else
-#     <handle success>
-#   fi
-shell::check_result() {
-  set +e
-  "$@" &> /dev/null
-  echo "$?"
-  set -e
-}
-
-# shell::tmp_dir can be used to create a temporary directory.
-#
-# Example:
-#   local -R TMP_DIR=$(shell::tmp_dir)
-shell::tmp_dir() {
-  local -r RESULT=$(mktemp -d "${TMPDIR}/XXXXXXXX")
-  echo "${RESULT}" >> "${V23_SHELL_TMP_DIRS}"
-  echo "${RESULT}"
-}
-
-# shell::tmp_file can be used to create a temporary file.
-#
-# Example:
-#   local -R TMP_FILE=$(shell::tmp_file)
-shell::tmp_file() {
-  local -r RESULT=$(mktemp "${TMPDIR}/XXXXXXXX")
-  echo "${RESULT}" >> "${V23_SHELL_TMP_FILES}"
-  echo "${RESULT}"
-}
-
-# shell::run_server is used to start a long running server process and
-# to verify that it is still running after a set timeout period. This
-# is useful for catching cases where the server either fails to start or
-# fails soon after it has started.
-# The script will return 0 if the command is running at that point in time
-# and a non-zero value otherwise. It echos the server's pid.
-#
-# Example:
-#   shell::run_server 5 stdout stderr command arg1 arg2 || exit 1
-shell::run_server() {
-  local -r TIMEOUT="$1"
-  local -r STDOUT="$2"
-  local -r STDERR="$3"
-  shift; shift; shift
-  if [[ "${STDOUT}" = "${STDERR}" ]]; then
-    "$@" > "${STDOUT}" 2>&1 &
-  else
-    "$@" > "${STDOUT}" 2> "${STDERR}" &
-  fi
-  local -r SERVER_PID=$!
-  echo "${SERVER_PID}" >> "${V23_SHELL_TMP_PIDS}"
-  echo "${SERVER_PID}"
-
-  for i in $(seq 1 "${TIMEOUT}"); do
-    sleep 1
-    local RESULT=$(shell::check_result kill -0 "${SERVER_PID}")
-    if [[ "${RESULT}" = "0" ]]; then
-      # server's up, can return early
-      return 0
-    fi
-  done
-  return "${RESULT}"
-}
-
-# shell::timed_wait_for is used to wait until the given pattern appears
-# in the given file within a set timeout. It returns 0 if the pattern
-# was successfully matched and 1 if the timeout expires before the pattern
-# is found.
-#
-# Example:
-#   local -r LOG_FILE=$(shell::tmp_file)
-#   shell::run_server 1 "${LOG_FILE}" "${LOG_FILE}" <server> <args>...
-#   shell::timed_wait_for 1 "${LOG_FILE}" "server started"
-shell::timed_wait_for() {
-  local -r TIMEOUT="$1"
-  local -r FILE="$2"
-  local -r PATTERN="$3"
-  for i in $(seq 1 "${TIMEOUT}"); do
-    sleep 1
-    grep -m 1 "${PATTERN}" "${FILE}" > /dev/null && return 0 || true
-  done
-  # grep has timed out.
-  echo "Timed out. Here's the file:"
-  echo "====BEGIN FILE==="
-  cat "${FILE}"
-  echo "====END FILE==="
-  return 1
-}
-
-# shell::silence runs the given command with the supplied arguments,
-# redirecting all of its output to /dev/null and ignoring its exit
-# code.
-shell::silence() {
-  "$@" &> /dev/null || true
-}
diff --git a/services/device/shell_test.sh b/services/device/shell_test.sh
deleted file mode 100755
index 9d66678..0000000
--- a/services/device/shell_test.sh
+++ /dev/null
@@ -1,276 +0,0 @@
-#!/bin/bash
-# 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.
-
-# The shell_test library is used to execute shell tests.
-
-# IMPORTANT: If your script registers its own "trap" handler, that
-# handler must call shell_test::at_exit to clean up all temporary
-# files and directories created by this library.
-
-source "$(go list -f {{.Dir}} v.io/x/ref/cmd/mgmt)/shell.sh"
-
-trap shell_test::at_exit INT TERM EXIT
-
-# ensure that we print a 'FAIL' message unless shell_test::pass is
-# explicitly called.
-shell_test_EXIT_MESSAGE="FAIL_UNEXPECTED_EXIT"
-
-# default timeout values.
-shell_test_DEFAULT_SERVER_TIMEOUT="10"
-shell_test_DEFAULT_MESSAGE_TIMEOUT="30"
-
-# default binary dir and working dir.
-shell_test_BIN_DIR="${shell_test_BIN_DIR:-$(shell::tmp_dir)}"
-shell_test_WORK_DIR="$(shell::tmp_dir)"
-
-# shell_test::assert_eq GOT WANT LINENO checks that GOT == WANT and if
-# not, fails the test, outputting the offending line number.
-shell_test::assert_eq() {
-  local -r GOT="$1"
-  local -r WANT="$2"
-  local -r LINENO="$3"
-  if [[ "${GOT}" != "${WANT}" ]]; then
-    shell_test::fail "line ${LINENO}: expected '${GOT}' == '${WANT}'"
-  fi
-}
-
-# shell_test::assert_ge GOT WANT LINENO checks that GOT >= WANT and if
-# not, fails the test, outputting the given line number and error
-# message.
-shell_test::assert_ge() {
-  local -r GOT="$1"
-  local -r WANT="$2"
-  local -r LINENO="$3"
-  if [[ "${GOT}" -lt "${WANT}" ]]; then
-    shell_test::fail "line ${LINENO}: expected ${GOT} >= ${WANT}"
-  fi
-}
-
-# shell_test::assert_gt GOT WANT LINENO checks that GOT > WANT and if
-# not, fails the test, outputting the given line number and error
-# message.
-shell_test::assert_gt() {
-  local -r GOT="$1"
-  local -r WANT="$2"
-  local -r LINENO="$3"
-  if [[ "${GOT}" -le "${WANT}" ]]; then
-    shell_test::fail "line ${LINENO}: expected ${GOT} > ${WANT}"
-  fi
-}
-
-# shell_test::assert_le GOT WANT LINENO checks that GOT <= WANT and if
-# not, fails the test, outputting the given line number and error
-# message.
-shell_test::assert_le() {
-  local -r GOT="$1"
-  local -r WANT="$2"
-  local -r LINENO="$3"
-  if [[ "${GOT}" -gt "${WANT}" ]]; then
-    shell_test::fail "line ${LINENO}: expected ${GOT} <= ${WANT}"
-  fi
-}
-
-# shell_test::assert_lt GOT WANT LINENO checks that GOT < WANT and if
-# not, fails the test, outputting the given line number and error
-# message.
-shell_test::assert_lt() {
-  local -r GOT="$1"
-  local -r WANT="$2"
-  local -r LINENO="$3"
-  if [[ "${GOT}" -ge "${WANT}" ]]; then
-    shell_test::fail "line ${LINENO}: expected ${GOT} < ${WANT}"
-  fi
-}
-
-# shell_test::assert_ne GOT WANT LINENO checks that GOT != WANT and if
-# not, fails the test, outputting the offending line number.
-shell_test::assert_ne() {
-  local -r GOT="$1"
-  local -r WANT="$2"
-  local -r LINENO="$3"
-  if [[ "${GOT}" == "${WANT}" ]]; then
-    shell_test::fail "line ${LINENO}: expected '${GOT}' != '${WANT}'"
-  fi
-}
-
-# shell_test::assert_contains GOT WANT LINENO checks that GOT contains WANT and
-# if not, fails the test, outputting the offending line number.
-shell_test::assert_contains() {
-  local -r GOT="$1"
-  local -r WANT="$2"
-  local -r LINENO="$3"
-  if [[ ! "${GOT}" =~ "${WANT}" ]]; then
-    shell_test::fail "line ${LINENO}: expected '${GOT}' contains '${WANT}'"
-  fi
-}
-
-# shell_test::at_exit is executed when the shell script exits.
-shell_test::at_exit() {
-  echo "${shell_test_EXIT_MESSAGE}"
-  # Note: shell::at_exit unsets our trap, so it won't run again on exit.
-  shell::at_exit
-  shell::kill_child_processes
-}
-
-# shell_test::fail is used to identify a failing test.
-#
-# Example:
-#   <command> || shell_test::fail "line ${LINENO}: <error message>"
-shell_test::fail() {
-  [[ "$#" -gt 0 ]] && echo "$0 $*"
-  shell_test_EXIT_MESSAGE="FAIL"
-  exit 1
-}
-
-# shell_test::pass is used to identify a passing test.
-shell_test::pass() {
-  shell_test_EXIT_MESSAGE="PASS"
-  exit 0
-}
-
-# shell_test::build_go_binary builds the binary for the given go package ($1) with
-# the given output ($2) in $shell_test_BIN_DIR. Before building a binary, it will
-# check whether it already exists. If so, it will skip it.
-shell_test::build_go_binary() {
-  pushd "${shell_test_BIN_DIR}" > /dev/null
-  local -r PACKAGE="$1"
-  local OUTPUT="${2:-$(basename ${PACKAGE})}"
-  if [[ -f "${OUTPUT}" ]]; then
-    echo "${shell_test_BIN_DIR}/${OUTPUT}"
-    return
-  fi
-
-  go build -o "${OUTPUT}" "${PACKAGE}" 2>/dev/null || shell_test::fail "line ${LINENO}: failed to build ${OUTPUT}"
-  echo "${shell_test_BIN_DIR}/${OUTPUT}"
-  popd > /dev/null
-}
-
-# shell_test::setup_server_test is common boilerplate used for testing vanadium
-# servers. In particular, this function sets up an instance of the mount table
-# daemon, and sets the V23_NAMESPACE environment variable accordingly.  It also
-# sets up credentials as needed.
-shell_test::setup_server_test() {
-  if [[ -n ${shell_test_RUNNING_UNDER_AGENT+1} ]]; then
-    shell_test::setup_server_test_agent
-  else
-    shell_test::setup_server_test_no_agent
-  fi
-}
-
-# shell_test::setup_mounttable brings up a mounttable and sets the
-# V23_NAMESPACE to point to it.
-shell_test::setup_mounttable() {
-  # Build the mount table daemon and related tools.
-  MOUNTTABLED_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/mounttable/mounttabled')"
-
-  # Start the mounttable daemon.
-  local -r MT_LOG=$(shell::tmp_file)
-  if [[ -n ${shell_test_RUNNING_UNDER_AGENT+1} ]]; then
-    shell::run_server "${shell_test_DEFAULT_SERVER_TIMEOUT}" "${MT_LOG}" "${MT_LOG}" "${VRUN}" "${MOUNTTABLED_BIN}" --v23="127.0.0.1:0" &> /dev/null || (cat "${MT_LOG}" && shell_test::fail "line ${LINENO}: failed to start mounttabled")
-  else
-    shell::run_server "${shell_test_DEFAULT_SERVER_TIMEOUT}" "${MT_LOG}" "${MT_LOG}" "${MOUNTTABLED_BIN}" --v23.tcp.address="127.0.0.1:0" &> /dev/null || (cat "${MT_LOG}" && shell_test::fail "line ${LINENO}: failed to start mounttabled")
-  fi
-  shell::timed_wait_for "${shell_test_DEFAULT_MESSAGE_TIMEOUT}" "${MT_LOG}" "Mount table service" || shell_test::fail "line ${LINENO}: failed to find expected output"
-
-  export V23_NAMESPACE=$(grep "Mount table service at:" "${MT_LOG}" | sed -e 's/^.*endpoint: //')
-  [[ -n "${V23_NAMESPACE}" ]] || shell_test::fail "line ${LINENO}: failed to read endpoint from logfile"
-}
-
-# shell_test::setup_server_test_no_agent does the work of setup_server_test when
-# not running under enable_agent (using credentials directories).
-shell_test::setup_server_test_no_agent() {
-  [[ ! -n ${shell_test_RUNNING_UNDER_AGENT+1} ]] \
-    || shell_test::fail "setup_server_test_no_agent called when running under enable_agent"
-  shell_test::setup_mounttable
-
-  # Create and use a new credentials directory /after/ the mount table is
-  # started so that servers or clients started after this point start with different
-  # credentials.
-  local -r TMP=${V23_CREDENTIALS:-}
-  if [[ -z "${TMP}" ]]; then
-    export V23_CREDENTIALS=$(shell::tmp_dir)
-  fi
-}
-
-# shell_test::setup_server_test_agent does the work of setup_server_test when
-# running under enable_agent.
-shell_test::setup_server_test_agent() {
-  [[ -n ${shell_test_RUNNING_UNDER_AGENT+1} ]] \
-    || shell_test::fail "setup_server_test_agent called when not running under enable_agent"
-  shell_test::setup_mounttable
-}
-
-# shell_test::start_server is used to start a server. The server is
-# considered started when it successfully mounts itself into a mount
-# table; an event that is detected using the shell::wait_for
-# function.
-#
-# Example:
-#   shell_test::start_server <server> <args>
-shell_test::start_server() {
-  START_SERVER_LOG_FILE=$(shell::tmp_file)
-  START_SERVER_PID=$(shell::run_server "${shell_test_DEFAULT_SERVER_TIMEOUT}" "${START_SERVER_LOG_FILE}" "${START_SERVER_LOG_FILE}" "$@" -logtostderr -vmodule=publisher=2)
-  shell::timed_wait_for "${shell_test_DEFAULT_MESSAGE_TIMEOUT}" "${START_SERVER_LOG_FILE}" "ipc pub: mount"
-}
-
-# shell_test::credentials is used to create a new VeyronCredentials directory.
-# The directory is initialized with a new principal that has a self-signed
-# blessing of the specified name set as default and shareable with all peers.
-#
-# Example:
-#   shell_test::credentials <self blessing name>
-shell_test::credentials() {
-  [[ ! -n ${shell_test_RUNNING_UNDER_AGENT+1} ]] \
-    || shell_test::fail "credentials called when running under enable_agent"
-  local -r PRINCIPAL_BIN="$(shell_test::build_go_binary 'v.io/x/ref/cmd/principal')"
-  local -r CRED=$(shell::tmp_dir)
-  "${PRINCIPAL_BIN}" create --overwrite=true "${CRED}" "$1" >/dev/null || shell_test::fail "line ${LINENO}: create failed"
-  echo "${CRED}"
-}
-
-# shell_test::forkcredentials is used to create a new VeyronCredentials directory
-# that is initialized with a principal blessed by the provided principal under the
-# provided extension.
-#
-# Example:
-#   shell_test::forkcredentials <blesser principal's VeyronCredentials> <extension>
-shell_test::forkcredentials() {
-  [[ ! -n ${shell_test_RUNNING_UNDER_AGENT+1} ]] \
-    || shell_test::fail "forkcredentials called when running under enable_agent"
-
-  local -r PRINCIPAL_BIN="$(shell_test::build_go_binary 'v.io/x/ref/cmd/principal')"
-  local -r FORKCRED=$(shell::tmp_dir)
-  "${PRINCIPAL_BIN}" create --overwrite=true "${FORKCRED}" self >/dev/null || shell_test::fail "line ${LINENO}: create failed"
-  "${PRINCIPAL_BIN}" --v23.credentials="$1" bless --require-caveats=false "${FORKCRED}" "$2" >blessing || shell_test::fail "line ${LINENO}: bless failed"
-  "${PRINCIPAL_BIN}" --v23.credentials="${FORKCRED}" store setdefault blessing || shell_test::fail "line ${LINENO}: store setdefault failed"
-  "${PRINCIPAL_BIN}" --v23.credentials="${FORKCRED}" store set blessing ... || shell_test::fail "line ${LINENO}: store set failed"
-  echo "${FORKCRED}"
-}
-
-# shell_test::enable_agent causes the test to be executed under the security
-# agent.  It sets up the agent and then runs itself under that agent.
-#
-# Example:
-#   shell_test::enable_agent "$@"
-#   build() {
-#     ...
-#   }
-#   main() {
-#     build
-#     ...
-#     shell_test::pass
-#   }
-#   main "$@"
-shell_test::enable_agent() {
-  if [[ ! -n ${shell_test_RUNNING_UNDER_AGENT+1} ]]; then
-    local -r AGENTD="$(shell_test::build_go_binary 'v.io/x/ref/services/agent/agentd')"
-    local -r WORKDIR="${shell_test_WORK_DIR}"
-    export shell_test_RUNNING_UNDER_AGENT=1
-    V23_CREDENTIALS="${WORKDIR}/credentials" exec ${AGENTD} --no-passphrase --additional-principals="${WORKDIR}/childcredentials" bash -"$-" "$0" "$@"
-    shell_test::fail "failed to run test under agent"
-  else
-    VRUN="$(shell_test::build_go_binary 'v.io/x/ref/cmd/vrun')"
-  fi
-}
diff --git a/services/device/suid_test.sh b/services/device/suid_test.sh
deleted file mode 100755
index 2c09107..0000000
--- a/services/device/suid_test.sh
+++ /dev/null
@@ -1,348 +0,0 @@
-#!/bin/bash
-# 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.
-
-# Test the device manager and related services and tools.
-#
-#
-# By default, this script tests the device manager in a fashion amenable
-# to automatic testing: the --single_user is passed to the device
-# manager so that all device manager components run as the same user and
-# no user input (such as an agent pass phrase) is needed.
-#
-# When this script is invoked with the --with_suid <user1> <user2> flag, it
-# installs the device manager in its more secure multi-account
-# configuration where the device manager runs under the account of <user1>
-# while test apps will be executed as <user2>. This mode will
-# require root permissions to install and may require configuring an
-# agent passphrase.
-#
-# For exanple:
-#
-#   ./suid_test.sh --with_suid devicemanager vana
-#
-# to test a device manager with multi-account support enabled for app
-# account vana.
-#
-
-# When running --with_suid, TMPDIR must grant the invoking user rwx
-# permissions and x permissions for all directories back to / for world.
-# Otherwise, the with_suid user will not be able to use absolute paths.
-# On Darwin, TMPDIR defaults to a directory hieararchy in /var that is
-# 0700. This is unworkable so force TMPDIR to /tmp in this case.
-WITH_SUID="${1:-no}"
-# TODO(caprita,rjkroege): Add logic to the integration test that verifies
-# installing and accessing packages from apps.  This would add coverage to the
-# package-related code in suid mode.
-if [[ "${WITH_SUID}" == "--with_suid" ]]; then
-  DEVMGR_USER="${2:?--with_suid requires a devicemgr user}"
-  SUID_USER="${3:?--with_suid requires a app user}"
-  SUDO_USER="root"
-  TMPDIR=/tmp
-  umask 066
-fi
-
- source "$(go list -f {{.Dir}} v.io/x/ref/cmd/mgmt)/shell_test.sh"
-
-# Run the test under the security agent.
-shell_test::enable_agent "$@"
-
-readonly WORKDIR="${shell_test_WORK_DIR}"
-
-build() {
-  echo ">> Building binaries"
-  BINARYD_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/binary/binaryd')"
-  BINARY_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/binary/binary')"
-  APPLICATIOND_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/application/applicationd')"
-  APPLICATION_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/application/application')"
-  AGENTD_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/agent/agentd')"
-  SUIDHELPER_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/device/suidhelper')"
-  INITHELPER_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/device/inithelper')"
-  DEVICEMANAGER_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/device/deviced')"
-  DEVICE_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/device/device')"
-  NAMESPACE_BIN="$(shell_test::build_go_binary 'v.io/x/ref/cmd/namespace')"
-  PRINCIPAL_BIN="$(shell_test::build_go_binary 'v.io/x/ref/cmd/principal')"
-  DEBUG_BIN="$(shell_test::build_go_binary 'v.io/x/ref/services/debug/debug')"
-  DEVICE_SCRIPT="$(go list -f {{.Dir}} v.io/x/ref/services/device)/devicex"
-}
-
-# TODO(caprita): Move to shell_tesh.sh
-
-###############################################################################
-# Waits until the given name appears in the mounttable, within a set timeout.
-# Arguments:
-#   path to namespace command-line tool
-#   timeout in seconds
-#   name to look up
-#   old mount entry value (if specified, waits until a different value appears)
-# Returns:
-#   0 if the name was successfully found, and 1 if the timeout expires before
-#   the name appears.
-#   Prints the new value of the mount entry.
-###############################################################################
-wait_for_mountentry() {
-  local -r NAMESPACE_BIN="$1"
-  local -r TIMEOUT="$2"
-  local -r NAME="$3"
-  local -r OLD_ENTRY="${4:+}"
-  for i in $(seq 1 "${TIMEOUT}"); do
-    local ENTRY=$("${NAMESPACE_BIN}" resolve "${NAME}" 2>/dev/null)
-    if [[ -n "${ENTRY}" && "${ENTRY}" != "${OLD_ENTRY}" ]]; then
-      echo ${ENTRY}
-      return 0
-    fi
-    sleep 1
-  done
-  echo "Timed out waiting for ${NAME} to have a mounttable entry different from ${OLD_ENTRY}."
-  return 1
-}
-
-###############################################################################
-# Waits until the given name disappears from the mounttable, within a set
-# timeout.
-# Arguments:
-#   path to namespace command-line tool
-#   timeout in seconds
-#   name to look up
-# Returns:
-#   0 if the name was gone from the mounttable, and 1 if the timeout expires
-#   while the name is still in the mounttable.
-###############################################################################
-wait_for_no_mountentry() {
-  local -r NAMESPACE_BIN="$1"
-  local -r TIMEOUT="$2"
-  local -r NAME="$3"
-  for i in $(seq 1 "${TIMEOUT}"); do
-    local ENTRY=$("${NAMESPACE_BIN}" resolve "${NAME}" 2>/dev/null)
-    if [[ -z "${ENTRY}" ]]; then
-      return 0
-    fi
-    sleep 1
-  done
-  echo "Timed out waiting for ${NAME} to disappear from the mounttable."
-  return 1
-}
-
-main() {
-  cd "${WORKDIR}"
-  build
-
-  local -r APPLICATIOND_NAME="applicationd"
-  local -r DEVICED_APP_NAME="${APPLICATIOND_NAME}/deviced/test"
-
-  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
-
-  if [[ "${WITH_SUID}" == "--with_suid" ]]; then
-    chmod go+x "${WORKDIR}"
-  fi
-
-  echo ">> Installing and starting the device manager"
-  DM_INSTALL_DIR="${WORKDIR}/dm"
-
-  export V23_DEVICE_DIR="${DM_INSTALL_DIR}"
-
-  if [[ "${WITH_SUID}" != "--with_suid" ]]; then
-    local -r extra_arg="--single_user"
-  else
-    local -r extra_arg="--devuser=${DEVMGR_USER}"
-  fi
-
-  local -r NEIGHBORHOODNAME="$(hostname)-$$-${RANDOM}"
-  "${DEVICE_SCRIPT}" install "${BIN_STAGING_DIR}" \
-    ${extra_arg} \
-    --origin="${DEVICED_APP_NAME}" \
-    -- \
-    --v23.tcp.address=127.0.0.1:0 \
-    --neighborhood-name="${NEIGHBORHOODNAME}"
-
-  "${VRUN}" "${DEVICE_SCRIPT}" start
-  local -r MT_NAME=devices/$(hostname)
-  MT_EP=$(wait_for_mountentry "${NAMESPACE_BIN}" 5 "${MT_NAME}")
-
-  # Verify that device manager's mounttable is published under the expected name
-  # (hostname).
-  shell_test::assert_ne "$("${NAMESPACE_BIN}" glob "${MT_NAME}")" "" "${LINENO}"
-
-  # Create a self-signed blessing with name "alice" and set it as default and
-  # shareable with all peers on the principal that this process is running
-  # as. This blessing will be used by all commands except those running under
-  # "vrun" which gets a principal forked from the process principal.
-  "${PRINCIPAL_BIN}" blessself alice > alice.bless || \
-    shell_test::fail "line ${LINENO}: blessself alice failed"
-  "${PRINCIPAL_BIN}" store setdefault alice.bless || \
-    shell_test::fail "line ${LINENO}: store setdefault failed"
-  "${PRINCIPAL_BIN}" store set alice.bless ... || \
-    shell_test::fail "line ${LINENO}: store set failed"
-
-  # Claim the device as "alice/myworkstation".
-  echo ">> Claiming the device manager"
-  "${DEVICE_BIN}" claim "${MT_NAME}/devmgr/device" myworkstation
-  # Wait for the device manager to re-mount after being claimed
-  MT_EP=$(wait_for_mountentry "${NAMESPACE_BIN}" 5 "${MT_NAME}" "${MT_EP}")
-
-  if [[ "${WITH_SUID}" == "--with_suid" ]]; then
-    echo ">> Verify that devicemanager has valid association for alice"
-    "${DEVICE_BIN}" associate add "${MT_NAME}/devmgr/device" "${SUID_USER}"  "alice"
-     shell_test::assert_eq   "$("${DEVICE_BIN}" associate list "${MT_NAME}/devmgr/device")" \
-       "alice ${SUID_USER}" "${LINENO}"
-     echo ">> Verify that devicemanager runs as ${DEVMGR_USER}"
-     local -r DPID=$("${DEBUG_BIN}" stats read \
-         "${MT_NAME}/devmgr/__debug/stats/system/pid" \
-         | awk '{print $2}')
-    # ps flags need to be different on linux
-    case "$(uname)" in
-    "Darwin")
-      local -r COMPUTED_DEVMGR_USER=$(ps -ej | \
-          awk '$2 ~'"${DPID}"' { print $1 }')
-      ;;
-    "Linux")
-      local -r COMPUTED_DEVMGR_USER=$(awk '/^Uid:/{print $2}' /proc/${DPID}/status | \
-          xargs getent passwd | awk -F: '{print $1}')
-      ;;
-     esac
-     shell_test::assert_eq "${COMPUTED_DEVMGR_USER}" \
-          "${DEVMGR_USER}" \
-          "${LINENO}"
-  fi
-
-  # Verify the device's default blessing is as expected.
-  shell_test::assert_contains "$("${DEBUG_BIN}" stats read "${MT_NAME}/devmgr/__debug/stats/security/principal/*/blessingstore" | head -1)" \
-    "Default blessings: alice/myworkstation" "${LINENO}"
-
-  # Get the device's profile.
-  local -r DEVICE_PROFILE=$("${DEVICE_BIN}" describe "${MT_NAME}/devmgr/device" | sed -e 's/{Profiles:map\[\(.*\):{}]}/\1/')
-
-  # Start a binary server under the blessing "alice/myworkstation/binaryd" so that
-  # 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="${WORKDIR}/binstore" --v23.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
-  # itself.
-  local -r SAMPLE_APP_BIN_NAME="${BINARYD_NAME}/testapp"
-  echo ">> Uploading ${SAMPLE_APP_BIN_NAME}"
-  "${BINARY_BIN}" upload "${SAMPLE_APP_BIN_NAME}" "${BINARYD_BIN}"
-
-  # Verify that the binary we uploaded is shown by glob.
-  shell_test::assert_eq "$("${NAMESPACE_BIN}" glob "${SAMPLE_APP_BIN_NAME}")" \
-    "${SAMPLE_APP_BIN_NAME}" "${LINENO}"
-
-  # 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="${WORKDIR}/appstore" --v23.tcp.address=127.0.0.1:0 \
-    || shell_test::fail "line ${LINENO} failed to start applicationd"
-
-  # Upload an envelope for our test app.
-  local -r SAMPLE_APP_NAME="${APPLICATIOND_NAME}/testapp/v0"
-  local -r APP_PUBLISH_NAME="testbinaryd"
-  echo ">> Uploading ${SAMPLE_APP_NAME}"
-  echo "{\"Title\":\"BINARYD\", \"Args\":[\"--name=${APP_PUBLISH_NAME}\", \"--root-dir=./binstore\", \"--v23.tcp.address=127.0.0.1:0\"], \"Binary\":{\"File\":\"${SAMPLE_APP_BIN_NAME}\"}, \"Env\":[]}" > ./app.envelope
-  "${APPLICATION_BIN}" put "${SAMPLE_APP_NAME}" "${DEVICE_PROFILE}" ./app.envelope
-  rm ./app.envelope
-
-  # Verify that the envelope we uploaded shows up with glob.
-  shell_test::assert_eq "$("${APPLICATION_BIN}" match "${SAMPLE_APP_NAME}" "${DEVICE_PROFILE}" | grep Title | sed -e 's/^.*"Title": "'// | sed -e 's/",//')" \
-    "BINARYD" "${LINENO}"
-
-  # Install the app on the device.
-  echo ">> Installing ${SAMPLE_APP_NAME}"
-  local -r INSTALLATION_NAME=$("${DEVICE_BIN}" install "${MT_NAME}/devmgr/apps" "${SAMPLE_APP_NAME}" | sed -e 's/Successfully installed: "//' | sed -e 's/"//')
-
-  # Verify that the installation shows up when globbing the device manager.
-  shell_test::assert_eq "$("${NAMESPACE_BIN}" glob "${MT_NAME}/devmgr/apps/BINARYD/*")" \
-    "${INSTALLATION_NAME}" "${LINENO}"
-
-  # Start an instance of the app, granting it blessing extension myapp.
-  echo ">> Starting ${INSTALLATION_NAME}"
-  local -r INSTANCE_NAME=$("${DEVICE_BIN}" start "${INSTALLATION_NAME}" myapp | sed -e 's/Successfully started: "//' | sed -e 's/"//')
-  wait_for_mountentry "${NAMESPACE_BIN}" "5" "${MT_NAME}/${APP_PUBLISH_NAME}"
-
-  # Verify that the instance shows up when globbing the device manager.
-  shell_test::assert_eq "$("${NAMESPACE_BIN}" glob "${MT_NAME}/devmgr/apps/BINARYD/*/*")" "${INSTANCE_NAME}" "${LINENO}"
-
-  if [[ "${WITH_SUID}" == "--with_suid" ]]; then
-    echo ">> Verifying that the app is actually running as the associated user"
-     local -r PID=$("${DEBUG_BIN}" stats read "${MT_NAME}/devmgr/apps/BINARYD/*/*/stats/system/pid"  | awk '{print $2}')
-    # ps flags need to be different on linux
-    case "$(uname)" in
-    "Darwin")
-      local -r COMPUTED_SUID_USER=$(ps -ej | awk '$2 ~'"${PID}"' { print $1 }')
-      ;;
-    "Linux")
-        local -r COMPUTED_SUID_USER=$(awk '/^Uid:/{print $2}' /proc/${PID}/status | \
-          xargs getent passwd | awk -F: '{print $1}')
-      ;;
-     esac
-     shell_test::assert_eq "${COMPUTED_SUID_USER}" "${SUID_USER}" "${LINENO}"
- fi
-
-  # Verify the app's default blessing.
-  shell_test::assert_contains "$("${DEBUG_BIN}" stats read "${INSTANCE_NAME}/stats/security/principal/*/blessingstore" | head -1)" \
-    "Default blessings: alice/myapp/BINARYD" "${LINENO}"
-
-  # Stop the instance.
-  echo ">> Stopping ${INSTANCE_NAME}"
-  "${DEVICE_BIN}" stop "${INSTANCE_NAME}"
-
-  # Verify that logs, but not stats, show up when globbing the stopped instance.
-  shell_test::assert_eq "$("${NAMESPACE_BIN}" glob "${INSTANCE_NAME}/stats/...")" "" "${LINENO}"
-  shell_test::assert_ne "$("${NAMESPACE_BIN}" glob "${INSTANCE_NAME}/logs/...")" "" "${LINENO}"
-
-  # Upload a deviced binary.
-  local -r DEVICED_APP_BIN_NAME="${BINARYD_NAME}/deviced"
-  echo ">> Uploading ${DEVICEMANAGER_BIN}"
-  "${BINARY_BIN}" upload "${DEVICED_APP_BIN_NAME}" "${DEVICEMANAGER_BIN}"
-
-  # Upload a device manager envelope.
-  echo ">> Uploading ${DEVICED_APP_NAME}"
-  echo "{\"Title\":\"device manager\", \"Binary\":{\"File\":\"${DEVICED_APP_BIN_NAME}\"}}" > ./deviced.envelope
-  "${APPLICATION_BIN}" put "${DEVICED_APP_NAME}" "${DEVICE_PROFILE}" ./deviced.envelope
-  rm ./deviced.envelope
-  # Update the device manager.
-  echo ">> Updating device manager"
-  "${DEVICE_BIN}" update "${MT_NAME}/devmgr/device"
-  MT_EP=$(wait_for_mountentry "${NAMESPACE_BIN}" 5 "${MT_NAME}" "${MT_EP}")
-
-  # Verify that device manager's mounttable is still published under the
-  # expected name (hostname).
-  shell_test::assert_ne "$("${NAMESPACE_BIN}" glob "${MT_NAME}")" "" "${LINENO}"
-
-  # Revert the device manager.
-  echo ">> Reverting device manager"
-  "${DEVICE_BIN}" revert "${MT_NAME}/devmgr/device"
-  MT_EP=$(wait_for_mountentry "${NAMESPACE_BIN}" 5 "${MT_NAME}" "${MT_EP}")
-
-  # Verify that device manager's mounttable is still published under the
-  # expected name (hostname).
-  shell_test::assert_ne "$("${NAMESPACE_BIN}" glob "${MT_NAME}")" "" "${LINENO}"
-
-  # Verify that the local mounttable exists, and that the device manager, the
-  # global namespace, and the neighborhood are mounted on it.
-  shell_test::assert_ne $("${NAMESPACE_BIN}" resolve "${MT_EP}/devmgr") "" "${LINENO}"
-  shell_test::assert_eq $("${NAMESPACE_BIN}" resolve "${MT_EP}/global") "[alice/myworkstation]${V23_NAMESPACE}" "${LINENO}"
-  shell_test::assert_ne $("${NAMESPACE_BIN}" resolve "${MT_EP}/nh") "" "${LINENO}"
-
-  # Suspend the device manager.
-  "${DEVICE_BIN}" suspend "${MT_NAME}/devmgr/device"
-  wait_for_mountentry "${NAMESPACE_BIN}" "5" "${MT_NAME}" "${MT_EP}"
-
-  # Stop the device manager.
-  "${DEVICE_SCRIPT}" stop
-  wait_for_no_mountentry "${NAMESPACE_BIN}" "5" "${MT_NAME}"
-
-  "${DEVICE_SCRIPT}" uninstall
-  if [[ -n "$(ls -A "${V23_DEVICE_DIR}" 2>/dev/null)" ]]; then
-    shell_test::fail "${V23_DEVICE_DIR} is not empty"
-  fi
-  shell_test::pass
-}
-
-main "$@"
diff --git a/services/device/test.sh b/services/device/test.sh
deleted file mode 100755
index 5bca914..0000000
--- a/services/device/test.sh
+++ /dev/null
@@ -1,114 +0,0 @@
-#!/bin/bash
-# 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.
-
-#
-# Unit tests for the shell functions in this directory
-#
-
-source "$(go list -f {{.Dir}} v.io/x/ref/cmd/mgmt)/shell_test.sh"
-
-test_assert() {
-  shell_test::assert_eq "foo" "foo" "${LINENO}"
-  shell_test::assert_eq "42" "42" "${LINENO}"
-  shell_test::assert_ge "1" "1" "${LINENO}"
-  shell_test::assert_ge "42" "1" "${LINENO}"
-  shell_test::assert_gt "42" "1" "${LINENO}"
-  shell_test::assert_le "1" "1" "${LINENO}"
-  shell_test::assert_le "1" "42" "${LINENO}"
-  shell_test::assert_lt "1" "42" "${LINENO}"
-  shell_test::assert_ne "1" "42" "${LINENO}"
-  shell_test::assert_ne "foo" "bar" "${LINENO}"
-}
-
-test_run_server() {
-  shell::run_server 1 /dev/null /dev/null foobar > /dev/null 2>&1
-  shell_test::assert_eq "$?" "1" "${LINENO}"
-
-  shell::run_server 1 /dev/null /dev/null sleep 10 > /dev/null 2>&1
-  shell_test::assert_eq "$?" "0" "${LINENO}"
-}
-
-test_timed_wait_for() {
-  local GOT WANT
-  local -r TMPFILE=$(shell::tmp_file)
-  touch "${TMPFILE}"
-
-  shell::timed_wait_for 2 "${TMPFILE}" "doesn't matter, it's all zeros anyway"
-  shell_test::assert_eq "$?" "1" "${LINENO}"
-
-  echo "foo" > "${TMPFILE}"
-  shell::timed_wait_for 2 "${TMPFILE}" "bar"
-  shell_test::assert_eq "$?" "1" "${LINENO}"
-
-  echo "bar" >> "${TMPFILE}"
-  echo "baz" >> "${TMPFILE}"
-  shell::timed_wait_for 2 "${TMPFILE}" "bar"
-  shell_test::assert_eq "$?" "0" "${LINENO}"
-}
-
-# rmpublickey replaces public keys (16 hex bytes, :-separated) with XX:....
-# This substitution enables comparison with golden output even when keys are freshly
-# minted by the "principal create" command.
-rmpublickey() {
-    sed -e "s/\([0-9a-f]\{2\}:\)\{15\}[0-9a-f]\{2\}/XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX/g"
-}
-
-test_credentials() {
-  local -r CRED=$(shell_test::credentials alice)
-
-  local -r PRINCIPAL_BIN="$(shell_test::build_go_binary 'v.io/x/ref/cmd/principal')"
-
-  "${PRINCIPAL_BIN}" --v23.credentials="${CRED}" dump >alice.dump ||  shell_test::fail "line ${LINENO}: ${PRINCIPAL_BIN} dump ${CRED} failed"
-  cat alice.dump | rmpublickey >got || shell_test::fail "line ${LINENO}: cat alice.dump | rmpublickey failed"
-  cat >want <<EOF
-Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
----------------- BlessingStore ----------------
-Default blessings: alice
-Peer pattern                   : Blessings
-...                            : alice
----------------- BlessingRoots ----------------
-Public key                                      : Pattern
-XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [alice]
-EOF
-  if ! diff got want; then
-    shell_test::fail "line ${LINENO}"
-  fi
-}
-
-test_forkcredentials() {
-  local -r CRED=$(shell_test::credentials alice)
-  local -r FORKCRED=$(shell_test::forkcredentials "${CRED}" fork)
-
-  local -r PRINCIPAL_BIN="$(shell_test::build_go_binary 'v.io/x/ref/cmd/principal')"
-
-  "${PRINCIPAL_BIN}" --v23.credentials="${FORKCRED}" dump >alice.dump ||  shell_test::fail "line ${LINENO}: ${PRINCIPAL_BIN} dump ${CRED} failed"
-  cat alice.dump | rmpublickey >got || shell_test::fail "line ${LINENO}: cat alice.dump | rmpublickey failed"
-  cat >want <<EOF
-Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
----------------- BlessingStore ----------------
-Default blessings: alice/fork
-Peer pattern                   : Blessings
-...                            : alice/fork
----------------- BlessingRoots ----------------
-Public key                                      : Pattern
-XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [alice]
-XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [self]
-EOF
-  if ! diff got want; then
-    shell_test::fail "line ${LINENO}"
-  fi
-}
-
-main() {
-  test_assert || shell_test::fail "assert"
-  test_run_server || shell_test::fail "test_run_server"
-  test_timed_wait_for  || shell_test::fail "test_run_server"
-  test_credentials  || shell_test::fail "test_credentials"
-  test_forkcredentials  || shell_test::fail "test_forkcredentials"
-
-  shell_test::pass
-}
-
-main "$@"
diff --git a/services/device/util_darwin_test.go b/services/device/util_darwin_test.go
new file mode 100644
index 0000000..2a91405
--- /dev/null
+++ b/services/device/util_darwin_test.go
@@ -0,0 +1,7 @@
+// 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 device_test
+
+const psFlags = "-ej"
diff --git a/services/device/util_linux_test.go b/services/device/util_linux_test.go
new file mode 100644
index 0000000..bb96666
--- /dev/null
+++ b/services/device/util_linux_test.go
@@ -0,0 +1,7 @@
+// 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 device_test
+
+const psFlags = "-ef"
diff --git a/services/profile/profiled/profiled_v23_test.go b/services/profile/profiled/profiled_v23_test.go
index 527ba7f..a91cfbe 100644
--- a/services/profile/profiled/profiled_v23_test.go
+++ b/services/profile/profiled/profiled_v23_test.go
@@ -51,7 +51,7 @@
 
 	// Start the profile repository.
 	profileRepoName := "test-profile-repo"
-	profileRepoStore := i.NewTempDir()
+	profileRepoStore := i.NewTempDir("")
 	args := []string{
 		"-name=" + profileRepoName, "-store=" + profileRepoStore,
 		"-v23.tcp.address=127.0.0.1:0",
diff --git a/test/v23tests/v23tests.go b/test/v23tests/v23tests.go
index 8945296..b2122e7 100644
--- a/test/v23tests/v23tests.go
+++ b/test/v23tests/v23tests.go
@@ -475,9 +475,9 @@
 
 // NewTempDir creates a temporary directory. Temporary directories and
 // their contents will be deleted by Cleanup.
-func (t *T) NewTempDir() string {
+func (t *T) NewTempDir(dir string) string {
 	loc := Caller(1)
-	f, err := ioutil.TempDir("", "")
+	f, err := ioutil.TempDir(dir, "")
 	if err != nil {
 		t.Fatalf("%s: TempDir() failed: %v", loc, err)
 	}
@@ -538,7 +538,7 @@
 		shutdown:      shutdown,
 	}
 	if len(e.cachedBinDir) == 0 {
-		e.binDir = e.NewTempDir()
+		e.binDir = e.NewTempDir("")
 	}
 	return e
 }