github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/shim/proc/exec.go (about) 1 // Copyright 2018 The containerd Authors. 2 // Copyright 2018 The gVisor Authors. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // https://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package proc 17 18 import ( 19 "context" 20 "fmt" 21 "io" 22 "os" 23 "path/filepath" 24 "sync" 25 "time" 26 27 "github.com/containerd/console" 28 "github.com/containerd/containerd/errdefs" 29 "github.com/containerd/containerd/log" 30 "github.com/containerd/containerd/pkg/stdio" 31 "github.com/containerd/fifo" 32 runc "github.com/containerd/go-runc" 33 specs "github.com/opencontainers/runtime-spec/specs-go" 34 "golang.org/x/sys/unix" 35 "github.com/SagerNet/gvisor/pkg/cleanup" 36 37 "github.com/SagerNet/gvisor/pkg/shim/runsc" 38 ) 39 40 type execProcess struct { 41 wg sync.WaitGroup 42 43 execState execState 44 45 mu sync.Mutex 46 id string 47 console console.Console 48 io runc.IO 49 status int 50 exited time.Time 51 pid int 52 internalPid int 53 closers []io.Closer 54 stdin io.Closer 55 stdio stdio.Stdio 56 path string 57 spec specs.Process 58 59 parent *Init 60 waitBlock chan struct{} 61 } 62 63 func (e *execProcess) Wait() { 64 <-e.waitBlock 65 } 66 67 func (e *execProcess) ID() string { 68 return e.id 69 } 70 71 func (e *execProcess) Pid() int { 72 e.mu.Lock() 73 defer e.mu.Unlock() 74 return e.pid 75 } 76 77 func (e *execProcess) ExitStatus() int { 78 e.mu.Lock() 79 defer e.mu.Unlock() 80 return e.status 81 } 82 83 func (e *execProcess) ExitedAt() time.Time { 84 e.mu.Lock() 85 defer e.mu.Unlock() 86 return e.exited 87 } 88 89 func (e *execProcess) SetExited(status int) { 90 e.mu.Lock() 91 defer e.mu.Unlock() 92 93 e.execState.SetExited(status) 94 } 95 96 func (e *execProcess) setExited(status int) { 97 if !e.exited.IsZero() { 98 log.L.Debugf("Exec: status already set to %d, ignoring status: %d", e.status, status) 99 return 100 } 101 102 log.L.Debugf("Exec: setting status: %d", status) 103 e.status = status 104 e.exited = time.Now() 105 e.parent.Platform.ShutdownConsole(context.Background(), e.console) 106 close(e.waitBlock) 107 } 108 109 func (e *execProcess) Delete(ctx context.Context) error { 110 e.mu.Lock() 111 defer e.mu.Unlock() 112 113 return e.execState.Delete(ctx) 114 } 115 116 func (e *execProcess) delete() error { 117 e.wg.Wait() 118 if e.io != nil { 119 for _, c := range e.closers { 120 c.Close() 121 } 122 e.io.Close() 123 } 124 return nil 125 } 126 127 func (e *execProcess) Resize(ws console.WinSize) error { 128 e.mu.Lock() 129 defer e.mu.Unlock() 130 131 return e.execState.Resize(ws) 132 } 133 134 func (e *execProcess) resize(ws console.WinSize) error { 135 if e.console == nil { 136 return nil 137 } 138 return e.console.Resize(ws) 139 } 140 141 func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error { 142 e.mu.Lock() 143 defer e.mu.Unlock() 144 145 return e.execState.Kill(ctx, sig, false) 146 } 147 148 func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error { 149 internalPid := e.internalPid 150 if internalPid == 0 { 151 return nil 152 } 153 154 opts := runsc.KillOpts{Pid: internalPid} 155 if err := e.parent.runtime.Kill(ctx, e.parent.id, int(sig), &opts); err != nil { 156 return fmt.Errorf("%s: %w", err.Error(), errdefs.ErrNotFound) 157 } 158 return nil 159 } 160 161 func (e *execProcess) Stdin() io.Closer { 162 return e.stdin 163 } 164 165 func (e *execProcess) Stdio() stdio.Stdio { 166 return e.stdio 167 } 168 169 func (e *execProcess) Start(ctx context.Context) error { 170 e.mu.Lock() 171 defer e.mu.Unlock() 172 173 return e.execState.Start(ctx) 174 } 175 176 func (e *execProcess) start(ctx context.Context) error { 177 var socket *runc.Socket 178 179 switch { 180 case e.stdio.Terminal: 181 s, err := runc.NewTempConsoleSocket() 182 if err != nil { 183 return fmt.Errorf("failed to create runc console socket: %w", err) 184 } 185 defer s.Close() 186 socket = s 187 188 case e.stdio.IsNull(): 189 io, err := runc.NewNullIO() 190 if err != nil { 191 return fmt.Errorf("creating new NULL IO: %w", err) 192 } 193 e.io = io 194 195 default: 196 io, err := runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID, withConditionalIO(e.stdio)) 197 if err != nil { 198 return fmt.Errorf("failed to create runc io pipes: %w", err) 199 } 200 e.io = io 201 } 202 203 opts := &runsc.ExecOpts{ 204 PidFile: filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id)), 205 InternalPidFile: filepath.Join(e.path, fmt.Sprintf("%s-internal.pid", e.id)), 206 IO: e.io, 207 Detach: true, 208 } 209 defer func() { 210 _ = os.Remove(opts.PidFile) 211 _ = os.Remove(opts.InternalPidFile) 212 }() 213 if socket != nil { 214 opts.ConsoleSocket = socket 215 } 216 217 eventCh := e.parent.Monitor.Subscribe() 218 cu := cleanup.Make(func() { 219 e.parent.Monitor.Unsubscribe(eventCh) 220 }) 221 defer cu.Clean() 222 223 if err := e.parent.runtime.Exec(ctx, e.parent.id, e.spec, opts); err != nil { 224 close(e.waitBlock) 225 return e.parent.runtimeError(err, "OCI runtime exec failed") 226 } 227 if e.stdio.Stdin != "" { 228 sc, err := fifo.OpenFifo(context.Background(), e.stdio.Stdin, unix.O_WRONLY|unix.O_NONBLOCK, 0) 229 if err != nil { 230 return fmt.Errorf("failed to open stdin fifo %s: %w", e.stdio.Stdin, err) 231 } 232 e.closers = append(e.closers, sc) 233 e.stdin = sc 234 } 235 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 236 defer cancel() 237 if socket != nil { 238 console, err := socket.ReceiveMaster() 239 if err != nil { 240 return fmt.Errorf("failed to retrieve console master: %w", err) 241 } 242 if e.console, err = e.parent.Platform.CopyConsole(ctx, console, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil { 243 return fmt.Errorf("failed to start console copy: %w", err) 244 } 245 } else if !e.stdio.IsNull() { 246 if err := copyPipes(ctx, e.io, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil { 247 return fmt.Errorf("failed to start io pipe copy: %w", err) 248 } 249 } 250 251 pid, err := runc.ReadPidFile(opts.PidFile) 252 if err != nil { 253 return fmt.Errorf("failed to retrieve OCI runtime exec pid: %w", err) 254 } 255 e.pid = pid 256 internalPid, err := runc.ReadPidFile(opts.InternalPidFile) 257 if err != nil { 258 return fmt.Errorf("failed to retrieve OCI runtime exec internal pid: %w", err) 259 } 260 e.internalPid = internalPid 261 262 go func() { 263 defer e.parent.Monitor.Unsubscribe(eventCh) 264 for event := range eventCh { 265 if event.Pid == e.pid { 266 ExitCh <- Exit{ 267 Timestamp: event.Timestamp, 268 ID: e.id, 269 Status: event.Status, 270 } 271 break 272 } 273 } 274 }() 275 276 cu.Release() // cancel cleanup on success. 277 return nil 278 } 279 280 func (e *execProcess) Status(context.Context) (string, error) { 281 e.mu.Lock() 282 defer e.mu.Unlock() 283 // if we don't have a pid then the exec process has just been created 284 if e.pid == 0 { 285 return "created", nil 286 } 287 // This checks that `runsc exec` process is still running. This process has 288 // the same lifetime as the process executing inside the container. So instead 289 // of calling `runsc kill --pid`, just do a quick check that `runsc exec` is 290 // still running. 291 if err := unix.Kill(e.pid, 0); err != nil { 292 // Can't signal the process, it must have exited. 293 return "stopped", nil 294 } 295 return "running", nil 296 }