Merge "veyron/lib/modules: introduce sh.StartExternalCommand"
diff --git a/profiles/chrome/chromeinit.go b/profiles/chrome/chromeinit.go
index 9f4ace2..1019de9 100644
--- a/profiles/chrome/chromeinit.go
+++ b/profiles/chrome/chromeinit.go
@@ -7,6 +7,7 @@
 
 	"v.io/core/veyron2"
 	"v.io/core/veyron2/context"
+	"v.io/core/veyron2/ipc"
 	"v.io/core/veyron2/ipc/stream"
 	"v.io/core/veyron2/vlog"
 
@@ -31,7 +32,9 @@
 		return nil, nil, nil, err
 	}
 
-	runtime, ctx, shutdown, err := grt.Init(ctx, nil, nil, nil, commonFlags.RuntimeFlags(), nil)
+	protocols := []string{"wsh", "ws"}
+	listenSpec := ipc.ListenSpec{Addrs: ipc.ListenAddrs{{Protocol: "ws", Address: ""}}}
+	runtime, ctx, shutdown, err := grt.Init(ctx, nil, protocols, &listenSpec, commonFlags.RuntimeFlags(), nil)
 	if err != nil {
 		return nil, nil, shutdown, err
 	}
diff --git a/profiles/roaming/roaming_server.go b/profiles/roaming/roaming_server.go
index e85f22e..30874be 100644
--- a/profiles/roaming/roaming_server.go
+++ b/profiles/roaming/roaming_server.go
@@ -7,6 +7,7 @@
 
 	"v.io/core/veyron2"
 	"v.io/core/veyron2/ipc"
+	"v.io/core/veyron2/vlog"
 
 	"v.io/core/veyron/profiles/roaming"
 )
@@ -14,11 +15,10 @@
 func main() {
 	ctx, shutdown := veyron2.Init()
 	defer shutdown()
-	log := veyron2.GetLogger(ctx)
 
 	server, err := veyron2.NewServer(ctx)
 	if err != nil {
-		log.Fatalf("unexpected error: %q", err)
+		vlog.Fatalf("unexpected error: %q", err)
 	}
 
 	listenSpec := veyron2.GetListenSpec(ctx)
@@ -26,13 +26,13 @@
 	fmt.Printf("listen spec: %v\n", listenSpec)
 	ep, err := server.Listen(listenSpec)
 	if err != nil {
-		log.Fatalf("unexpected error: %q", err)
+		vlog.Fatalf("unexpected error: %q", err)
 	}
 	if ep != nil {
 		fmt.Println(ep)
 	}
 	if err := server.Serve("roamer", &receiver{}, nil); err != nil {
-		log.Fatalf("unexpected error: %q", err)
+		vlog.Fatalf("unexpected error: %q", err)
 	}
 
 	done := make(chan struct{})
diff --git a/profiles/roaming/roaminginit.go b/profiles/roaming/roaminginit.go
index 48350e4..45a5f56 100644
--- a/profiles/roaming/roaminginit.go
+++ b/profiles/roaming/roaminginit.go
@@ -139,7 +139,6 @@
 	ch chan<- config.Setting) {
 	defer close(ch)
 
-	log := runtime.GetLogger(ctx)
 	listenSpec := runtime.GetListenSpec(ctx)
 
 	// TODO(cnicolaou): add support for listening on multiple network addresses.
@@ -150,21 +149,21 @@
 		case <-watcher.Channel():
 			cur, err := netstate.GetAccessibleIPs()
 			if err != nil {
-				log.Errorf("failed to read network state: %s", err)
+				vlog.Errorf("failed to read network state: %s", err)
 				continue
 			}
 			removed := netstate.FindRemoved(prev, cur)
 			added := netstate.FindAdded(prev, cur)
-			log.VI(2).Infof("Previous: %d: %s", len(prev), prev)
-			log.VI(2).Infof("Current : %d: %s", len(cur), cur)
-			log.VI(2).Infof("Added   : %d: %s", len(added), added)
-			log.VI(2).Infof("Removed : %d: %s", len(removed), removed)
+			vlog.VI(2).Infof("Previous: %d: %s", len(prev), prev)
+			vlog.VI(2).Infof("Current : %d: %s", len(cur), cur)
+			vlog.VI(2).Infof("Added   : %d: %s", len(added), added)
+			vlog.VI(2).Infof("Removed : %d: %s", len(removed), removed)
 			if len(removed) == 0 && len(added) == 0 {
-				log.VI(2).Infof("Network event that lead to no address changes since our last 'baseline'")
+				vlog.VI(2).Infof("Network event that lead to no address changes since our last 'baseline'")
 				continue
 			}
 			if len(removed) > 0 {
-				log.VI(2).Infof("Sending removed: %s", removed)
+				vlog.VI(2).Infof("Sending removed: %s", removed)
 				ch <- ipc.NewRmAddrsSetting(removed)
 			}
 			// We will always send the best currently available address
diff --git a/runtimes/fake/runtime.go b/runtimes/fake/runtime.go
index baa6386..4f5bd29 100644
--- a/runtimes/fake/runtime.go
+++ b/runtimes/fake/runtime.go
@@ -7,7 +7,6 @@
 	"v.io/core/veyron2"
 	"v.io/core/veyron2/context"
 	"v.io/core/veyron2/security"
-	"v.io/core/veyron2/vlog"
 
 	tsecurity "v.io/core/veyron/lib/testutil/security"
 )
@@ -28,6 +27,10 @@
 	return &Runtime{}, ctx, func() {}, nil
 }
 
+func (r *Runtime) Init(ctx *context.T) error {
+	return nil
+}
+
 func (r *Runtime) SetPrincipal(ctx *context.T, principal security.Principal) (*context.T, error) {
 	return context.WithValue(ctx, principalKey, principal), nil
 }
@@ -37,19 +40,6 @@
 	return p
 }
 
-func (r *Runtime) SetNewLogger(ctx *context.T, name string, opts ...vlog.LoggingOpts) (*context.T, vlog.Logger, error) {
-	logger, err := vlog.NewLogger(name, opts...)
-	if err != nil {
-		return context.WithValue(ctx, loggerKey, logger), logger, nil
-	}
-	return ctx, nil, err
-}
-
-func (r *Runtime) GetLogger(ctx *context.T) vlog.Logger {
-	l, _ := ctx.Value(loggerKey).(vlog.Logger)
-	return l
-}
-
 func (r *Runtime) GetAppCycle(ctx *context.T) veyron2.AppCycle {
 	panic("unimplemented")
 }
diff --git a/runtimes/google/rt/mgmt.go b/runtimes/google/rt/mgmt.go
index ca0f7e3..ccaad62 100644
--- a/runtimes/google/rt/mgmt.go
+++ b/runtimes/google/rt/mgmt.go
@@ -14,13 +14,17 @@
 	"v.io/core/veyron/lib/exec"
 )
 
-func (rt *Runtime) initMgmt(ctx *context.T, appCycle veyron2.AppCycle, handle *exec.ChildHandle) error {
-	// Do not initialize the mgmt runtime if the process has not
-	// been started through the veyron exec library by a device
-	// manager.
-	if handle == nil {
+func (rt *Runtime) initMgmt(ctx *context.T) error {
+	handle, err := exec.GetChildHandle()
+	if err == exec.ErrNoVersion {
+		// Do not initialize the mgmt runtime if the process has not
+		// been started through the veyron exec library by a device
+		// manager.
 		return nil
+	} else if err != nil {
+		return err
 	}
+
 	parentName, err := handle.Config.Get(mgmt.ParentNameConfigKey)
 	if err != nil {
 		return nil
@@ -45,7 +49,7 @@
 	if err != nil {
 		return err
 	}
-	if err := server.Serve("", appCycle.Remote(), nil); err != nil {
+	if err := server.Serve("", veyron2.GetAppCycle(ctx).Remote(), nil); err != nil {
 		server.Stop()
 		return err
 	}
@@ -55,6 +59,13 @@
 		return err
 	}
 
+	// Note(mattr): that we ignore the error here.
+	// As far as I can tell this is because the modules framework actually calls
+	// SetReady(), so we would then call it here a second time and get
+	// an error.
+	// TODO(mattr): We should change the modules framework so we can properly check
+	// errors here.
+	handle.SetReady()
 	return nil
 }
 
@@ -79,7 +90,7 @@
 
 func (rt *Runtime) callbackToParent(ctx *context.T, parentName, myName string) error {
 	ctx, _ = context.WithTimeout(ctx, time.Minute)
-	call, err := rt.GetClient(ctx).StartCall(ctx, parentName, "Set", []interface{}{mgmt.AppCycleManagerConfigKey, myName}, options.NoResolve{})
+	call, err := rt.GetClient(ctx).StartCall(ctx, parentName, "Set", []interface{}{mgmt.AppCycleManagerConfigKey, myName})
 
 	if err != nil {
 		return err
diff --git a/runtimes/google/rt/rt_test.go b/runtimes/google/rt/rt_test.go
index ce3a6a4..a4e473d 100644
--- a/runtimes/google/rt/rt_test.go
+++ b/runtimes/google/rt/rt_test.go
@@ -36,7 +36,7 @@
 	ctx, shutdown := veyron2.Init()
 	defer shutdown()
 
-	l := veyron2.GetLogger(ctx)
+	l := vlog.Log
 	fmt.Println(l)
 	args := fmt.Sprintf("%s", l)
 	expected := regexp.MustCompile("name=veyron logdirs=\\[/tmp\\] logtostderr=true|false alsologtostderr=false|true max_stack_buf_size=4292608 v=[0-9] stderrthreshold=2 vmodule= log_backtrace_at=:0")
@@ -59,10 +59,10 @@
 }
 
 func child(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
-	ctx, shutdown := veyron2.Init()
+	_, shutdown := veyron2.Init()
 	defer shutdown()
 
-	logger := veyron2.GetLogger(ctx)
+	logger := vlog.Log
 	vlog.Infof("%s\n", logger)
 	fmt.Fprintf(stdout, "%s\n", logger)
 	modules.WaitForEOF(stdin)
diff --git a/runtimes/google/rt/runtime.go b/runtimes/google/rt/runtime.go
index b74bc10..f52b2d9 100644
--- a/runtimes/google/rt/runtime.go
+++ b/runtimes/google/rt/runtime.go
@@ -21,7 +21,6 @@
 	"v.io/core/veyron2/vlog"
 	"v.io/core/veyron2/vtrace"
 
-	"v.io/core/veyron/lib/exec"
 	"v.io/core/veyron/lib/flags"
 	_ "v.io/core/veyron/lib/stats/sysstats"
 	iipc "v.io/core/veyron/runtimes/google/ipc"
@@ -39,7 +38,6 @@
 	streamManagerKey = contextKey(iota)
 	clientKey
 	namespaceKey
-	loggerKey
 	principalKey
 	reservedNameKey
 	profileKey
@@ -63,26 +61,16 @@
 	opts       []ipc.ServerOpt
 }
 
-// TODO(mattr,suharshs): Decide if ROpts would be better than this.
+// TODO(mattr,suharshs): Decide if Options would be better than this.
 func Init(ctx *context.T, appCycle veyron2.AppCycle, protocols []string, listenSpec *ipc.ListenSpec, flags flags.RuntimeFlags,
 	reservedDispatcher ipc.Dispatcher, dispatcherOpts ...ipc.ServerOpt) (*Runtime, *context.T, veyron2.Shutdown, error) {
 	r := &Runtime{deps: dependency.NewGraph()}
 
-	handle, err := exec.GetChildHandle()
-	switch err {
-	case exec.ErrNoVersion:
-		// The process has not been started through the veyron exec
-		// library. No further action is needed.
-	case nil:
-		// The process has been started through the veyron exec
-		// library.
-	default:
+	err := vlog.ConfigureLibraryLoggerFromFlags()
+	if err != nil {
 		return nil, nil, nil, err
 	}
 
-	r.initLogging(ctx)
-	ctx = context.WithValue(ctx, loggerKey, vlog.Log)
-
 	// Setup the initial trace.
 	ctx, err = ivtrace.Init(ctx, flags.Vtrace)
 	if err != nil {
@@ -148,7 +136,7 @@
 	}
 
 	// Initialize security.
-	principal, err := initSecurity(ctx, handle, flags.Credentials, client)
+	principal, err := initSecurity(ctx, flags.Credentials, client)
 	if err != nil {
 		return nil, nil, nil, err
 	}
@@ -160,20 +148,11 @@
 		return nil, nil, nil, err
 	}
 
-	// Initialize management.
-	if err := r.initMgmt(ctx, r.GetAppCycle(ctx), handle); err != nil {
-		return nil, nil, nil, err
-	}
-
 	ctx = r.SetBackgroundContext(ctx)
 
 	// TODO(suharshs,mattr): Go through the rt.Cleanup function and make sure everything
 	// gets cleaned up.
 
-	if handle != nil {
-		handle.SetReady()
-	}
-
 	return r, ctx, r.shutdown, nil
 }
 
@@ -192,17 +171,15 @@
 	return nil
 }
 
+func (r *Runtime) Init(ctx *context.T) error {
+	return r.initMgmt(ctx)
+}
+
 func (r *Runtime) shutdown() {
 	r.deps.CloseAndWaitForAll()
 	vlog.FlushLog()
 }
 
-// initLogging configures logging for the runtime. It needs to be called after
-// flag.Parse and after signal handling has been initialized.
-func (r *Runtime) initLogging(ctx *context.T) error {
-	return vlog.ConfigureLibraryLoggerFromFlags()
-}
-
 func (r *Runtime) initSignalHandling(ctx *context.T) {
 	// TODO(caprita): Given that our device manager implementation is to
 	// kill all child apps when the device manager dies, we should
@@ -394,19 +371,6 @@
 	return ns
 }
 
-func (*Runtime) SetNewLogger(ctx *context.T, name string, opts ...vlog.LoggingOpts) (*context.T, vlog.Logger, error) {
-	logger, err := vlog.NewLogger(name, opts...)
-	if err == nil {
-		ctx = context.WithValue(ctx, loggerKey, logger)
-	}
-	return ctx, logger, err
-}
-
-func (*Runtime) GetLogger(ctx *context.T) vlog.Logger {
-	logger, _ := ctx.Value(loggerKey).(vlog.Logger)
-	return logger
-}
-
 func (*Runtime) GetAppCycle(ctx *context.T) veyron2.AppCycle {
 	appCycle, _ := ctx.Value(appCycleKey).(veyron2.AppCycle)
 	return appCycle
diff --git a/runtimes/google/rt/security.go b/runtimes/google/rt/security.go
index ab640df..b4a3aef 100644
--- a/runtimes/google/rt/security.go
+++ b/runtimes/google/rt/security.go
@@ -18,8 +18,8 @@
 	"v.io/core/veyron/security/agent"
 )
 
-func initSecurity(ctx *context.T, handle *exec.ChildHandle, credentials string, client ipc.Client) (security.Principal, error) {
-	principal, err := setupPrincipal(ctx, handle, credentials, client)
+func initSecurity(ctx *context.T, credentials string, client ipc.Client) (security.Principal, error) {
+	principal, err := setupPrincipal(ctx, credentials, client)
 	if err != nil {
 		return nil, err
 	}
@@ -31,13 +31,13 @@
 	return principal, nil
 }
 
-func setupPrincipal(ctx *context.T, handle *exec.ChildHandle, credentials string, client ipc.Client) (security.Principal, error) {
+func setupPrincipal(ctx *context.T, credentials string, client ipc.Client) (security.Principal, error) {
 	var err error
 	var principal security.Principal
 	if principal, _ = ctx.Value(principalKey).(security.Principal); principal != nil {
 		return principal, nil
 	}
-	if fd, err := agentFD(handle); err != nil {
+	if fd, err := agentFD(); err != nil {
 		return nil, err
 	} else if fd >= 0 {
 		return connectToAgent(ctx, fd, client)
@@ -66,7 +66,11 @@
 // agentFD returns a non-negative file descriptor to be used to communicate with
 // the security agent if the current process has been configured to use the
 // agent.
-func agentFD(handle *exec.ChildHandle) (int, error) {
+func agentFD() (int, error) {
+	handle, err := exec.GetChildHandle()
+	if err != nil && err != exec.ErrNoVersion {
+		return -1, err
+	}
 	var fd string
 	if handle != nil {
 		// We were started by a parent (presumably, device manager).
diff --git a/security/agent/agentd/main.go b/security/agent/agentd/main.go
index c011d8a..ec7babd 100644
--- a/security/agent/agentd/main.go
+++ b/security/agent/agentd/main.go
@@ -81,13 +81,11 @@
 		vlog.Panic("failed to set principal for ctx: %v", err)
 	}
 
-	log := veyron2.GetLogger(ctx)
-
 	if err = os.Setenv(agent.FdVarName, "3"); err != nil {
-		log.Fatalf("setenv: %v", err)
+		vlog.Fatalf("setenv: %v", err)
 	}
 	if err = os.Setenv(consts.VeyronCredentials, ""); err != nil {
-		log.Fatalf("setenv: %v", err)
+		vlog.Fatalf("setenv: %v", err)
 	}
 
 	if *keypath == "" && passphrase != nil {
@@ -101,11 +99,11 @@
 	// Start running our server.
 	var sock, mgrSock *os.File
 	if sock, err = server.RunAnonymousAgent(ctx, p); err != nil {
-		log.Fatalf("RunAnonymousAgent: %v", err)
+		vlog.Fatalf("RunAnonymousAgent: %v", err)
 	}
 	if *keypath != "" {
 		if mgrSock, err = server.RunKeyManager(ctx, *keypath, passphrase); err != nil {
-			log.Fatalf("RunKeyManager: %v", err)
+			vlog.Fatalf("RunKeyManager: %v", err)
 		}
 	}
 
@@ -123,7 +121,7 @@
 
 		err = cmd.Start()
 		if err != nil {
-			log.Fatalf("Error starting child: %v", err)
+			vlog.Fatalf("Error starting child: %v", err)
 		}
 		shutdown := make(chan struct{})
 		go func() {
diff --git a/security/agent/pingpong/main.go b/security/agent/pingpong/main.go
index 532edd9..4b8c428 100644
--- a/security/agent/pingpong/main.go
+++ b/security/agent/pingpong/main.go
@@ -8,6 +8,7 @@
 	"v.io/core/veyron2/context"
 	"v.io/core/veyron2/ipc"
 	"v.io/core/veyron2/security"
+	"v.io/core/veyron2/vlog"
 
 	"v.io/core/veyron/lib/signals"
 	_ "v.io/core/veyron/profiles"
@@ -22,24 +23,22 @@
 }
 
 func clientMain(ctx *context.T) {
-	log := veyron2.GetLogger(ctx)
-	log.Info("Pinging...")
+	vlog.Info("Pinging...")
 
 	s := PingPongClient("pingpong")
 	pong, err := s.Ping(ctx, "ping")
 	if err != nil {
-		log.Fatal("error pinging: ", err)
+		vlog.Fatal("error pinging: ", err)
 	}
 	fmt.Println(pong)
 }
 
 func serverMain(ctx *context.T) {
-	log := veyron2.GetLogger(ctx)
 	s, err := veyron2.NewServer(ctx)
 	if err != nil {
-		log.Fatal("failure creating server: ", err)
+		vlog.Fatal("failure creating server: ", err)
 	}
-	log.Info("Waiting for ping")
+	vlog.Info("Waiting for ping")
 
 	serverPong := PingPongServer(&pongd{})
 
@@ -47,11 +46,11 @@
 	if endpoint, err := s.Listen(spec); err == nil {
 		fmt.Printf("Listening at: %v\n", endpoint)
 	} else {
-		log.Fatal("error listening to service: ", err)
+		vlog.Fatal("error listening to service: ", err)
 	}
 
 	if err := s.Serve("pingpong", serverPong, allowEveryone{}); err != nil {
-		log.Fatal("error serving service: ", err)
+		vlog.Fatal("error serving service: ", err)
 	}
 
 	// Wait forever.
@@ -63,8 +62,6 @@
 func (allowEveryone) Authorize(security.Context) error { return nil }
 
 func main() {
-	flag.Parse()
-
 	ctx, shutdown := veyron2.Init()
 	defer shutdown()
 
diff --git a/services/mgmt/device/impl/app_service.go b/services/mgmt/device/impl/app_service.go
index 940df40..1538c98 100644
--- a/services/mgmt/device/impl/app_service.go
+++ b/services/mgmt/device/impl/app_service.go
@@ -119,7 +119,6 @@
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"os/user"
 	"path"
 	"path/filepath"
 	"reflect"
@@ -677,56 +676,6 @@
 	return instanceDir, instanceID, nil
 }
 
-// isSetuid is defined like this so we can override its
-// implementation for tests.
-var isSetuid = func(fileStat os.FileInfo) bool {
-	vlog.VI(2).Infof("running the original isSetuid")
-	return fileStat.Mode()&os.ModeSetuid == os.ModeSetuid
-}
-
-// systemAccountForHelper returns the uname that the helper uses to invoke the
-// application. If the helper exists and is setuid, the device manager
-// requires that there is a uname associated with the Veyron
-// identity that requested starting an application.
-// TODO(rjkroege): This function assumes a desktop installation target
-// and is probably not a good fit in other contexts. Revisit the design
-// as appropriate. This function also internalizes a decision as to when
-// it is possible to start an application that needs to be made explicit.
-func systemAccountForHelper(ctx ipc.ServerContext, helperPath string, uat BlessingSystemAssociationStore) (systemName string, err error) {
-	identityNames := ctx.RemoteBlessings().ForContext(ctx)
-	helperStat, err := os.Stat(helperPath)
-	if err != nil {
-		vlog.Errorf("Stat(%v) failed: %v. helper is required.", helperPath, err)
-		return "", verror2.Make(ErrOperationFailed, ctx.Context())
-	}
-	haveHelper := isSetuid(helperStat)
-	systemName, present := uat.SystemAccountForBlessings(identityNames)
-
-	switch {
-	case haveHelper && present:
-		return systemName, nil
-	case haveHelper && !present:
-		// The helper is owned by the device manager and installed as
-		// setuid root.  Therefore, the device manager must never run an
-		// app as itself to prevent an app trivially granting itself
-		// root permissions.  There must be an associated uname for the
-		// account in this case.
-		return "", verror2.Make(verror2.NoAccess, ctx.Context(), "use of setuid helper requires an associated uname.")
-	case !haveHelper:
-		// When the helper is not setuid, the helper can't change the
-		// app's uid so just run the app as the device manager's uname
-		// whether or not there is an association.
-		vlog.VI(1).Infof("helper not setuid. Device manager will invoke app with its own userid")
-		user, err := user.Current()
-		if err != nil {
-			vlog.Errorf("user.Current() failed: %v", err)
-			return "", verror2.Make(ErrOperationFailed, ctx.Context())
-		}
-		return user.Username, nil
-	}
-	return "", verror2.Make(ErrOperationFailed, ctx.Context())
-}
-
 func genCmd(instanceDir, helperPath, systemName string, nsRoots []string) (*exec.Cmd, error) {
 	versionLink := filepath.Join(instanceDir, "version")
 	versionDir, err := filepath.EvalSymlinks(versionLink)
@@ -745,7 +694,15 @@
 	}
 
 	cmd := exec.Command(helperPath)
-	cmd.Args = append(cmd.Args, "--username", systemName)
+
+	switch yes, err := suidHelper.suidhelperEnabled(systemName, helperPath); {
+	case err != nil:
+		return nil, err
+	case yes:
+		cmd.Args = append(cmd.Args, "--username", systemName)
+	case !yes:
+		cmd.Args = append(cmd.Args, "--username", systemName, "--dryrun")
+	}
 
 	var nsRootEnvs []string
 	for i, r := range nsRoots {
@@ -908,12 +865,7 @@
 		return nil, err
 	}
 
-	systemName, err := systemAccountForHelper(call, helper, i.uat)
-	if err != nil {
-		cleanupDir(instanceDir, helper)
-		return nil, err
-	}
-
+	systemName := suidHelper.usernameForPrincipal(call, i.uat)
 	if err := saveSystemNameForInstance(instanceDir, systemName); err != nil {
 		cleanupDir(instanceDir, helper)
 		return nil, err
@@ -956,11 +908,7 @@
 		return err
 	}
 
-	systemName, err := systemAccountForHelper(call, i.config.Helper, i.uat)
-	if err != nil {
-		return err
-	}
-
+	systemName := suidHelper.usernameForPrincipal(call, i.uat)
 	startSystemName, err := readSystemNameForInstance(instanceDir)
 	if err != nil {
 		return err
diff --git a/services/mgmt/device/impl/helper_manager.go b/services/mgmt/device/impl/helper_manager.go
new file mode 100644
index 0000000..9e935d2
--- /dev/null
+++ b/services/mgmt/device/impl/helper_manager.go
@@ -0,0 +1,69 @@
+package impl
+
+import (
+	"os"
+	"os/user"
+
+	"v.io/core/veyron2/ipc"
+	"v.io/core/veyron2/verror2"
+	"v.io/core/veyron2/vlog"
+)
+
+type suidHelperState string
+
+var suidHelper suidHelperState
+
+func init() {
+	u, err := user.Current()
+	if err != nil {
+		vlog.Panicf("devicemanager has no current user: %v", err)
+	}
+	suidHelper = suidHelperState(u.Username)
+}
+
+// isSetuid is defined like this so we can override its
+// implementation for tests.
+var isSetuid = func(fileStat os.FileInfo) bool {
+	vlog.VI(2).Infof("running the original isSetuid")
+	return fileStat.Mode()&os.ModeSetuid == os.ModeSetuid
+}
+
+// unameRequiresSuidhelper determines if the the suidhelper must exist
+// and be setuid to run an application as system user un. If false, the
+// setuidhelper must be invoked with the --dryrun flag to skip making
+// system calls that will fail or provide apps with a trivial
+// priviledge escalation.
+func (dn suidHelperState) suidhelperEnabled(un, helperPath string) (bool, error) {
+	helperStat, err := os.Stat(helperPath)
+	if err != nil {
+		vlog.Errorf("Stat(%v) failed: %v. helper is required.", helperPath, err)
+		return false, verror2.Make(ErrOperationFailed, nil)
+	}
+	haveHelper := isSetuid(helperStat)
+
+	switch {
+	case haveHelper && string(dn) != un:
+		return true, nil
+	case haveHelper && string(dn) == un:
+		return false, verror2.Make(verror2.NoAccess, nil)
+	default:
+		return false, nil
+	}
+	// Keep the compiler happy.
+	return false, nil
+}
+
+// usernameForVanadiumPrincipal returns the system name that the
+// devicemanager will use to invoke apps.
+// TODO(rjkroege): This code assumes a desktop target and will need
+// to be reconsidered for embedded contexts.
+func (i suidHelperState) usernameForPrincipal(ctx ipc.ServerContext, uat BlessingSystemAssociationStore) string {
+	identityNames := ctx.RemoteBlessings().ForContext(ctx)
+	systemName, present := uat.SystemAccountForBlessings(identityNames)
+
+	if present {
+		return systemName
+	} else {
+		return string(i)
+	}
+}
diff --git a/services/mgmt/suidhelper/impl/args.go b/services/mgmt/suidhelper/impl/args.go
index 67003a3..185bae8 100644
--- a/services/mgmt/suidhelper/impl/args.go
+++ b/services/mgmt/suidhelper/impl/args.go
@@ -36,7 +36,7 @@
 var (
 	flagUsername, flagWorkspace, flagLogDir, flagRun *string
 	flagMinimumUid                                   *int64
-	flagRemove                                       *bool
+	flagRemove, flagDryrun                           *bool
 )
 
 func init() {
@@ -53,6 +53,7 @@
 	flagRun = sflag.Run
 	flagMinimumUid = sflag.MinimumUid
 	flagRemove = sflag.Remove
+	flagDryrun = sflag.Dryrun
 }
 
 // ParseArguments populates the WorkParameter object from the provided args
@@ -88,6 +89,8 @@
 		return fmt.Errorf("suidhelper does not permit uids less than %d", *flagMinimumUid)
 	}
 
+	wp.dryrun = *flagDryrun
+
 	// Preserve the arguments for examination by the test harness if executed
 	// in the course of a test.
 	if os.Getenv("VEYRON_SUIDHELPER_TEST") != "" {
diff --git a/services/mgmt/suidhelper/impl/args_test.go b/services/mgmt/suidhelper/impl/args_test.go
index 9358e9f..9341fc7 100644
--- a/services/mgmt/suidhelper/impl/args_test.go
+++ b/services/mgmt/suidhelper/impl/args_test.go
@@ -85,6 +85,24 @@
 			fmt.Errorf("suidhelper does not permit uids less than 501"),
 			WorkParameters{},
 		},
+
+		{
+			[]string{"setuidhelper", "--minuid", "1", "--username", testUserName, "--workspace", "/hello",
+				"--logdir", "/logging", "--run", "/bin/veyron", "--dryrun", "--", "one", "two"},
+			[]string{"A=B"},
+			nil,
+			WorkParameters{
+				uid:       testUid,
+				gid:       testGid,
+				workspace: "/hello",
+				logDir:    "/logging",
+				argv0:     "/bin/veyron",
+				argv:      []string{"/bin/veyron", "one", "two"},
+				envv:      []string{"A=B"},
+				dryrun:    true,
+				remove:    false,
+			},
+		},
 	}
 
 	for _, c := range cases {
diff --git a/services/mgmt/suidhelper/impl/flag/flag.go b/services/mgmt/suidhelper/impl/flag/flag.go
index f8b53e1..273f56b 100644
--- a/services/mgmt/suidhelper/impl/flag/flag.go
+++ b/services/mgmt/suidhelper/impl/flag/flag.go
@@ -15,7 +15,7 @@
 var (
 	Username, Workspace, LogDir, Run *string
 	MinimumUid                       *int64
-	Remove                           *bool
+	Remove, Dryrun                   *bool
 )
 
 func init() {
@@ -29,6 +29,7 @@
 	Run = fs.String("run", "", "Path to the application to exec.")
 	MinimumUid = fs.Int64("minuid", uidThreshold, "UIDs cannot be less than this number.")
 	Remove = fs.Bool("rm", false, "Remove the file trees given as command-line arguments.")
+	Dryrun = fs.Bool("dryrun", false, "Elides root-requiring systemcalls.")
 }
 
 const uidThreshold = 501
diff --git a/services/mgmt/suidhelper/impl/system.go b/services/mgmt/suidhelper/impl/system.go
index 60a7b92..8a93d91 100644
--- a/services/mgmt/suidhelper/impl/system.go
+++ b/services/mgmt/suidhelper/impl/system.go
@@ -34,22 +34,37 @@
 }
 
 func (hw *WorkParameters) Exec() error {
+	attr := new(syscall.ProcAttr)
+
+	if dir, err := os.Getwd(); err != nil {
+		log.Printf("error Getwd(): %v\n", err)
+		return fmt.Errorf("os.Getwd failed: %v", err)
+		attr.Dir = dir
+	}
+	attr.Env = hw.envv
+
 	if hw.dryrun {
 		log.Printf("[dryrun] syscall.Setgid(%d)", hw.gid)
 		log.Printf("[dryrun] syscall.Setuid(%d)", hw.uid)
 	} else {
-		// NOTE(caprita): Commenting this out since it's broken with go
-		// 1.4, to make the integration test pass.  go/vcl/8240 will fix
-		// it properly.
-
-		// if err := syscall.Setgid(hw.gid); err != nil {
-		// 	return fmt.Errorf("syscall.Setgid(%d) failed: %v", hw.gid, err)
-		// }
-		// if err := syscall.Setuid(hw.uid); err != nil {
-		// 	return fmt.Errorf("syscall.Setuid(%d) failed: %v", hw.uid, err)
-		// }
+		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)
 	}
-	return syscall.Exec(hw.argv0, hw.argv, hw.envv)
+
+	_, _, err := syscall.StartProcess(hw.argv0, hw.argv, attr)
+	if err != nil {
+		if !hw.dryrun {
+			log.Printf("StartProcess failed: attr: %#v, attr.Sys: %#v, attr.Sys.Cred: %#v error: %v\n", attr, attr.Sys, attr.Sys.Credential, err)
+		} else {
+			log.Printf("StartProcess failed: %v", err)
+		}
+		return fmt.Errorf("syscall.StartProcess(%s) failed: %v", hw.argv0, err)
+	}
+	// TODO(rjkroege): Return the pid to the node manager.
+	os.Exit(0)
+	return nil // Not reached.
 }
 
 func (hw *WorkParameters) Remove() error {
diff --git a/tools/application/impl.go b/tools/application/impl.go
index 026f2c3..ef0bf9e 100644
--- a/tools/application/impl.go
+++ b/tools/application/impl.go
@@ -17,7 +17,9 @@
 	"v.io/lib/cmdline"
 )
 
-func getEnvelopeJSON(ctx *context.T, app repository.ApplicationClientMethods, profiles string) ([]byte, error) {
+func getEnvelopeJSON(app repository.ApplicationClientMethods, profiles string) ([]byte, error) {
+	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	defer cancel()
 	env, err := app.Match(ctx, strings.Split(profiles, ","))
 	if err != nil {
 		return nil, err
@@ -29,11 +31,13 @@
 	return j, nil
 }
 
-func putEnvelopeJSON(ctx *context.T, app repository.ApplicationClientMethods, profiles string, j []byte) error {
+func putEnvelopeJSON(app repository.ApplicationClientMethods, profiles string, j []byte) error {
 	var env application.Envelope
 	if err := json.Unmarshal(j, &env); err != nil {
 		return fmt.Errorf("Unmarshal(%v) failed: %v", string(j), err)
 	}
+	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	defer cancel()
 	if err := app.Put(ctx, strings.Split(profiles, ","), env); err != nil {
 		return err
 	}
@@ -66,9 +70,7 @@
 	}
 	name, profiles := args[0], args[1]
 	app := repository.ApplicationClient(name)
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
-	defer cancel()
-	j, err := getEnvelopeJSON(ctx, app, profiles)
+	j, err := getEnvelopeJSON(app, profiles)
 	if err != nil {
 		return err
 	}
@@ -81,29 +83,40 @@
 	Name:     "put",
 	Short:    "Add the given envelope to the application for the given profiles.",
 	Long:     "Add the given envelope to the application for the given profiles.",
-	ArgsName: "<application> <profiles> <envelope>",
+	ArgsName: "<application> <profiles> [<envelope>]",
 	ArgsLong: `
 <application> is the full name of the application.
 <profiles> is a comma-separated list of profiles.
-<envelope> is the file that contains a JSON-encoded envelope.`,
+<envelope> is the file that contains a JSON-encoded envelope. If this file is
+not provided, the user will be prompted to enter the data manually.`,
 }
 
 func runPut(cmd *cmdline.Command, args []string) error {
-	if expected, got := 3, len(args); expected != got {
-		return cmd.UsageErrorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
+	if got := len(args); got != 2 && got != 3 {
+		return cmd.UsageErrorf("put: incorrect number of arguments, expected 2 or 3, got %d", got)
 	}
-	name, profiles, envelope := args[0], args[1], args[2]
+	name, profiles := args[0], args[1]
 	app := repository.ApplicationClient(name)
-	j, err := ioutil.ReadFile(envelope)
-	if err != nil {
-		return fmt.Errorf("ReadFile(%v): %v", envelope, err)
+	if len(args) == 3 {
+		envelope := args[2]
+		j, err := ioutil.ReadFile(envelope)
+		if err != nil {
+			return fmt.Errorf("ReadFile(%v): %v", envelope, err)
+		}
+		if err = putEnvelopeJSON(app, profiles, j); err != nil {
+			return err
+		}
+		fmt.Fprintln(cmd.Stdout(), "Application envelope added successfully.")
+		return nil
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
-	defer cancel()
-	if err = putEnvelopeJSON(ctx, app, profiles, j); err != nil {
+	env := application.Envelope{Args: []string{}, Env: []string{}, Packages: map[string]string{}}
+	j, err := json.MarshalIndent(env, "", "  ")
+	if err != nil {
+		return fmt.Errorf("MarshalIndent() failed: %v", err)
+	}
+	if err := editAndPutEnvelopeJSON(cmd, app, profiles, j); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Application envelope added successfully.")
 	return nil
 }
 
@@ -150,6 +163,18 @@
 	}
 	name, profile := args[0], args[1]
 	app := repository.ApplicationClient(name)
+
+	envData, err := getEnvelopeJSON(app, profile)
+	if err != nil {
+		return err
+	}
+	if err := editAndPutEnvelopeJSON(cmd, app, profile, envData); err != nil {
+		return err
+	}
+	return nil
+}
+
+func editAndPutEnvelopeJSON(cmd *cmdline.Command, app repository.ApplicationClientMethods, profile string, envData []byte) error {
 	f, err := ioutil.TempFile("", "application-edit-")
 	if err != nil {
 		return fmt.Errorf("TempFile() failed: %v", err)
@@ -157,13 +182,6 @@
 	fileName := f.Name()
 	f.Close()
 	defer os.Remove(fileName)
-
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
-	defer cancel()
-	envData, err := getEnvelopeJSON(ctx, app, profile)
-	if err != nil {
-		return err
-	}
 	if err = ioutil.WriteFile(fileName, envData, os.FileMode(0644)); err != nil {
 		return err
 	}
@@ -191,7 +209,7 @@
 			fmt.Fprintln(cmd.Stdout(), "Nothing changed")
 			return nil
 		}
-		if err = putEnvelopeJSON(ctx, app, profile, newData); err != nil {
+		if err = putEnvelopeJSON(app, profile, newData); err != nil {
 			fmt.Fprintf(cmd.Stdout(), "Error: %v\n", err)
 			if ans := promptUser(cmd, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" {
 				continue