// setupIO modifies the given process config according to the options.
// Terminal Modes:
// New Terminal
// Pass-Through
// runc Modes:
// Foreground
// Detached
funcsetupIO(process*libcontainer.Process,rootuid,rootgidint,createTTY,detachbool,sockpathstring)(*tty,error){ifcreateTTY{process.Stdin=nilprocess.Stdout=nilprocess.Stderr=nilt:=&tty{}if!detach{// 1, New Terminal & Foreground
iferr:=t.initHostConsole();err!=nil{returnnil,err}parent,child,err:=utils.NewSockPair("console")iferr!=nil{returnnil,err}process.ConsoleSocket=childt.postStart=append(t.postStart,parent,child)t.consoleC=make(chanerror,1)gofunc(){t.consoleC<-t.recvtty(parent)}()}else{// 2, New Terminal & Detached
// the caller of runc will handle receiving the console master
conn,err:=net.Dial("unix",sockpath)iferr!=nil{returnnil,err}uc,ok:=conn.(*net.UnixConn)if!ok{returnnil,errors.New("casting to UnixConn failed")}t.postStart=append(t.postStart,uc)socket,err:=uc.File()iferr!=nil{returnnil,err}t.postStart=append(t.postStart,socket)process.ConsoleSocket=socket}returnt,nil}// when runc will detach the caller provides the stdio to runc via runc's 0,1,2
// and the container's process inherits runc's stdio.
// 3, Pass-Through & Detached
ifdetach{inheritStdio(process)return&tty{},nil}// 4, Pass-Through & Foreground
returnsetupProcessPipes(process,rootuid,rootgid)}
New Terminal & Foreground
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
process.Stdin=nilprocess.Stdout=nilprocess.Stderr=nilt:=&tty{}// 1, New Terminal & Foreground
iferr:=t.initHostConsole();err!=nil{returnnil,err}parent,child,err:=utils.NewSockPair("console")iferr!=nil{returnnil,err}process.ConsoleSocket=childt.postStart=append(t.postStart,parent,child)t.consoleC=make(chanerror,1)gofunc(){t.consoleC<-t.recvtty(parent)}()
func(t*tty)recvtty(socket*os.File)(Errerror){f,err:=utils.RecvFd(socket)iferr!=nil{returnerr}cons,err:=console.ConsoleFromFile(f)iferr!=nil{returnerr}err=console.ClearONLCR(cons.Fd())iferr!=nil{returnerr}epoller,err:=console.NewEpoller()iferr!=nil{returnerr}epollConsole,err:=epoller.Add(cons)iferr!=nil{returnerr}deferfunc(){ifErr!=nil{_=epollConsole.Close()}}()gofunc(){_=epoller.Wait()}()gofunc(){_,_=io.Copy(epollConsole,os.Stdin)}()t.wg.Add(1)got.copyIO(os.Stdout,epollConsole)// Set raw mode for the controlling terminal.
iferr:=t.hostConsole.SetRaw();err!=nil{returnfmt.Errorf("failed to set the terminal from the stdin: %w",err)}gohandleInterrupt(t.hostConsole)t.epoller=epollert.console=epollConsolet.closers=[]io.Closer{epollConsole}returnnil}
func(c*Container)commandTemplate(p*Process,childInitPipe*os.File,childLogPipe*os.File)*exec.Cmd{cmd:=exec.Command("/proc/self/exe","init")cmd.Args[0]=os.Args[0]// 当 New Terminal 模式时
// p.Stdin、p.Stdout、p.Stderr都被设置为nil
cmd.Stdin=p.Stdincmd.Stdout=p.Stdoutcmd.Stderr=p.Stderrcmd.Dir=c.config.Rootfsifcmd.SysProcAttr==nil{cmd.SysProcAttr=&unix.SysProcAttr{}}cmd.Env=append(cmd.Env,"GOMAXPROCS="+os.Getenv("GOMAXPROCS"))cmd.ExtraFiles=append(cmd.ExtraFiles,p.ExtraFiles...)ifp.ConsoleSocket!=nil{cmd.ExtraFiles=append(cmd.ExtraFiles,p.ConsoleSocket)cmd.Env=append(cmd.Env,"_LIBCONTAINER_CONSOLE="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1),)}cmd.ExtraFiles=append(cmd.ExtraFiles,childInitPipe)cmd.Env=append(cmd.Env,"_LIBCONTAINER_INITPIPE="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1),"_LIBCONTAINER_STATEDIR="+c.root,)cmd.ExtraFiles=append(cmd.ExtraFiles,childLogPipe)cmd.Env=append(cmd.Env,"_LIBCONTAINER_LOGPIPE="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1),"_LIBCONTAINER_LOGLEVEL="+p.LogLevel,)// NOTE: when running a container with no PID namespace and the parent process spawning the container is
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
// even with the parent still running.
ifc.config.ParentDeathSignal>0{cmd.SysProcAttr.Pdeathsig=unix.Signal(c.config.ParentDeathSignal)}returncmd}
// runc/libcontainer/process_linux.go
func(p*initProcess)sendConfig()error{// send the config to the container's init process, we don't use JSON Encode
// here because there might be a problem in JSON decoder in some cases, see:
// https://github.com/docker/docker/issues/14203#issuecomment-174177790
returnutils.WriteJSON(p.messageSockPair.parent,p.config)}
// setupConsole sets up the console from inside the container, and sends the
// master pty fd to the config.Pipe (using cmsg). This is done to ensure that
// consoles are scoped to a container properly (see runc#814 and the many
// issues related to that). This has to be run *after* we've pivoted to the new
// rootfs (and the users' configuration is entirely set up).
funcsetupConsole(socket*os.File,config*initConfig,mountbool)error{defersocket.Close()// At this point, /dev/ptmx points to something that we would expect. We
// used to change the owner of the slave path, but since the /dev/pts mount
// can have gid=X set (at the users' option). So touching the owner of the
// slave PTY is not necessary, as the kernel will handle that for us. Note
// however, that setupUser (specifically fixStdioPermissions) *will* change
// the UID owner of the console to be the user the process will run as (so
// they can actually control their console).
pty,slavePath,err:=console.NewPty()iferr!=nil{returnerr}// After we return from here, we don't need the console anymore.
deferpty.Close()ifconfig.ConsoleHeight!=0&&config.ConsoleWidth!=0{err=pty.Resize(console.WinSize{Height:config.ConsoleHeight,Width:config.ConsoleWidth,})iferr!=nil{returnerr}}// Mount the console inside our rootfs.
ifmount{iferr:=mountConsole(slavePath);err!=nil{returnerr}}// While we can access console.master, using the API is a good idea.
iferr:=utils.SendFd(socket,pty.Name(),pty.Fd());err!=nil{returnerr}// Now, dup over all the things.
returndupStdio(slavePath)}
// dupStdio opens the slavePath for the console and dups the fds to the current
// processes stdio, fd 0,1,2.
funcdupStdio(slavePathstring)error{fd,err:=unix.Open(slavePath,unix.O_RDWR,0)iferr!=nil{return&os.PathError{Op:"open",Path:slavePath,Err:err,}}// 打开控制台的 slavePath 并将 fds 复制到当前进程 stdio,fd 0,1,2。
// 这里的 0 1 2 代表容器进程的标准输入,标准输出,标准错误
// 如果这里把 1 2 去掉,那在前台运行的时候,将看不到容器输出内容
for_,i:=range[]int{0,1,2}{iferr:=unix.Dup3(fd,i,0);err!=nil{returnerr}}returnnil}
New Terminal & Detached
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// the caller of runc will handle receiving the console master
conn,err:=net.Dial("unix",sockpath)iferr!=nil{returnnil,err}uc,ok:=conn.(*net.UnixConn)if!ok{returnnil,errors.New("casting to UnixConn failed")}t.postStart=append(t.postStart,uc)socket,err:=uc.File()iferr!=nil{returnnil,err}t.postStart=append(t.postStart,socket)process.ConsoleSocket=socket