1package gcs 2 3import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "sync" 10 11 "github.com/Microsoft/go-winio" 12 "github.com/Microsoft/hcsshim/internal/cow" 13 "github.com/Microsoft/hcsshim/internal/log" 14 "github.com/Microsoft/hcsshim/internal/logfields" 15 "github.com/Microsoft/hcsshim/internal/oc" 16 "github.com/sirupsen/logrus" 17 "go.opencensus.io/trace" 18) 19 20const ( 21 hrNotFound = 0x80070490 22) 23 24// Process represents a process in a container or container host. 25type Process struct { 26 gc *GuestConnection 27 cid string 28 id uint32 29 waitCall *rpc 30 waitResp containerWaitForProcessResponse 31 stdin, stdout, stderr *ioChannel 32 stdinCloseWriteOnce sync.Once 33 stdinCloseWriteErr error 34} 35 36var _ cow.Process = &Process{} 37 38type baseProcessParams struct { 39 CreateStdInPipe, CreateStdOutPipe, CreateStdErrPipe bool 40} 41 42func (gc *GuestConnection) exec(ctx context.Context, cid string, params interface{}) (_ cow.Process, err error) { 43 b, err := json.Marshal(params) 44 if err != nil { 45 return nil, err 46 } 47 var bp baseProcessParams 48 err = json.Unmarshal(b, &bp) 49 if err != nil { 50 return nil, err 51 } 52 53 req := containerExecuteProcess{ 54 requestBase: makeRequest(ctx, cid), 55 Settings: executeProcessSettings{ 56 ProcessParameters: anyInString{params}, 57 }, 58 } 59 60 p := &Process{gc: gc, cid: cid} 61 defer func() { 62 if err != nil { 63 p.Close() 64 } 65 }() 66 67 // Construct the stdio channels. Windows guests expect hvsock service IDs 68 // instead of vsock ports. 69 var hvsockSettings executeProcessStdioRelaySettings 70 var vsockSettings executeProcessVsockStdioRelaySettings 71 if gc.os == "windows" { 72 req.Settings.StdioRelaySettings = &hvsockSettings 73 } else { 74 req.Settings.VsockStdioRelaySettings = &vsockSettings 75 } 76 if bp.CreateStdInPipe { 77 p.stdin, vsockSettings.StdIn, err = gc.newIoChannel() 78 if err != nil { 79 return nil, err 80 } 81 g := winio.VsockServiceID(vsockSettings.StdIn) 82 hvsockSettings.StdIn = &g 83 } 84 if bp.CreateStdOutPipe { 85 p.stdout, vsockSettings.StdOut, err = gc.newIoChannel() 86 if err != nil { 87 return nil, err 88 } 89 g := winio.VsockServiceID(vsockSettings.StdOut) 90 hvsockSettings.StdOut = &g 91 } 92 if bp.CreateStdErrPipe { 93 p.stderr, vsockSettings.StdErr, err = gc.newIoChannel() 94 if err != nil { 95 return nil, err 96 } 97 g := winio.VsockServiceID(vsockSettings.StdErr) 98 hvsockSettings.StdErr = &g 99 } 100 101 var resp containerExecuteProcessResponse 102 err = gc.brdg.RPC(ctx, rpcExecuteProcess, &req, &resp, false) 103 if err != nil { 104 return nil, err 105 } 106 p.id = resp.ProcessID 107 log.G(ctx).WithField("pid", p.id).Debug("created process pid") 108 // Start a wait message. 109 waitReq := containerWaitForProcess{ 110 requestBase: makeRequest(ctx, cid), 111 ProcessID: p.id, 112 TimeoutInMs: 0xffffffff, 113 } 114 p.waitCall, err = gc.brdg.AsyncRPC(ctx, rpcWaitForProcess, &waitReq, &p.waitResp) 115 if err != nil { 116 return nil, fmt.Errorf("failed to wait on process, leaking process: %s", err) 117 } 118 go p.waitBackground() 119 return p, nil 120} 121 122// Close releases resources associated with the process and closes the 123// associated standard IO streams. 124func (p *Process) Close() error { 125 ctx, span := trace.StartSpan(context.Background(), "gcs::Process::Close") 126 defer span.End() 127 span.AddAttributes( 128 trace.StringAttribute("cid", p.cid), 129 trace.Int64Attribute("pid", int64(p.id))) 130 131 err := p.stdin.Close() 132 if err != nil { 133 log.G(ctx).WithError(err).Warn("close stdin failed") 134 } 135 err = p.stdout.Close() 136 if err != nil { 137 log.G(ctx).WithError(err).Warn("close stdout failed") 138 } 139 err = p.stderr.Close() 140 if err != nil { 141 log.G(ctx).WithError(err).Warn("close stderr failed") 142 } 143 return nil 144} 145 146// CloseStdin causes the process to read EOF on its stdin stream. 147func (p *Process) CloseStdin(ctx context.Context) (err error) { 148 ctx, span := trace.StartSpan(ctx, "gcs::Process::CloseStdin") 149 defer span.End() 150 defer func() { oc.SetSpanStatus(span, err) }() 151 span.AddAttributes( 152 trace.StringAttribute("cid", p.cid), 153 trace.Int64Attribute("pid", int64(p.id))) 154 155 p.stdinCloseWriteOnce.Do(func() { 156 p.stdinCloseWriteErr = p.stdin.CloseWrite() 157 }) 158 return p.stdinCloseWriteErr 159} 160 161// ExitCode returns the process's exit code, or an error if the process is still 162// running or the exit code is otherwise unknown. 163func (p *Process) ExitCode() (_ int, err error) { 164 if !p.waitCall.Done() { 165 return -1, errors.New("process not exited") 166 } 167 if err := p.waitCall.Err(); err != nil { 168 return -1, err 169 } 170 return int(p.waitResp.ExitCode), nil 171} 172 173// Kill sends a forceful terminate signal to the process and returns whether the 174// signal was delivered. The process might not be terminated by the time this 175// returns. 176func (p *Process) Kill(ctx context.Context) (_ bool, err error) { 177 ctx, span := trace.StartSpan(ctx, "gcs::Process::Kill") 178 defer span.End() 179 defer func() { oc.SetSpanStatus(span, err) }() 180 span.AddAttributes( 181 trace.StringAttribute("cid", p.cid), 182 trace.Int64Attribute("pid", int64(p.id))) 183 184 return p.Signal(ctx, nil) 185} 186 187// Pid returns the process ID. 188func (p *Process) Pid() int { 189 return int(p.id) 190} 191 192// ResizeConsole requests that the pty associated with the process resize its 193// window. 194func (p *Process) ResizeConsole(ctx context.Context, width, height uint16) (err error) { 195 ctx, span := trace.StartSpan(ctx, "gcs::Process::ResizeConsole") 196 defer span.End() 197 defer func() { oc.SetSpanStatus(span, err) }() 198 span.AddAttributes( 199 trace.StringAttribute("cid", p.cid), 200 trace.Int64Attribute("pid", int64(p.id))) 201 202 req := containerResizeConsole{ 203 requestBase: makeRequest(ctx, p.cid), 204 ProcessID: p.id, 205 Height: height, 206 Width: width, 207 } 208 var resp responseBase 209 return p.gc.brdg.RPC(ctx, rpcResizeConsole, &req, &resp, true) 210} 211 212// Signal sends a signal to the process, returning whether it was delivered. 213func (p *Process) Signal(ctx context.Context, options interface{}) (_ bool, err error) { 214 ctx, span := trace.StartSpan(ctx, "gcs::Process::Signal") 215 defer span.End() 216 defer func() { oc.SetSpanStatus(span, err) }() 217 span.AddAttributes( 218 trace.StringAttribute("cid", p.cid), 219 trace.Int64Attribute("pid", int64(p.id))) 220 221 req := containerSignalProcess{ 222 requestBase: makeRequest(ctx, p.cid), 223 ProcessID: p.id, 224 Options: options, 225 } 226 var resp responseBase 227 // FUTURE: SIGKILL is idempotent and can safely be cancelled, but this interface 228 // does currently make it easy to determine what signal is being sent. 229 err = p.gc.brdg.RPC(ctx, rpcSignalProcess, &req, &resp, false) 230 if err != nil { 231 if uint32(resp.Result) != hrNotFound { 232 return false, err 233 } 234 if !p.waitCall.Done() { 235 log.G(ctx).WithFields(logrus.Fields{ 236 logrus.ErrorKey: err, 237 logfields.ContainerID: p.cid, 238 logfields.ProcessID: p.id, 239 }).Warn("ignoring missing process") 240 } 241 return false, nil 242 } 243 return true, nil 244} 245 246// Stdio returns the standard IO streams associated with the container. They 247// will be closed when Close is called. 248func (p *Process) Stdio() (stdin io.Writer, stdout, stderr io.Reader) { 249 return p.stdin, p.stdout, p.stderr 250} 251 252// Wait waits for the process (or guest connection) to terminate. 253func (p *Process) Wait() error { 254 p.waitCall.Wait() 255 return p.waitCall.Err() 256} 257 258func (p *Process) waitBackground() { 259 ctx, span := trace.StartSpan(context.Background(), "gcs::Process::waitBackground") 260 defer span.End() 261 span.AddAttributes( 262 trace.StringAttribute("cid", p.cid), 263 trace.Int64Attribute("pid", int64(p.id))) 264 265 p.waitCall.Wait() 266 ec, err := p.ExitCode() 267 if err != nil { 268 log.G(ctx).WithError(err).Error("failed wait") 269 } 270 log.G(ctx).WithField("exitCode", ec).Debug("process exited") 271 oc.SetSpanStatus(span, err) 272} 273