Merge "[Reland] lucifer: capture non-zero exit code in deployment."
diff --git a/src/lucifer/cmd/lucifer/deploytask.go b/src/lucifer/cmd/lucifer/deploytask.go
index 6a73a6f..995a449 100644
--- a/src/lucifer/cmd/lucifer/deploytask.go
+++ b/src/lucifer/cmd/lucifer/deploytask.go
@@ -120,6 +120,8 @@
 		reason = fmt.Errorf("process failed to start")
 	case r.Aborted:
 		reason = fmt.Errorf("process aborted")
+	case r.ExitStatus != 0:
+		reason = fmt.Errorf("process fail with exit code %d", r.ExitStatus)
 	}
 	switch {
 	case reason == nil:
diff --git a/src/lucifer/osutil/osutil.go b/src/lucifer/osutil/osutil.go
index d4e8f96..2bef5e6 100644
--- a/src/lucifer/osutil/osutil.go
+++ b/src/lucifer/osutil/osutil.go
@@ -20,6 +20,22 @@
 	// Started is true if the process was started successfully.
 	Started bool
 	Aborted bool
+	// ExitStatus only makes sense if Started is true.
+	ExitStatus int
+}
+
+func getCmdExitStatus(err error) int {
+	if ee, ok := err.(*exec.ExitError); ok {
+		// Cannot use ee.ProcessState.ExitCode() as ExitCode is not supported until go1.12.
+		// https://golang.org/pkg/os/#ProcessState.ExitCode
+		// But chroot has go version 1.11.2.
+		if status, ok := ee.ProcessState.Sys().(syscall.WaitStatus); ok {
+			return status.ExitStatus()
+		} else {
+			return -1
+		}
+	}
+	return -1
 }
 
 // RunWithAbort runs an exec.Cmd with context cancellation/aborting.
@@ -36,7 +52,9 @@
 	r.Started = true
 	exited := make(chan struct{})
 	go func() {
-		_ = cmd.Wait()
+		if err := cmd.Wait(); err != nil {
+			r.ExitStatus = getCmdExitStatus(err)
+		}
 		close(exited)
 	}()
 	select {