Beispiel #1
0
// 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
}
Beispiel #2
0
// 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
}