// Run as pid 1 and monitor the contained process to return its exit code. func containerInitApp(args *ContainerInitArgs) error { init := newContainerInit(args) if err := rpcplus.Register(init); err != nil { return err } init.mtx.Lock() defer init.mtx.Unlock() // Prepare the cmd based on the given args // If this fails we report that below cmdPath, cmdErr := getCmdPath(args) cmd := exec.Command(cmdPath, args.args[1:]...) cmd.Dir = args.workDir cmd.Env = args.env // App runs in its own session cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} // Console setup. Hook up the container app's stdin/stdout/stderr to // either a pty or pipes. The FDs for the controlling side of the // pty/pipes will be passed to flynn-host later via a UNIX socket. if args.tty { ptyMaster, ptySlave, err := pty.Open() if err != nil { return err } init.ptyMaster = ptyMaster cmd.Stdout = ptySlave cmd.Stderr = ptySlave if args.openStdin { cmd.Stdin = ptySlave cmd.SysProcAttr.Setctty = true } } else { stdout, err := cmd.StdoutPipe() if err != nil { return err } init.stdout = stdout.(*os.File) stderr, err := cmd.StderrPipe() if err != nil { return err } init.stderr = stderr.(*os.File) if args.openStdin { // Can't use cmd.StdinPipe() here, since in Go 1.2 it // returns an io.WriteCloser with the underlying object // being an *exec.closeOnce, neither of which provides // a way to convert to an FD. pipeRead, pipeWrite, err := os.Pipe() if err != nil { return err } cmd.Stdin = pipeRead init.stdin = pipeWrite } } go runRPCServer() // Wait for flynn-host to tell us to start init.mtx.Unlock() // Allow calls <-init.resume init.mtx.Lock() exitCode := 1 if cmdErr != nil { init.changeState(StateFailed, cmdErr.Error(), -1) return cmdErr } // Container setup if err := setupCommon(args); err != nil { init.changeState(StateFailed, err.Error(), -1) } // Start the app if err := cmd.Start(); err != nil { init.changeState(StateFailed, err.Error(), -1) } init.process = cmd.Process init.changeState(StateRunning, "", -1) init.mtx.Unlock() // Allow calls exitCode = babySit(init.process) init.mtx.Lock() init.changeState(StateExited, "", exitCode) init.mtx.Unlock() // Allow calls // Wait for the client to call Resume() again. This gives the client // a chance to get the exit code from the RPC socket call interface before // we die. select { case <-init.resume: case <-time.After(time.Second): return fmt.Errorf("timeout waiting for client to call Resume()") } init.mtx.Lock() os.Exit(exitCode) return nil }
// Run as pid 1 and monitor the contained process to return its exit code. func containerInitApp(c *Config, logFile *os.File) error { log := logger.New("fn", "containerInitApp") init := newContainerInit(c, logFile) log.Info("registering RPC server") if err := rpcplus.Register(init); err != nil { log.Error("error registering RPC server", "err", err) return err } init.mtx.Lock() defer init.mtx.Unlock() // Prepare the cmd based on the given args // If this fails we report that below cmdPath, cmdErr := getCmdPath(c) cmd := exec.Command(cmdPath, c.Args[1:]...) cmd.Dir = c.WorkDir cmd.Env = make([]string, 0, len(c.Env)) for k, v := range c.Env { cmd.Env = append(cmd.Env, k+"="+v) } // App runs in its own session cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} // Console setup. Hook up the container app's stdin/stdout/stderr to // either a pty or pipes. The FDs for the controlling side of the // pty/pipes will be passed to flynn-host later via a UNIX socket. if c.TTY { log.Info("creating PTY") ptyMaster, ptySlave, err := pty.Open() if err != nil { log.Info("error creating PTY", "err", err) return err } init.ptyMaster = ptyMaster cmd.Stdout = ptySlave cmd.Stderr = ptySlave if c.OpenStdin { log.Info("attaching stdin to PTY") cmd.Stdin = ptySlave cmd.SysProcAttr.Setctty = true } } else { // we use syscall.Socketpair (rather than cmd.StdoutPipe) to make it easier // for flynn-host to do non-blocking I/O (via net.FileConn) so that no // read(2) calls can succeed after closing the logs during an update. log.Info("creating stdout pipe") var err error cmd.Stdout, init.stdout, err = newSocketPair("stdout") if err != nil { log.Error("error creating stdout pipe", "err", err) return err } log.Info("creating stderr pipe") cmd.Stderr, init.stderr, err = newSocketPair("stderr") if err != nil { log.Error("error creating stderr pipe", "err", err) return err } if c.OpenStdin { // Can't use cmd.StdinPipe() here, since in Go 1.2 it // returns an io.WriteCloser with the underlying object // being an *exec.closeOnce, neither of which provides // a way to convert to an FD. log.Info("creating stdin pipe") pipeRead, pipeWrite, err := os.Pipe() if err != nil { log.Error("creating stdin pipe", "err", err) return err } cmd.Stdin = pipeRead init.stdin = pipeWrite } } go runRPCServer() // Wait for flynn-host to tell us to start init.mtx.Unlock() // Allow calls log.Info("waiting to be resumed") <-init.resume log.Info("resuming") init.mtx.Lock() if cmdErr != nil { log.Error("command failed", "err", cmdErr) init.changeState(StateFailed, cmdErr.Error(), -1) init.exit(1) } // Container setup log.Info("setting up the container") if err := setupCommon(c, log); err != nil { log.Error("error setting up the container", "err", err) init.changeState(StateFailed, err.Error(), -1) init.exit(1) } // Start the app log.Info("starting the command") if err := cmd.Start(); err != nil { log.Error("error starting the command", "err", err) init.changeState(StateFailed, err.Error(), -1) init.exit(1) } log.Info("setting state to running") init.process = cmd.Process init.changeState(StateRunning, "", -1) init.mtx.Unlock() // Allow calls // monitor services hbs := make([]discoverd.Heartbeater, 0, len(c.Ports)) for _, port := range c.Ports { if port.Service == nil { continue } log = log.New("service", port.Service.Name, "port", port.Port, "proto", port.Proto) log.Info("monitoring service") hb, err := monitor(port, init, c.Env, log) if err != nil { log.Error("error monitoring service", "err", err) os.Exit(70) } hbs = append(hbs, hb) } exitCode := babySit(init.process) log.Info("command exited", "status", exitCode) init.mtx.Lock() for _, hb := range hbs { hb.Close() } init.changeState(StateExited, "", exitCode) init.mtx.Unlock() // Allow calls log.Info("exiting") init.exit(exitCode) return nil }