github.com/demonoid81/containerd@v1.3.4/pkg/process/exec.go (about) 1 // +build !windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package process 20 21 import ( 22 "context" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "sync" 28 "syscall" 29 "time" 30 31 "golang.org/x/sys/unix" 32 33 "github.com/containerd/console" 34 "github.com/containerd/containerd/errdefs" 35 "github.com/containerd/containerd/pkg/stdio" 36 "github.com/containerd/fifo" 37 runc "github.com/containerd/go-runc" 38 specs "github.com/opencontainers/runtime-spec/specs-go" 39 "github.com/pkg/errors" 40 ) 41 42 type execProcess struct { 43 wg sync.WaitGroup 44 45 execState execState 46 47 mu sync.Mutex 48 id string 49 console console.Console 50 io *processIO 51 status int 52 exited time.Time 53 pid safePid 54 closers []io.Closer 55 stdin io.Closer 56 stdio stdio.Stdio 57 path string 58 spec specs.Process 59 60 parent *Init 61 waitBlock chan struct{} 62 } 63 64 func (e *execProcess) Wait() { 65 <-e.waitBlock 66 } 67 68 func (e *execProcess) ID() string { 69 return e.id 70 } 71 72 func (e *execProcess) Pid() int { 73 return e.pid.get() 74 } 75 76 func (e *execProcess) ExitStatus() int { 77 e.mu.Lock() 78 defer e.mu.Unlock() 79 return e.status 80 } 81 82 func (e *execProcess) ExitedAt() time.Time { 83 e.mu.Lock() 84 defer e.mu.Unlock() 85 return e.exited 86 } 87 88 func (e *execProcess) SetExited(status int) { 89 e.mu.Lock() 90 defer e.mu.Unlock() 91 92 e.execState.SetExited(status) 93 } 94 95 func (e *execProcess) setExited(status int) { 96 e.status = status 97 e.exited = time.Now() 98 e.parent.Platform.ShutdownConsole(context.Background(), e.console) 99 close(e.waitBlock) 100 } 101 102 func (e *execProcess) Delete(ctx context.Context) error { 103 e.mu.Lock() 104 defer e.mu.Unlock() 105 106 return e.execState.Delete(ctx) 107 } 108 109 func (e *execProcess) delete(ctx context.Context) error { 110 waitTimeout(ctx, &e.wg, 2*time.Second) 111 if e.io != nil { 112 for _, c := range e.closers { 113 c.Close() 114 } 115 e.io.Close() 116 } 117 pidfile := filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id)) 118 // silently ignore error 119 os.Remove(pidfile) 120 return nil 121 } 122 123 func (e *execProcess) Resize(ws console.WinSize) error { 124 e.mu.Lock() 125 defer e.mu.Unlock() 126 127 return e.execState.Resize(ws) 128 } 129 130 func (e *execProcess) resize(ws console.WinSize) error { 131 if e.console == nil { 132 return nil 133 } 134 return e.console.Resize(ws) 135 } 136 137 func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error { 138 e.mu.Lock() 139 defer e.mu.Unlock() 140 141 return e.execState.Kill(ctx, sig, false) 142 } 143 144 func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error { 145 pid := e.pid.get() 146 switch { 147 case pid == 0: 148 return errors.Wrap(errdefs.ErrFailedPrecondition, "process not created") 149 case !e.exited.IsZero(): 150 return errors.Wrapf(errdefs.ErrNotFound, "process already finished") 151 default: 152 if err := unix.Kill(pid, syscall.Signal(sig)); err != nil { 153 return errors.Wrapf(checkKillError(err), "exec kill error") 154 } 155 } 156 return nil 157 } 158 159 func (e *execProcess) Stdin() io.Closer { 160 return e.stdin 161 } 162 163 func (e *execProcess) Stdio() stdio.Stdio { 164 return e.stdio 165 } 166 167 func (e *execProcess) Start(ctx context.Context) error { 168 e.mu.Lock() 169 defer e.mu.Unlock() 170 171 return e.execState.Start(ctx) 172 } 173 174 func (e *execProcess) start(ctx context.Context) (err error) { 175 // The reaper may receive exit signal right after 176 // the container is started, before the e.pid is updated. 177 // In that case, we want to block the signal handler to 178 // access e.pid until it is updated. 179 e.pid.Lock() 180 defer e.pid.Unlock() 181 182 var ( 183 socket *runc.Socket 184 pio *processIO 185 pidFile = newExecPidFile(e.path, e.id) 186 ) 187 if e.stdio.Terminal { 188 if socket, err = runc.NewTempConsoleSocket(); err != nil { 189 return errors.Wrap(err, "failed to create runc console socket") 190 } 191 defer socket.Close() 192 } else { 193 if pio, err = createIO(ctx, e.id, e.parent.IoUID, e.parent.IoGID, e.stdio); err != nil { 194 return errors.Wrap(err, "failed to create init process I/O") 195 } 196 e.io = pio 197 } 198 opts := &runc.ExecOpts{ 199 PidFile: pidFile.Path(), 200 Detach: true, 201 } 202 if pio != nil { 203 opts.IO = pio.IO() 204 } 205 if socket != nil { 206 opts.ConsoleSocket = socket 207 } 208 if err := e.parent.runtime.Exec(ctx, e.parent.id, e.spec, opts); err != nil { 209 close(e.waitBlock) 210 return e.parent.runtimeError(err, "OCI runtime exec failed") 211 } 212 if e.stdio.Stdin != "" { 213 if err := e.openStdin(e.stdio.Stdin); err != nil { 214 return err 215 } 216 } 217 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 218 defer cancel() 219 if socket != nil { 220 console, err := socket.ReceiveMaster() 221 if err != nil { 222 return errors.Wrap(err, "failed to retrieve console master") 223 } 224 if e.console, err = e.parent.Platform.CopyConsole(ctx, console, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil { 225 return errors.Wrap(err, "failed to start console copy") 226 } 227 } else { 228 if err := pio.Copy(ctx, &e.wg); err != nil { 229 return errors.Wrap(err, "failed to start io pipe copy") 230 } 231 } 232 pid, err := pidFile.Read() 233 if err != nil { 234 return errors.Wrap(err, "failed to retrieve OCI runtime exec pid") 235 } 236 e.pid.pid = pid 237 return nil 238 } 239 240 func (e *execProcess) openStdin(path string) error { 241 sc, err := fifo.OpenFifo(context.Background(), path, syscall.O_WRONLY|syscall.O_NONBLOCK, 0) 242 if err != nil { 243 return errors.Wrapf(err, "failed to open stdin fifo %s", path) 244 } 245 e.stdin = sc 246 e.closers = append(e.closers, sc) 247 return nil 248 } 249 250 func (e *execProcess) Status(ctx context.Context) (string, error) { 251 s, err := e.parent.Status(ctx) 252 if err != nil { 253 return "", err 254 } 255 // if the container as a whole is in the pausing/paused state, so are all 256 // other processes inside the container, use container state here 257 switch s { 258 case "paused", "pausing": 259 return s, nil 260 } 261 e.mu.Lock() 262 defer e.mu.Unlock() 263 return e.execState.Status(ctx) 264 }