Example #1
0
func main() {
	if err := rpcplus.Register(&Obj{}); err != nil {
		log.Fatal(err)
	}

	os.Remove("/tmp/test.socket")
	log.Fatal(fdrpc.ListenAndServe("/tmp/test.socket"))
}
Example #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
		}

		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
}
Example #3
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
}
Example #4
0
func Register(rcvr interface{}) error {
	return rpcplus.Register(rcvr)
}
Example #5
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()

	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
}
Example #6
0
func init() {
	rpcplus.Register(new(Arith))
}