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