ARTICLE — POSTS

My Docker 1

今日任务

init 传参改用匿名管道

init 传参改用匿名管道

匿名管道

为什么要使用匿名管道

原方案通过 /proc/self/exe init [args...] 将所有参数直接塞进 exec.Command() 调用中,如果参数过长或者有特殊符号就会发生错误。

匿名管道的读和写直接走文件io,可以加强对参数的控制和校验

// run
func NewParentProcess(interactive, tty bool) (*exec.Cmd, *os.File) { // 返回 Cmd 对象和 管道的写对象
	fr, fw, err := os.Pipe() // 创建匿名管道
	if err != nil {
		l.Error("failed to create pipe", "detail", err)
		return nil, nil
	}

	cmd := exec.Command("/proc/self/exe", "init") // 不需要在此处将 args 传入
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID |
			syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
	}
	if interactive {
		cmd.Stdin = os.Stdin
	}
	if tty {
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	}

	cmd.ExtraFiles = append(cmd.ExtraFiles, fr) // 将管道的读文件描述符放进新创建进程的文件描述符数组中, 新的进程通过这个fd可以打开对应的管道文件
	return cmd, fw
}
// init
const readingFd uintptr = 3 // 不固定

func Init() error {
	l.Info("initiating")

	l.Info("opening pipe")
	f := os.NewFile(readingFd, "pipe") // 打开对应的管道
	if f == nil {
		l.Error("failed to create FILE with the given fd", "fd", readingFd)
		return fmt.Errorf("failed to create FILE with the given fd: %d", readingFd)
	}

	l.Info("reading from pipe")
	data, err := io.ReadAll(f) // 直接走文件io
	if err != nil {
		l.Error("failed to read from pipe", "detail", err)
		return err
	}

    // 将参数反序列化
    //
    // 用 NUL 分隔符还原 cmds,避免含空格的参数(如 sh -c "echo hi")被腰斩
    // 使用管道传参可以完成更多的操作
	args := strings.Split(strings.TrimRight(string(data), "\x00"), "\x00")
	if len(args) < 1 || args[0] == "" {
		return fmt.Errorf("command is nil")
	}

	l.Info("remounting `/`")
	if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
		l.Error("failed to remount `/` with `MS_PRIVATE`", "detail", err)
		return err
	}

	mountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
	if err := syscall.Mount("proc", "/proc", "proc", uintptr(mountFlags), ""); err != nil {
		l.Error("failed to mount proc", "detail", err)
		return err
	}
	l.Info("executing", "argv0", args[0], "argv", args)
	if err := syscall.Exec(args[0], args, os.Environ()); err != nil {
		l.Error("failed to init", "cmds", args, "detail", err)
		return err
	}
	return nil
}
系列

本系列文章

Docker
  1. My Docker 0
  2. My Docker 1
  3. My Docker 2
  4. My Docker 3