veyron/services/mgmt: integrate device manager with sysinit

This change adds support to the deviced binary to install/uninstall/start/stop
itself via the sysinit package (which in turn interfaces with upstart/sysinit).

In 'init mode', the install command creates a service description and registers
it with the system daemon manager.  Uninstall, Start, and Stop will then also
use sysinit to perform their respective duties.

Since sysinit operations need to be run as root, we introduce a new helper
(inithelper) analogous to suidhelper, which deviced calls when it needs to do
sysinit operations.

This change also makes some necessary changes to the sysinit package:
- setting the uid to 0 when euid is 0
- adding a New() api to create the appropriate InstallSystemInit object
- removing the setup function
- adding serialization/deserialization routines for ServiceDescription

Other changes lumped up with this CL, which could have been separated out into
their own CLs if we had less time pressure:

- managing stdout/stderr output from device manager and security agent, and
  ensuring we persist that in appropriate log dirs

- changing the exit code behavior of device manager to return 0 when a restart
  is not desired, and to non-zero if restart is desired; in particular, Stop now
  return 0 but Revert/Update/Suspend do not (nor do crashes obviously). This
  makes it easier to decide if device manager is meant to be restarted or not.

- adding a 'hack' to lib/signals to treat duplicate signals received by the
  process as one; this gets around the problem of deviced receiving a signal
  from its parent (the security agent) in addition to the signal it gets from
  e.g. a ^C or from upstart by virtue of being part of the process group lead by
  the agent.

Change-Id: I9f75577abb2258600b9b58bd141002bd2e05fac7
diff --git a/lib/signals/signals.go b/lib/signals/signals.go
index e2d64a6..564c6c6 100644
--- a/lib/signals/signals.go
+++ b/lib/signals/signals.go
@@ -4,6 +4,7 @@
 	"os"
 	"os/signal"
 	"syscall"
+	"time"
 
 	"v.io/core/veyron2"
 	"v.io/core/veyron2/context"
@@ -19,6 +20,25 @@
 	DoubleStopExitCode = 1
 )
 
+// TODO(caprita): Needless to say, this is a hack.  The motivator was getting
+// the device manager (run by the security agent) to shut down cleanly when the
+// process group containing both the agent and device manager receives a signal
+// (and the agent propagates that signal to the child).  We should be able to
+// finesse this by demonizing the device manager and/or being smarter about how
+// and when the agent sends the signal to the child.
+
+// SameSignalTimeWindow specifies the time window during which multiple
+// deliveries of the same signal are counted as one signal.  If set to zero, no
+// such de-duping occurs.  This is useful in situations where a process receives
+// a signal explicitly sent by its parent when the parent receives the signal,
+// but also receives it independently by virtue of being part of the same
+// process group.
+//
+// This is a variable, so that I can be set appropriately.  Note, there is no
+// locking around it, the assumption being that it's set during initialization
+// and never reset afterwards.
+var SameSignalTimeWindow time.Duration
+
 // defaultSignals returns a set of platform-specific signals that an application
 // is encouraged to listen on.
 func defaultSignals() []os.Signal {
@@ -68,11 +88,19 @@
 	go func() {
 		// First signal received.
 		sig := <-ch
+		sigTime := time.Now()
 		ret <- sig
 		// Wait for a second signal, and force an exit if the process is
 		// still executing cleanup code.
-		<-ch
-		os.Exit(DoubleStopExitCode)
+		for {
+			secondSig := <-ch
+			// If signal de-duping is enabled, ignore the signal if
+			// it's the same signal and has occured within the
+			// specified time window.
+			if SameSignalTimeWindow <= 0 || secondSig.String() != sig.String() || sigTime.Add(SameSignalTimeWindow).Before(time.Now()) {
+				os.Exit(DoubleStopExitCode)
+			}
+		}
 	}()
 	return ret
 }
diff --git a/services/mgmt/device/deviced/commands.go b/services/mgmt/device/deviced/commands.go
index edf5e95..62d3108 100644
--- a/services/mgmt/device/deviced/commands.go
+++ b/services/mgmt/device/deviced/commands.go
@@ -15,8 +15,10 @@
 	installFrom string
 	suidHelper  string
 	agent       string
+	initHelper  string
 	singleUser  bool
 	sessionMode bool
+	initMode    bool
 )
 
 const deviceDirEnv = "VANADIUM_DEVICE_DIR"
@@ -47,8 +49,10 @@
 	cmdInstall.Flags.StringVar(&installFrom, "from", "", "if specified, performs the installation from the provided application envelope object name")
 	cmdInstall.Flags.StringVar(&suidHelper, "suid_helper", "", "path to suid helper")
 	cmdInstall.Flags.StringVar(&agent, "agent", "", "path to security agent")
+	cmdInstall.Flags.StringVar(&initHelper, "init_helper", "", "path to sysinit helper")
 	cmdInstall.Flags.BoolVar(&singleUser, "single_user", false, "if set, performs the installation assuming a single-user system")
 	cmdInstall.Flags.BoolVar(&sessionMode, "session_mode", false, "if set, installs the device manager to run a single session. Otherwise, the device manager is configured to get restarted upon exit")
+	cmdInstall.Flags.BoolVar(&initMode, "init_mode", false, "if set, installs the device manager with the system init service manager")
 }
 
 func runInstall(cmd *cmdline.Command, args []string) error {
@@ -66,7 +70,10 @@
 	if agent == "" {
 		return cmd.UsageErrorf("--agent must be set")
 	}
-	if err := impl.SelfInstall(installationDir(), suidHelper, agent, singleUser, sessionMode, args, os.Environ()); err != nil {
+	if initMode && initHelper == "" {
+		return cmd.UsageErrorf("--init_helper must be set")
+	}
+	if err := impl.SelfInstall(installationDir(), suidHelper, agent, initHelper, singleUser, sessionMode, initMode, args, os.Environ(), cmd.Stderr(), cmd.Stdout()); err != nil {
 		vlog.Errorf("SelfInstall failed: %v", err)
 		return err
 	}
@@ -80,8 +87,8 @@
 	Long:  fmt.Sprintf("Removes the device manager installation from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
 }
 
-func runUninstall(*cmdline.Command, []string) error {
-	if err := impl.Uninstall(installationDir()); err != nil {
+func runUninstall(cmd *cmdline.Command, _ []string) error {
+	if err := impl.Uninstall(installationDir(), cmd.Stderr(), cmd.Stdout()); err != nil {
 		vlog.Errorf("Uninstall failed: %v", err)
 		return err
 	}
@@ -95,8 +102,8 @@
 	Long:  fmt.Sprintf("Starts the device manager installed under from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
 }
 
-func runStart(*cmdline.Command, []string) error {
-	if err := impl.Start(installationDir(), nil, nil); err != nil {
+func runStart(cmd *cmdline.Command, _ []string) error {
+	if err := impl.Start(installationDir(), cmd.Stderr(), cmd.Stdout()); err != nil {
 		vlog.Errorf("Start failed: %v", err)
 		return err
 	}
@@ -110,14 +117,14 @@
 	Long:  fmt.Sprintf("Stops the device manager installed under from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
 }
 
-func runStop(*cmdline.Command, []string) error {
+func runStop(cmd *cmdline.Command, _ []string) error {
 	runtime, err := rt.New()
 	if err != nil {
 		vlog.Errorf("Could not initialize runtime: %v", err)
 		return err
 	}
 	defer runtime.Cleanup()
-	if err := impl.Stop(runtime.NewContext(), installationDir()); err != nil {
+	if err := impl.Stop(runtime.NewContext(), installationDir(), cmd.Stderr(), cmd.Stdout()); err != nil {
 		vlog.Errorf("Stop failed: %v", err)
 		return err
 	}
diff --git a/services/mgmt/device/deviced/server.go b/services/mgmt/device/deviced/server.go
index 5ec30e7..69eef3a 100644
--- a/services/mgmt/device/deviced/server.go
+++ b/services/mgmt/device/deviced/server.go
@@ -2,6 +2,7 @@
 
 import (
 	"flag"
+	"time"
 
 	"v.io/lib/cmdline"
 
@@ -15,10 +16,10 @@
 )
 
 var (
-	// TODO(caprita): publishAs and stopExitCode should be provided by the
+	// TODO(caprita): publishAs and restartExitCode should be provided by the
 	// config?
-	publishAs    = flag.String("name", "", "name to publish the device manager at")
-	stopExitCode = flag.Int("stop_exit_code", 0, "exit code to return when stopped via the Stop RPC")
+	publishAs       = flag.String("name", "", "name to publish the device manager at")
+	restartExitCode = flag.Int("restart_exit_code", 0, "exit code to return when device manager should be restarted")
 )
 
 func runServer(*cmdline.Command, []string) error {
@@ -54,7 +55,7 @@
 	// implementation detail).
 
 	var exitErr error
-	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, func() { exitErr = cmdline.ErrExitCode(*stopExitCode) })
+	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, func() { exitErr = cmdline.ErrExitCode(*restartExitCode) })
 	if err != nil {
 		vlog.Errorf("Failed to create dispatcher: %v", err)
 		return err
@@ -66,7 +67,9 @@
 	vlog.VI(0).Infof("Device manager published as: %v", *publishAs)
 	impl.InvokeCallback(runtime.NewContext(), name)
 
-	// Wait until shutdown.
+	// Wait until shutdown.  Ignore duplicate signals (sent by agent and
+	// received as part of process group).
+	signals.SameSignalTimeWindow = 500 * time.Millisecond
 	<-signals.ShutdownOnSignals(ctx)
 
 	return exitErr
diff --git a/services/mgmt/device/impl/device_installer.go b/services/mgmt/device/impl/device_installer.go
index 15cf0ca..cfa0015 100644
--- a/services/mgmt/device/impl/device_installer.go
+++ b/services/mgmt/device/impl/device_installer.go
@@ -1,11 +1,39 @@
 package impl
 
+// The device installer logic is responsible for managing the device manager
+// server, including setting it up / tearing it down, and starting / stopping
+// it.
+//
+// When setting up the device manager installation, the installer creates a
+// directory structure from which the device manager can be run.  It sets up:
+//
+// <installDir>                - provided when installer is invoked
+//   dmroot/                   - created/owned by the installation
+//     device-manager/         - will be the root for the device manager server;
+//                               set as <Config.Root> (see comment in
+//                               device_service.go for what goes under here)
+//       base/                 - initial installation of device manager
+//         deviced             - link to deviced (self)
+//         deviced.sh          - script to start the device manager
+//       logs/                 - device manager logs will go here
+//     current                 - set as <Config.CurrentLink>
+//     agent_deviced.sh        - script to launch device manager under agent
+//     security/               - security agent keeps credentials here
+//       keys/
+//       principal/
+//     agent_logs/             - security agent logs
+//       STDERR-<timestamp>
+//       STDOUT-<timestamp>
+//     service_description     - json-encoded sysinit device manager config
+//     inithelper              - soft link to init helper
+
 import (
 	"fmt"
 	"io"
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"os/user"
 	"path/filepath"
 	"regexp"
 	"strings"
@@ -16,12 +44,14 @@
 
 	"v.io/core/veyron/lib/flags/consts"
 	"v.io/core/veyron/services/mgmt/device/config"
+	"v.io/core/veyron/services/mgmt/sysinit"
 )
 
-// stopExitCode is the exit code that the device manager should return when it
-// gets a Stop RPC.  This number is picked quasi-arbitrarily from the set of
+// restartExitCode is the exit code that the device manager should return when it
+// wants to be restarted by its parent (i.e., the security agent).
+// This number is picked quasi-arbitrarily from the set of
 // exit codes without prior special meanings.
-const stopExitCode = 140
+const restartExitCode = 140
 
 // dmRoot is the directory name where the device manager installs itself.
 const dmRoot = "dmroot"
@@ -62,9 +92,34 @@
 	return filterEnvironment(env, allowedVarsRE, deniedVarsRE)
 }
 
+// initCommand verifies if init mode is enabled, and if so executes the
+// appropriate sysinit command.  Returns whether init mode was detected, as well
+// as any error encountered.
+func initCommand(root, command string, stderr, stdout io.Writer) (bool, error) {
+	sdFile := filepath.Join(root, "service_description")
+	if _, err := os.Stat(sdFile); err != nil {
+		if os.IsNotExist(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("Stat(%v) failed: %v", sdFile, err)
+	}
+	helperLink := filepath.Join(root, "inithelper")
+	cmd := exec.Command(helperLink, fmt.Sprintf("--service_description=%s", sdFile), command)
+	if stderr != nil {
+		cmd.Stderr = stderr
+	}
+	if stdout != nil {
+		cmd.Stdout = stdout
+	}
+	if err := cmd.Run(); err != nil {
+		return true, fmt.Errorf("Running init helper %v failed: %v", command, err)
+	}
+	return true, nil
+}
+
 // SelfInstall installs the device manager and configures it using the
 // environment and the supplied command-line flags.
-func SelfInstall(installDir, suidHelper, agent string, singleUser, sessionMode bool, args, env []string) error {
+func SelfInstall(installDir, suidHelper, agent, initHelper string, singleUser, sessionMode, init bool, args, env []string, stderr, stdout io.Writer) error {
 	root := filepath.Join(installDir, dmRoot)
 	if _, err := os.Stat(root); err == nil || !os.IsNotExist(err) {
 		return fmt.Errorf("%v already exists", root)
@@ -89,7 +144,7 @@
 		extraArgs = append(extraArgs, fmt.Sprintf("--name=%q", name))
 	}
 	if !sessionMode {
-		extraArgs = append(extraArgs, fmt.Sprintf("--stop_exit_code=%d", stopExitCode))
+		extraArgs = append(extraArgs, fmt.Sprintf("--restart_exit_code=%d", restartExitCode))
 	}
 	envelope := &application.Envelope{
 		Args: append(extraArgs, args...),
@@ -106,7 +161,8 @@
 	if err != nil {
 		return fmt.Errorf("failed to serialize config %v: %v", configState, err)
 	}
-	if err := generateScript(deviceDir, configSettings, envelope); err != nil {
+	logs := filepath.Join(root, "device-manager", "logs")
+	if err := generateScript(deviceDir, configSettings, envelope, logs); err != nil {
 		return err
 	}
 
@@ -115,8 +171,37 @@
 		return err
 	}
 
-	return generateAgentScript(root, agent, currLink, singleUser, sessionMode)
-	// TODO(caprita): Update system management daemon.
+	if err := generateAgentScript(root, agent, currLink, singleUser, sessionMode); err != nil {
+		return err
+	}
+	if init {
+		agentScript := filepath.Join(root, "agent_deviced.sh")
+		currentUser, err := user.Current()
+		if err != nil {
+			return err
+		}
+		sd := &sysinit.ServiceDescription{
+			Service:     "deviced",
+			Description: "Vanadium Device Manager",
+			Binary:      agentScript,
+			Command:     []string{agentScript},
+			User:        currentUser.Username,
+		}
+		sdFile := filepath.Join(root, "service_description")
+		if err := sd.SaveTo(sdFile); err != nil {
+			return fmt.Errorf("SaveTo for %v failed: %v", sd, err)
+		}
+		helperLink := filepath.Join(root, "inithelper")
+		if err := os.Symlink(initHelper, helperLink); err != nil {
+			return fmt.Errorf("Symlink(%v, %v) failed: %v", initHelper, helperLink, err)
+		}
+		if initMode, err := initCommand(root, "install", stderr, stdout); err != nil {
+			return err
+		} else if !initMode {
+			return fmt.Errorf("enabling init mode failed")
+		}
+	}
+	return nil
 }
 
 func generateAgentScript(workspace, agent, currLink string, singleUser, sessionMode bool) error {
@@ -130,8 +215,13 @@
 	if err := os.MkdirAll(keyDir, perm); err != nil {
 		return fmt.Errorf("MkdirAll(%v, %v) failed: %v", keyDir, perm, err)
 	}
+	logs := filepath.Join(workspace, "agent_logs")
+	if err := os.MkdirAll(logs, perm); err != nil {
+		return fmt.Errorf("MkdirAll(%v, %v) failed: %v", logs, perm, err)
+	}
 	// TODO(caprita): Switch all our generated bash scripts to use templates.
 	output := "#!/bin/bash\n"
+	output += fmt.Sprintln("readonly TIMESTAMP=$(date +%s%N)")
 	output += fmt.Sprintf("%s=%q ", consts.VeyronCredentials, principalDir)
 	// Escape the path to the binary; %q uses Go-syntax escaping, but it's
 	// close enough to Bash that we're using it as an approximation.
@@ -143,10 +233,13 @@
 		output += "--no_passphrase "
 	}
 	if !sessionMode {
-		output += fmt.Sprintf("--restart_exit_code=!%d ", stopExitCode)
+		output += fmt.Sprintf("--restart_exit_code=!0 ")
 	}
 	output += fmt.Sprintf("--additional_principals=%q ", keyDir)
-	output += fmt.Sprintf("%q\n", currLink)
+	stdoutLog, stderrLog := filepath.Join(logs, "STDERR"), filepath.Join(logs, "STDOUT")
+	// Write stdout and stderr both to the standard streams, and also to
+	// timestamped files.
+	output += fmt.Sprintf("%q > >(tee %s-$TIMESTAMP) 2> >(tee %s-$TIMESTAMP >&2)\n", currLink, stdoutLog, stderrLog)
 	path := filepath.Join(workspace, "agent_deviced.sh")
 	if err := ioutil.WriteFile(path, []byte(output), 0700); err != nil {
 		return fmt.Errorf("WriteFile(%v) failed: %v", path, err)
@@ -157,16 +250,18 @@
 
 // Uninstall undoes SelfInstall, removing the device manager's installation
 // directory.
-func Uninstall(installDir string) error {
+func Uninstall(installDir string, stdout, stderr io.Writer) error {
 	// TODO(caprita): ensure device is stopped?
 
 	root := filepath.Join(installDir, dmRoot)
+	if _, err := initCommand(root, "uninstall", stdout, stderr); err != nil {
+		return err
+	}
 	// TODO(caprita): Use suidhelper to delete dirs/files owned by other
 	// users under the app work dirs.
 	if err := os.RemoveAll(root); err != nil {
 		return fmt.Errorf("RemoveAll(%v) failed: %v", root, err)
 	}
-	// TODO(caprita): Update system management daemon.
 	return nil
 }
 
@@ -175,7 +270,13 @@
 	// TODO(caprita): make sure it's not already running?
 
 	root := filepath.Join(installDir, dmRoot)
-	// TODO(caprita): In non-session mode, use system management daemon.
+
+	if initMode, err := initCommand(root, "start", stderr, stdout); err != nil {
+		return err
+	} else if initMode {
+		return nil
+	}
+
 	agentScript := filepath.Join(root, "agent_deviced.sh")
 	cmd := exec.Command(agentScript)
 	if stderr != nil {
@@ -191,8 +292,13 @@
 }
 
 // Stop stops the device manager.
-func Stop(ctx *context.T, installDir string) error {
+func Stop(ctx *context.T, installDir string, stderr, stdout io.Writer) error {
 	root := filepath.Join(installDir, dmRoot)
+	if initMode, err := initCommand(root, "stop", stderr, stdout); err != nil {
+		return err
+	} else if initMode {
+		return nil
+	}
 	info, err := loadManagerInfo(filepath.Join(root, "device-manager"))
 	if err != nil {
 		return fmt.Errorf("loadManagerInfo failed: %v", err)
@@ -201,7 +307,5 @@
 		return fmt.Errorf("Stop failed: %v", err)
 	}
 	// TODO(caprita): Wait for the (device|agent) process to be gone.
-
-	// TODO(caprita): In non-session mode, use system management daemon.
 	return nil
 }
diff --git a/services/mgmt/device/impl/device_service.go b/services/mgmt/device/impl/device_service.go
index 02d1113..49ca261 100644
--- a/services/mgmt/device/impl/device_service.go
+++ b/services/mgmt/device/impl/device_service.go
@@ -8,6 +8,9 @@
 //   device-manager/
 //     info                    - metadata for the device manager (such as object
 //                               name and process id)
+//     logs/                   - device manager logs
+//       STDERR-<timestamp>    - one for each execution of device manager
+//       STDOUT-<timestamp>    - one for each execution of device manager
 //     <version 1 timestamp>/  - timestamp of when the version was downloaded
 //       deviced               - the device manager binary
 //       deviced.sh            - a shell script to start the binary
@@ -90,12 +93,12 @@
 
 // deviceService implements the Device manager's Device interface.
 type deviceService struct {
-	updating    *updatingState
-	stopHandler func()
-	callback    *callbackState
-	config      *config.State
-	disp        *dispatcher
-	uat         BlessingSystemAssociationStore
+	updating       *updatingState
+	restartHandler func()
+	callback       *callbackState
+	config         *config.State
+	disp           *dispatcher
+	uat            BlessingSystemAssociationStore
 }
 
 // managerInfo holds state about a running device manager.
@@ -196,6 +199,9 @@
 	if err := updateLink(s.config.Previous, s.config.CurrentLink); err != nil {
 		return err
 	}
+	if s.restartHandler != nil {
+		s.restartHandler()
+	}
 	veyron2.GetAppCycle(ctx).Stop()
 	return nil
 }
@@ -311,7 +317,7 @@
 
 // TODO(caprita): Move this to util.go since device_installer is also using it now.
 
-func generateScript(workspace string, configSettings []string, envelope *application.Envelope) error {
+func generateScript(workspace string, configSettings []string, envelope *application.Envelope, logs string) error {
 	// TODO(caprita): Remove this snippet of code, it doesn't seem to serve
 	// any purpose.
 	path, err := filepath.EvalSymlinks(os.Args[0])
@@ -321,6 +327,7 @@
 	}
 
 	output := "#!/bin/bash\n"
+	output += fmt.Sprintln("readonly TIMESTAMP=$(date +%s%N)")
 	output += strings.Join(config.QuoteEnv(append(envelope.Env, configSettings...)), " ") + " "
 	// Escape the path to the binary; %q uses Go-syntax escaping, but it's
 	// close enough to Bash that we're using it as an approximation.
@@ -329,7 +336,14 @@
 	// veyron/tools/debug/impl.go) instead.
 	output += fmt.Sprintf("exec %q", filepath.Join(workspace, "deviced")) + " "
 	output += strings.Join(envelope.Args, " ")
-	output += "\n"
+	if err := os.MkdirAll(logs, 0700); err != nil {
+		vlog.Errorf("MkdirAll(%v) failed: %v", logs, err)
+		return verror2.Make(ErrOperationFailed, nil)
+	}
+	stderrLog, stdoutLog := filepath.Join(logs, "STDERR"), filepath.Join(logs, "STDOUT")
+	// Write stdout and stderr both to the standard streams, and also to
+	// timestamped files.
+	output += fmt.Sprintf(" > >(tee %s-$TIMESTAMP) 2> >(tee %s-$TIMESTAMP >&2)\n", stdoutLog, stderrLog)
 	path = filepath.Join(workspace, "deviced.sh")
 	if err := ioutil.WriteFile(path, []byte(output), 0700); err != nil {
 		vlog.Errorf("WriteFile(%v) failed: %v", path, err)
@@ -389,7 +403,8 @@
 		return verror2.Make(ErrOperationFailed, ctx)
 	}
 
-	if err := generateScript(workspace, configSettings, envelope); err != nil {
+	logs := filepath.Join(s.config.Root, "device-manager", "logs")
+	if err := generateScript(workspace, configSettings, envelope, logs); err != nil {
 		return err
 	}
 
@@ -401,6 +416,9 @@
 		return err
 	}
 
+	if s.restartHandler != nil {
+		s.restartHandler()
+	}
 	veyron2.GetAppCycle(ctx).Stop()
 	deferrer = nil
 	return nil
@@ -443,15 +461,17 @@
 	return nil, verror2.Make(ErrInvalidSuffix, ctx.Context())
 }
 
-func (s *deviceService) Stop(call ipc.ServerContext, _ uint32) error {
-	if s.stopHandler != nil {
-		s.stopHandler()
-	}
+func (*deviceService) Stop(call ipc.ServerContext, _ uint32) error {
 	veyron2.GetAppCycle(call.Context()).Stop()
 	return nil
 }
 
-func (*deviceService) Suspend(call ipc.ServerContext) error {
+func (s *deviceService) Suspend(call ipc.ServerContext) error {
+	// TODO(caprita): move this to Restart and disable Suspend for device
+	// manager?
+	if s.restartHandler != nil {
+		s.restartHandler()
+	}
 	veyron2.GetAppCycle(call.Context()).Stop()
 	return nil
 }
diff --git a/services/mgmt/device/impl/dispatcher.go b/services/mgmt/device/impl/dispatcher.go
index dabfcbc..eae1fdd 100644
--- a/services/mgmt/device/impl/dispatcher.go
+++ b/services/mgmt/device/impl/dispatcher.go
@@ -32,10 +32,10 @@
 // internalState wraps state shared between different device manager
 // invocations.
 type internalState struct {
-	callback      *callbackState
-	updating      *updatingState
-	securityAgent *securityAgentState
-	stopHandler   func()
+	callback       *callbackState
+	updating       *updatingState
+	securityAgent  *securityAgentState
+	restartHandler func()
 }
 
 // aclLocks provides a mutex lock for each acl file path.
@@ -85,7 +85,7 @@
 )
 
 // NewDispatcher is the device manager dispatcher factory.
-func NewDispatcher(principal security.Principal, config *config.State, stopHandler func()) (*dispatcher, error) {
+func NewDispatcher(principal security.Principal, config *config.State, restartHandler func()) (*dispatcher, error) {
 	if err := config.Validate(); err != nil {
 		return nil, fmt.Errorf("invalid config %v: %v", config, err)
 	}
@@ -106,9 +106,9 @@
 	d := &dispatcher{
 		etag: "default",
 		internal: &internalState{
-			callback:    newCallbackState(config.Name),
-			updating:    newUpdatingState(),
-			stopHandler: stopHandler,
+			callback:       newCallbackState(config.Name),
+			updating:       newUpdatingState(),
+			restartHandler: restartHandler,
 		},
 		config: config,
 		uat:    uat,
@@ -364,12 +364,12 @@
 	switch components[0] {
 	case deviceSuffix:
 		receiver := device.DeviceServer(&deviceService{
-			callback:    d.internal.callback,
-			updating:    d.internal.updating,
-			stopHandler: d.internal.stopHandler,
-			config:      d.config,
-			disp:        d,
-			uat:         d.uat,
+			callback:       d.internal.callback,
+			updating:       d.internal.updating,
+			restartHandler: d.internal.restartHandler,
+			config:         d.config,
+			disp:           d,
+			uat:            d.uat,
 		})
 		return receiver, d.auth, nil
 	case appsSuffix:
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index d509be9..ad98597 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -187,7 +187,7 @@
 		}
 		configState.Root, configState.Helper, configState.Origin, configState.CurrentLink = args[0], args[1], args[2], args[3]
 	}
-	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(globalCtx), configState, func() { fmt.Println("stop handler") })
+	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(globalCtx), configState, func() { fmt.Println("restart handler") })
 	if err != nil {
 		vlog.Fatalf("Failed to create device manager dispatcher: %v", err)
 	}
@@ -400,6 +400,7 @@
 
 	dmh.CloseStdin()
 
+	dms.Expect("restart handler")
 	dms.Expect("factoryDM terminating")
 	dmh.Shutdown(os.Stderr, os.Stderr)
 
@@ -431,6 +432,7 @@
 		t.Fatalf("current link didn't change")
 	}
 
+	dms.Expect("restart handler")
 	dms.Expect("v2DM terminating")
 
 	dmh.Shutdown(os.Stderr, os.Stderr)
@@ -449,6 +451,7 @@
 	revertDevice(t, "v3DM")
 	revertDeviceExpectError(t, "v3DM", impl.ErrOperationInProgress.ID) // Revert already in progress.
 	dmh.CloseStdin()
+	dms.Expect("restart handler")
 	dms.Expect("v3DM terminating")
 	if evalLink() != scriptPathV2 {
 		t.Fatalf("current link was not reverted correctly")
@@ -463,6 +466,7 @@
 
 	// Revert the device manager to its previous version (factory).
 	revertDevice(t, "v2DM")
+	dms.Expect("restart handler")
 	dms.Expect("v2DM terminating")
 	if evalLink() != scriptPathFactory {
 		t.Fatalf("current link was not reverted correctly")
@@ -475,7 +479,6 @@
 	mgmttest.ReadPID(t, dms)
 	resolve(t, "factoryDM", 1) // Current link should have been launching factory version.
 	stopDevice(t, "factoryDM")
-	dms.Expect("stop handler")
 	dms.Expect("factoryDM terminating")
 	dms.ExpectEOF()
 
@@ -485,6 +488,7 @@
 	mgmttest.ReadPID(t, dms)
 	resolve(t, "factoryDM", 1)
 	suspendDevice(t, "factoryDM")
+	dms.Expect("restart handler")
 	dms.Expect("factoryDM terminating")
 	dms.ExpectEOF()
 }
@@ -949,17 +953,19 @@
 	defer cleanup()
 
 	// Create a script wrapping the test target that implements suidhelper.
-	helperPath := generateSuidHelperScript(t, testDir)
+	suidHelperPath := generateSuidHelperScript(t, testDir)
 	// Create a dummy script mascarading as the security agent.
 	agentPath := generateAgentScript(t, testDir)
+	initHelperPath := ""
 
 	// Create an 'envelope' for the device manager that we can pass to the
 	// installer, to ensure that the device manager that the installer
 	// configures can run.
 	dmargs, dmenv := sh.CommandEnvelope(deviceManagerCmd, nil, "dm")
 	dmDir := filepath.Join(testDir, "dm")
-	singleUser, sessionMode := true, true
-	if err := impl.SelfInstall(dmDir, helperPath, agentPath, singleUser, sessionMode, dmargs[1:], dmenv); err != nil {
+	// TODO(caprita): Add test logic when initMode = true.
+	singleUser, sessionMode, initMode := true, true, false
+	if err := impl.SelfInstall(dmDir, suidHelperPath, agentPath, initHelperPath, singleUser, sessionMode, initMode, dmargs[1:], dmenv, os.Stderr, os.Stdout); err != nil {
 		t.Fatalf("SelfInstall failed: %v", err)
 	}
 
@@ -975,14 +981,13 @@
 	revertDeviceExpectError(t, "dm", impl.ErrUpdateNoOp.ID) // No previous version available.
 
 	// Stop the device manager.
-	if err := impl.Stop(globalCtx, dmDir); err != nil {
+	if err := impl.Stop(globalCtx, dmDir, os.Stderr, os.Stdout); err != nil {
 		t.Fatalf("Stop failed: %v", err)
 	}
-	dms.Expect("stop handler")
 	dms.Expect("dm terminating")
 
 	// Uninstall.
-	if err := impl.Uninstall(dmDir); err != nil {
+	if err := impl.Uninstall(dmDir, os.Stderr, os.Stdout); err != nil {
 		t.Fatalf("Uninstall failed: %v", err)
 	}
 	// Ensure that the installation is gone.
diff --git a/services/mgmt/inithelper/main.go b/services/mgmt/inithelper/main.go
new file mode 100644
index 0000000..a6f3fb4
--- /dev/null
+++ b/services/mgmt/inithelper/main.go
@@ -0,0 +1,98 @@
+package main
+
+// TODO(caprita): The separation of responsibilities between root/non-root code
+// can be shifted away from root a bit more, by having the init daemon files
+// created as non-root and root just installs files and runs a specific set of
+// commands.  Figure out if worth it.  Also consider combining with suidhelper.
+// For now they're separate since we don't always need both at the same time.
+
+// TODO(caprita): Add unit test.
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"os"
+
+	"v.io/core/veyron/services/mgmt/sysinit"
+)
+
+func usage() {
+	const usage = `Usage:
+%s [flags] [command]
+
+ Flags:
+%s
+ Command:
+  print: prints the file that would be installed
+  install: installs the service
+  uninstall: uninstalls the service
+  start: starts the service
+  stop: stops the service
+`
+	var flagDefaults bytes.Buffer
+	flag.CommandLine.SetOutput(&flagDefaults)
+	flag.CommandLine.PrintDefaults()
+	flag.CommandLine.SetOutput(nil)
+	fmt.Fprintf(os.Stderr, usage, os.Args[0], flagDefaults.String())
+}
+
+func main() {
+	flag.Usage = usage
+	sdFlag := flag.String("service_description", "", "File containing a JSON-encoded sysinit.ServiceDescription object.")
+	systemFlag := flag.String("system", sysinit.InitSystem(), "System label, to select the appropriate sysinit mechanism.")
+	flag.Parse()
+	if *sdFlag == "" {
+		fmt.Fprintf(os.Stderr, "--service_description must be set.\n")
+		flag.Usage()
+		os.Exit(1)
+	}
+	if *systemFlag == "" {
+		fmt.Fprintf(os.Stderr, "--system must be set.\n")
+		flag.Usage()
+		os.Exit(1)
+	}
+	var sd sysinit.ServiceDescription
+	if err := sd.LoadFrom(*sdFlag); err != nil {
+		fmt.Fprintf(os.Stderr, "LoadFrom(%v) failed: %v\n", *sdFlag, err)
+		os.Exit(2)
+	}
+	si := sysinit.New(*systemFlag, &sd)
+	args := flag.Args()
+	if len(args) != 1 {
+		fmt.Fprintf(os.Stderr, "Command must be specified.\n")
+		flag.Usage()
+		os.Exit(1)
+	}
+	switch args[0] {
+	case "print":
+		if err := si.Print(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to print %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	case "install":
+		if err := si.Install(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to install %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	case "uninstall":
+		if err := si.Uninstall(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to uninstall %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	case "start":
+		if err := si.Start(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to start %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	case "stop":
+		if err := si.Stop(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to stop %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	default:
+		fmt.Fprintf(os.Stderr, "Invalid command: %s\n", args[0])
+		flag.Usage()
+		os.Exit(1)
+	}
+}
diff --git a/services/mgmt/sysinit/init_darwin.go b/services/mgmt/sysinit/init_darwin.go
new file mode 100644
index 0000000..e56e07f
--- /dev/null
+++ b/services/mgmt/sysinit/init_darwin.go
@@ -0,0 +1,11 @@
+package sysinit
+
+func InitSystem() string {
+	panic("Darwin not supported yet")
+	return ""
+}
+
+func New(system string, sd *ServiceDescription) InstallSystemInit {
+	panic("Darwin not supported yet")
+	return nil
+}
diff --git a/services/mgmt/sysinit/init_linux.go b/services/mgmt/sysinit/init_linux.go
index beacc8e..e6da324 100644
--- a/services/mgmt/sysinit/init_linux.go
+++ b/services/mgmt/sysinit/init_linux.go
@@ -1,6 +1,3 @@
-//
-// +build linux
-
 package sysinit
 
 // TODO(cnicolaou): will need to figure out a simple of way of handling the
@@ -13,22 +10,35 @@
 	"log"
 	"os"
 	"os/exec"
+	"syscall"
 )
 
 // action is a var so we can override it for testing.
 var action = func(command, action, service string) error {
-	output, err := exec.Command(command, action, service).CombinedOutput()
-	log.Printf("%s output: for %s %s: %s\n",
-		command, action, service, output)
+	cmd := exec.Command(command, action, service)
+	if os.Geteuid() == 0 && os.Getuid() > 0 {
+		// Set uid to root (e.g. when running from a suid binary),
+		// otherwise initctl doesn't work.
+		sysProcAttr := new(syscall.SysProcAttr)
+		sysProcAttr.Credential = new(syscall.Credential)
+		sysProcAttr.Credential.Gid = uint32(0)
+		sysProcAttr.Credential.Uid = uint32(0)
+		cmd.SysProcAttr = sysProcAttr
+	}
+	// Clear env.  In particular, initctl doesn't like USER being set to
+	// something other than root.
+	cmd.Env = []string{}
+	output, err := cmd.CombinedOutput()
+	log.Printf("%s output: for %s %s: %s\n", command, action, service, output)
 	return err
 }
 
 var (
 	upstartDir        = "/etc/init"
+	upstartBin        = "/sbin/initctl"
 	systemdDir        = "/usr/lib/systemd/system"
 	systemdTmpFileDir = "/usr/lib/tmpfiles.d"
 	dockerDir         = "/home/veyron/init"
-	logDir            = "/var/log/veyron"
 )
 
 // InitSystem attempts to determine what kind of init system is in use on
@@ -46,7 +56,7 @@
 	if fi, err := os.Stat("/home/veyron/init"); err == nil && fi.Mode().IsDir() {
 		return "docker"
 	}
-	if fi, err := os.Stat("/sbin/initctl"); err == nil {
+	if fi, err := os.Stat(upstartBin); err == nil {
 		if (fi.Mode() & os.ModePerm & 0100) != 0 {
 			return "upstart"
 		}
@@ -64,6 +74,21 @@
 	return ""
 }
 
+// New returns the appropriate implementation of InstallSystemInit for the
+// underlying system.
+func New(system string, sd *ServiceDescription) InstallSystemInit {
+	switch system {
+	case "docker":
+		return (*DockerService)(sd)
+	case "upstart":
+		return (*UpstartService)(sd)
+	case "systemd":
+		return (*SystemdService)(sd)
+	default:
+		return nil
+	}
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Upstart support
 
@@ -101,19 +126,13 @@
 
 script
   set -e
-  #  setuid {{.User}} - causes the subsequent exec to fail for unknown reasons.
   echo '{{.Service}} starting'
-  exec{{range $cmd := .Command}} {{$cmd}}{{end}}
+  exec sudo -u {{.User}} {{range $cmd := .Command}} {{$cmd}}{{end}}
 end script
 `
 
-// Install implements the InstallSystemInit method.
+// Implements the InstallSystemInit method.
 func (u *UpstartService) Install() error {
-	if u.Setup != nil {
-		if err := u.Setup((*ServiceDescription)(u)); err != nil {
-			return err
-		}
-	}
 	file := fmt.Sprintf("%s/%s.conf", upstartDir, u.Service)
 	return (*ServiceDescription)(u).writeTemplate(upstartTemplate, file)
 }
@@ -123,23 +142,24 @@
 	return (*ServiceDescription)(u).writeTemplate(upstartTemplate, "")
 }
 
-// Uninstall implements the InstallSystemInit method.
+// Implements the InstallSystemInit method.
 func (u *UpstartService) Uninstall() error {
-	if err := u.Stop(); err != nil {
-		return err
-	}
+	// For now, ignore any errors returned by Stop, since Stop complains
+	// when there is no instance to stop.
+	// TODO(caprita): Only call Stop if there are running instances.
+	u.Stop()
 	file := fmt.Sprintf("%s/%s.conf", upstartDir, u.Service)
 	return os.Remove(file)
 }
 
 // Start implements the InstallSystemInit method.
 func (u *UpstartService) Start() error {
-	return action("initctl", "start", u.Service)
+	return action(upstartBin, "start", u.Service)
 }
 
 // Stop implements the InstallSystemInit method.
 func (u *UpstartService) Stop() error {
-	return action("initctl", "stop", u.Service)
+	return action(upstartBin, "stop", u.Service)
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -174,11 +194,6 @@
 
 // Install implements the InstallSystemInit method.
 func (s *SystemdService) Install() error {
-	if s.Setup != nil {
-		if err := s.Setup((*ServiceDescription)(s)); err != nil {
-			return err
-		}
-	}
 	file := fmt.Sprintf("%s/%s.service", systemdDir, s.Service)
 	if err := (*ServiceDescription)(s).writeTemplate(systemdTemplate, file); err != nil {
 		return err
@@ -253,11 +268,6 @@
 
 // Install implements the InstallSystemInit method.
 func (s *DockerService) Install() error {
-	if s.Setup != nil {
-		if err := s.Setup((*ServiceDescription)(s)); err != nil {
-			return err
-		}
-	}
 	file := fmt.Sprintf("%s/%s.sh", dockerDir, s.Service)
 	if err := (*ServiceDescription)(s).writeTemplate(dockerTemplate, file); err != nil {
 		return err
diff --git a/services/mgmt/sysinit/linux_test.go b/services/mgmt/sysinit/linux_test.go
index a926229..d21634e 100644
--- a/services/mgmt/sysinit/linux_test.go
+++ b/services/mgmt/sysinit/linux_test.go
@@ -30,23 +30,15 @@
 	}()
 
 	defer os.RemoveAll(upstartDir)
-	serviceName := ""
 	u := &UpstartService{
 		Service:     "tester",
 		Description: "my test",
 		Binary:      "/bin/echo",
 		Command:     []string{"/bin/echo -n foo"},
-		Setup: func(s *ServiceDescription) error {
-			serviceName = s.Service
-			return nil
-		},
 	}
 	if err := u.Install(); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
-	if serviceName != "tester" {
-		t.Errorf("Setup was not called")
-	}
 	rc, _ := os.Open(upstartDir + "/tester.conf")
 	lines := bufio.NewScanner(rc)
 	lines.Scan()
diff --git a/services/mgmt/sysinit/service_description.go b/services/mgmt/sysinit/service_description.go
index 925c8c1..ba1150d 100644
--- a/services/mgmt/sysinit/service_description.go
+++ b/services/mgmt/sysinit/service_description.go
@@ -1,6 +1,9 @@
 package sysinit
 
 import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
 	"os"
 	"text/template"
 	"time"
@@ -11,14 +14,37 @@
 // ServiceDescription is a generic service description that represents the
 // common configuration details for specific systems.
 type ServiceDescription struct {
-	Service     string                          // The name of the Service
-	Description string                          // A description of the Service
-	Environment map[string]string               // Environment variables needed by the service
-	Binary      string                          // The binary to be run
-	Command     []string                        // The script/binary and command line options to use to start/stop the binary
-	User        string                          // The username this service is to run as
-	Setup       func(*ServiceDescription) error // Optional function to run before install.
-	SetupParams map[string]string               // Params for the Setup function.
+	Service     string            // The name of the Service
+	Description string            // A description of the Service
+	Environment map[string]string // Environment variables needed by the service
+	Binary      string            // The binary to be run
+	Command     []string          // The script/binary and command line options to use to start/stop the binary
+	User        string            // The username this service is to run as
+}
+
+// TODO(caprita): Unit test.
+
+// SaveTo serializes the service description object to a file.
+func (sd *ServiceDescription) SaveTo(fName string) error {
+	jsonSD, err := json.Marshal(sd)
+	if err != nil {
+		return fmt.Errorf("Marshal(%v) failed: %v", sd, err)
+	}
+	if err := ioutil.WriteFile(fName, jsonSD, 0600); err != nil {
+		return fmt.Errorf("WriteFile(%v) failed: %v", fName, err)
+	}
+	return nil
+}
+
+// LoadFrom de-serializes the service description object from a file created by
+// SaveTo.
+func (sd *ServiceDescription) LoadFrom(fName string) error {
+	if sdBytes, err := ioutil.ReadFile(fName); err != nil {
+		return fmt.Errorf("ReadFile(%v) failed: %v", fName, err)
+	} else if err := json.Unmarshal(sdBytes, sd); err != nil {
+		return fmt.Errorf("Unmarshal(%v) failed: %v", sdBytes, err)
+	}
+	return nil
 }
 
 func (sd *ServiceDescription) writeTemplate(templateContents, file string) error {
diff --git a/tools/mgmt/device/devicex b/tools/mgmt/device/devicex
index 813d842..17fc8ea 100755
--- a/tools/mgmt/device/devicex
+++ b/tools/mgmt/device/devicex
@@ -29,7 +29,7 @@
   echo "VANADIUM_DEVICE_DIR=<installation dir> ./devicex stop"
 }
 
-readonly BIN_NAMES=(deviced suidhelper agentd)
+readonly BIN_NAMES=(deviced suidhelper agentd inithelper)
 
 ###############################################################################
 # Copies one binary from source to destination.
@@ -153,25 +153,31 @@
   echo "Binaries are in ${BIN_INSTALL}."
 
   # Set up the suidhelper.
-  echo "Configuring suidhelper ..."
-  local -r SETUID_SCRIPT="${BIN_INSTALL}/suidhelper"
+  echo "Configuring helpers ..."
   local SINGLE_USER=false
+  local INIT_MODE=false
   for ARG in $*; do
     if [[ ${ARG} = "--" ]]; then
       break
-    elif [[ ${ARG} = "--single_user" ]]; then
+    elif [[ ${ARG} = "--single_user" || ${ARG} = "--single_user=true" ]]; then
       SINGLE_USER=true
-      break
+    elif [[ ${ARG} = "--init_mode" || ${ARG} = "--init_mode=true" ]]; then
+      INIT_MODE=true
     fi
   done
+  local -r SETUID_SCRIPT="${BIN_INSTALL}/suidhelper"
   if [[ ${SINGLE_USER} == false ]]; then
     sudo bash -c "chown root:root \"${SETUID_SCRIPT}\"; chmod 4551 \"${SETUID_SCRIPT}\""
   fi
-  echo "Suidhelper configured."
+  local -r INIT_SCRIPT="${BIN_INSTALL}/inithelper"
+  if [[ ${INIT_MODE} == true ]]; then
+    sudo bash -c "chown root:root \"${INIT_SCRIPT}\"; chmod 4551 \"${INIT_SCRIPT}\""
+  fi
+  echo "Helpers configured."
 
   # Install the device manager.
   echo "Installing device manager under ${VANADIUM_DEVICE_DIR} ..."
-  "${BIN_INSTALL}/deviced" install --suid_helper="${SETUID_SCRIPT}" --agent="${BIN_INSTALL}/agentd" "$@"
+  "${BIN_INSTALL}/deviced" install --suid_helper="${SETUID_SCRIPT}" --agent="${BIN_INSTALL}/agentd" --init_helper="${INIT_SCRIPT}" "$@"
   echo "Device manager installed."
 }
 
diff --git a/tools/mgmt/test.sh b/tools/mgmt/test.sh
index 7c52a27..432bedf 100755
--- a/tools/mgmt/test.sh
+++ b/tools/mgmt/test.sh
@@ -16,6 +16,7 @@
   APPLICATION_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/tools/application')"
   AGENTD_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/security/agent/agentd')"
   SUIDHELPER_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/services/mgmt/suidhelper')"
+  INITHELPER_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/services/mgmt/inithelper')"
   DEVICEMANAGER_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/services/mgmt/device/deviced')"
   DEVICE_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/tools/mgmt/device')"
   NAMESPACE_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/tools/namespace')"
@@ -86,7 +87,7 @@
   build
 
   BIN_STAGING_DIR=$(shell::tmp_dir)
-  cp "${AGENTD_BIN}" "${SUIDHELPER_BIN}" "${DEVICEMANAGER_BIN}" "${BIN_STAGING_DIR}"
+  cp "${AGENTD_BIN}" "${SUIDHELPER_BIN}" "${INITHELPER_BIN}" "${DEVICEMANAGER_BIN}" "${BIN_STAGING_DIR}"
   shell_test::setup_server_test
 
   # TODO(caprita): Expose an option to turn --single_user off, so we can run