func main() { if err := rpcplus.Register(&Obj{}); err != nil { log.Fatal(err) } os.Remove("/tmp/test.socket") log.Fatal(fdrpc.ListenAndServe("/tmp/test.socket")) }
// 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 } log.Info("creating FD proxies") if err := createFDProxies(cmd); err != nil { log.Error("error creating FD proxies", "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 }
// 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 }
func Register(rcvr interface{}) error { return rpcplus.Register(rcvr) }
// 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() init := newContainerInit(c, logFile) log.Debug("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} if c.Uid != nil || c.Gid != nil { cmd.SysProcAttr.Credential = &syscall.Credential{} if c.Uid != nil { cmd.SysProcAttr.Credential.Uid = *c.Uid } if c.Gid != nil { cmd.SysProcAttr.Credential.Gid = *c.Gid } } // 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.Debug("creating PTY") ptyMaster, ptySlave, err := pty.Open() if err != nil { log.Error("error creating PTY", "err", err) return err } init.ptyMaster = ptyMaster cmd.Stdout = ptySlave cmd.Stderr = ptySlave if c.OpenStdin { log.Debug("attaching stdin to PTY") cmd.Stdin = ptySlave cmd.SysProcAttr.Setctty = true } if c.Uid != nil && c.Gid != nil { if err := syscall.Fchown(int(ptySlave.Fd()), int(*c.Uid), int(*c.Gid)); err != nil { log.Error("error changing PTY ownership", "err", err) return err } } } else { // We copy through a socketpair (rather than using cmd.StdoutPipe directly) 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. // // We also don't assign the socketpair directly to fd 1 because that prevents jobs // using /dev/stdout (calling open(2) on a socket leads to an ENXIO error, see // http://marc.info/?l=ast-users&m=120978595414993). newPipe := func(pipeFn func() (io.ReadCloser, error), name string) (*os.File, error) { pipe, err := pipeFn() if err != nil { return nil, err } if c.Uid != nil && c.Gid != nil { if err := syscall.Fchown(int(pipe.(*os.File).Fd()), int(*c.Uid), int(*c.Gid)); err != nil { return nil, err } } sockR, sockW, err := newSocketPair(name) if err != nil { return nil, err } go func() { defer sockW.Close() for { // copy data from the pipe to the socket using splice(2) // (rather than io.Copy) to avoid a needless copy through // user space n, err := syscall.Splice(int(pipe.(*os.File).Fd()), nil, int(sockW.Fd()), nil, 65535, 0) if err != nil || n == 0 { return } } }() return sockR, nil } log.Debug("creating stdout pipe") var err error init.stdout, err = newPipe(cmd.StdoutPipe, "stdout") if err != nil { log.Error("error creating stdout pipe", "err", err) return err } log.Debug("creating stderr pipe") init.stderr, err = newPipe(cmd.StderrPipe, "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.Debug("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.Debug("waiting to be resumed") <-init.resume log.Debug("resuming") init.mtx.Lock() log.Info("starting the job", "args", cmd.Args) if cmdErr != nil { log.Error("error starting the job", "err", cmdErr) init.changeState(StateFailed, cmdErr.Error(), -1) init.exit(1) } if err := cmd.Start(); err != nil { log.Error("error starting the job", "err", err) init.changeState(StateFailed, err.Error(), -1) init.exit(1) } log.Debug("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("name", 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, hbs) log.Info("job exited", "status", exitCode) init.mtx.Lock() init.changeState(StateExited, "", exitCode) init.mtx.Unlock() // Allow calls log.Info("exiting") init.exit(exitCode) return nil }
func init() { rpcplus.Register(new(Arith)) }