1package main
2
3import (
4	"context"
5	"encoding/json"
6	"os"
7	"path/filepath"
8	"strings"
9
10	runhcsopts "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
11	"github.com/Microsoft/hcsshim/internal/oci"
12	"github.com/Microsoft/hcsshim/internal/shimdiag"
13	containerd_v1_types "github.com/containerd/containerd/api/types/task"
14	"github.com/containerd/containerd/errdefs"
15	"github.com/containerd/containerd/mount"
16	"github.com/containerd/containerd/runtime/v2/task"
17	"github.com/containerd/typeurl"
18	google_protobuf1 "github.com/gogo/protobuf/types"
19	"github.com/opencontainers/runtime-spec/specs-go"
20	"github.com/pkg/errors"
21)
22
23var empty = &google_protobuf1.Empty{}
24
25// getPod returns the pod this shim is tracking or else returns `nil`. It is the
26// callers responsibility to verify that `s.isSandbox == true` before calling
27// this method.
28//
29//
30// If `pod==nil` returns `errdefs.ErrFailedPrecondition`.
31func (s *service) getPod() (shimPod, error) {
32	raw := s.taskOrPod.Load()
33	if raw == nil {
34		return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "task with id: '%s' must be created first", s.tid)
35	}
36	return raw.(shimPod), nil
37}
38
39// getTask returns a task matching `tid` or else returns `nil`. This properly
40// handles a task in a pod or a singular task shim.
41//
42// If `tid` is not found will return `errdefs.ErrNotFound`.
43func (s *service) getTask(tid string) (shimTask, error) {
44	raw := s.taskOrPod.Load()
45	if raw == nil {
46		return nil, errors.Wrapf(errdefs.ErrNotFound, "task with id: '%s' not found", tid)
47	}
48	if s.isSandbox {
49		p := raw.(shimPod)
50		return p.GetTask(tid)
51	}
52	// When its not a sandbox only the init task is a valid id.
53	if s.tid != tid {
54		return nil, errors.Wrapf(errdefs.ErrNotFound, "task with id: '%s' not found", tid)
55	}
56	return raw.(shimTask), nil
57}
58
59func (s *service) stateInternal(ctx context.Context, req *task.StateRequest) (*task.StateResponse, error) {
60	t, err := s.getTask(req.ID)
61	if err != nil {
62		return nil, err
63	}
64	e, err := t.GetExec(req.ExecID)
65	if err != nil {
66		return nil, err
67	}
68	return e.Status(), nil
69}
70
71func (s *service) createInternal(ctx context.Context, req *task.CreateTaskRequest) (*task.CreateTaskResponse, error) {
72	setupDebuggerEvent()
73
74	var shimOpts *runhcsopts.Options
75	if req.Options != nil {
76		v, err := typeurl.UnmarshalAny(req.Options)
77		if err != nil {
78			return nil, err
79		}
80		shimOpts = v.(*runhcsopts.Options)
81	}
82
83	var spec specs.Spec
84	f, err := os.Open(filepath.Join(req.Bundle, "config.json"))
85	if err != nil {
86		return nil, err
87	}
88	if err := json.NewDecoder(f).Decode(&spec); err != nil {
89		f.Close()
90		return nil, err
91	}
92	f.Close()
93
94	spec = oci.UpdateSpecFromOptions(spec, shimOpts)
95
96	if len(req.Rootfs) == 0 {
97		// If no mounts are passed via the snapshotter its the callers full
98		// responsibility to manage the storage. Just move on without affecting
99		// the config.json at all.
100		if spec.Windows == nil || len(spec.Windows.LayerFolders) < 2 {
101			return nil, errors.Wrap(errdefs.ErrFailedPrecondition, "no Windows.LayerFolders found in oci spec")
102		}
103	} else if len(req.Rootfs) != 1 {
104		return nil, errors.Wrap(errdefs.ErrFailedPrecondition, "Rootfs does not contain exactly 1 mount for the root file system")
105	} else {
106		m := req.Rootfs[0]
107		if m.Type != "windows-layer" && m.Type != "lcow-layer" {
108			return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "unsupported mount type '%s'", m.Type)
109		}
110
111		// parentLayerPaths are passed in layerN, layerN-1, ..., layer 0
112		//
113		// The OCI spec expects:
114		//   layerN, layerN-1, ..., layer0, scratch
115		var parentLayerPaths []string
116		for _, option := range m.Options {
117			if strings.HasPrefix(option, mount.ParentLayerPathsFlag) {
118				err := json.Unmarshal([]byte(option[len(mount.ParentLayerPathsFlag):]), &parentLayerPaths)
119				if err != nil {
120					return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "failed to unmarshal parent layer paths from mount: %v", err)
121				}
122			}
123		}
124
125		if m.Type == "lcow-layer" {
126			// If we are creating LCOW make sure that spec.Windows is filled out before
127			// appending layer folders.
128			if spec.Windows == nil {
129				spec.Windows = &specs.Windows{}
130			}
131			if spec.Windows.HyperV == nil {
132				spec.Windows.HyperV = &specs.WindowsHyperV{}
133			}
134		} else if spec.Windows.HyperV == nil {
135			// This is a Windows Argon make sure that we have a Root filled in.
136			if spec.Root == nil {
137				spec.Root = &specs.Root{}
138			}
139		}
140
141		// Append the parents
142		spec.Windows.LayerFolders = append(spec.Windows.LayerFolders, parentLayerPaths...)
143		// Append the scratch
144		spec.Windows.LayerFolders = append(spec.Windows.LayerFolders, m.Source)
145	}
146
147	if req.Terminal && req.Stderr != "" {
148		return nil, errors.Wrap(errdefs.ErrFailedPrecondition, "if using terminal, stderr must be empty")
149	}
150
151	resp := &task.CreateTaskResponse{}
152	s.cl.Lock()
153	if s.isSandbox {
154		pod, err := s.getPod()
155		if err == nil {
156			// The POD sandbox was previously created. Unlock and forward to the POD
157			s.cl.Unlock()
158			t, err := pod.CreateTask(ctx, req, &spec)
159			if err != nil {
160				return nil, err
161			}
162			e, _ := t.GetExec("")
163			resp.Pid = uint32(e.Pid())
164			return resp, nil
165		}
166		pod, err = createPod(ctx, s.events, req, &spec)
167		if err != nil {
168			s.cl.Unlock()
169			return nil, err
170		}
171		t, _ := pod.GetTask(req.ID)
172		e, _ := t.GetExec("")
173		resp.Pid = uint32(e.Pid())
174		s.taskOrPod.Store(pod)
175	} else {
176		t, err := newHcsStandaloneTask(ctx, s.events, req, &spec)
177		if err != nil {
178			s.cl.Unlock()
179			return nil, err
180		}
181		e, _ := t.GetExec("")
182		resp.Pid = uint32(e.Pid())
183		s.taskOrPod.Store(t)
184	}
185	s.cl.Unlock()
186	return resp, nil
187}
188
189func (s *service) startInternal(ctx context.Context, req *task.StartRequest) (*task.StartResponse, error) {
190	t, err := s.getTask(req.ID)
191	if err != nil {
192		return nil, err
193	}
194	e, err := t.GetExec(req.ExecID)
195	if err != nil {
196		return nil, err
197	}
198	err = e.Start(ctx)
199	if err != nil {
200		return nil, err
201	}
202	return &task.StartResponse{
203		Pid: uint32(e.Pid()),
204	}, nil
205}
206
207func (s *service) deleteInternal(ctx context.Context, req *task.DeleteRequest) (*task.DeleteResponse, error) {
208	// TODO: JTERRY75 we need to send this to the POD for isSandbox
209
210	t, err := s.getTask(req.ID)
211	if err != nil {
212		return nil, err
213	}
214	pid, exitStatus, exitedAt, err := t.DeleteExec(ctx, req.ExecID)
215	if err != nil {
216		return nil, err
217	}
218	// TODO: We should be removing the task after this right?
219	return &task.DeleteResponse{
220		Pid:        uint32(pid),
221		ExitStatus: exitStatus,
222		ExitedAt:   exitedAt,
223	}, nil
224}
225
226func (s *service) pidsInternal(ctx context.Context, req *task.PidsRequest) (*task.PidsResponse, error) {
227	t, err := s.getTask(req.ID)
228	if err != nil {
229		return nil, err
230	}
231	pids, err := t.Pids(ctx)
232	if err != nil {
233		return nil, err
234	}
235	processes := make([]*containerd_v1_types.ProcessInfo, len(pids))
236	for i, p := range pids {
237		a, err := typeurl.MarshalAny(&p)
238		if err != nil {
239			return nil, errors.Wrapf(err, "failed to marshal ProcessDetails for process: %s, task: %s", p.ExecID, req.ID)
240		}
241		proc := &containerd_v1_types.ProcessInfo{
242			Pid:  p.ProcessID,
243			Info: a,
244		}
245		processes[i] = proc
246	}
247	return &task.PidsResponse{
248		Processes: processes,
249	}, nil
250}
251
252func (s *service) pauseInternal(ctx context.Context, req *task.PauseRequest) (*google_protobuf1.Empty, error) {
253	/*
254		s.events <- cdevent{
255			topic: runtime.TaskPausedEventTopic,
256			event: &eventstypes.TaskPaused{
257				req.ID,
258			},
259		}
260	*/
261	return nil, errdefs.ErrNotImplemented
262}
263
264func (s *service) resumeInternal(ctx context.Context, req *task.ResumeRequest) (*google_protobuf1.Empty, error) {
265	/*
266		s.events <- cdevent{
267			topic: runtime.TaskResumedEventTopic,
268			event: &eventstypes.TaskResumed{
269				req.ID,
270			},
271		}
272	*/
273	return nil, errdefs.ErrNotImplemented
274}
275
276func (s *service) checkpointInternal(ctx context.Context, req *task.CheckpointTaskRequest) (*google_protobuf1.Empty, error) {
277	return nil, errdefs.ErrNotImplemented
278}
279
280func (s *service) killInternal(ctx context.Context, req *task.KillRequest) (*google_protobuf1.Empty, error) {
281	if s.isSandbox {
282		pod, err := s.getPod()
283		if err != nil {
284			return nil, errors.Wrapf(errdefs.ErrNotFound, "%v: task with id: '%s' not found", err, req.ID)
285		}
286		// Send it to the POD and let it cascade on its own through all tasks.
287		err = pod.KillTask(ctx, req.ID, req.ExecID, req.Signal, req.All)
288		if err != nil {
289			return nil, err
290		}
291		return empty, nil
292	}
293	t, err := s.getTask(req.ID)
294	if err != nil {
295		return nil, err
296	}
297	// Send it to the task and let it cascade on its own through all exec's
298	err = t.KillExec(ctx, req.ExecID, req.Signal, req.All)
299	if err != nil {
300		return nil, err
301	}
302	return empty, nil
303}
304
305func (s *service) execInternal(ctx context.Context, req *task.ExecProcessRequest) (*google_protobuf1.Empty, error) {
306	t, err := s.getTask(req.ID)
307	if err != nil {
308		return nil, err
309	}
310	if req.Terminal && req.Stderr != "" {
311		return nil, errors.Wrap(errdefs.ErrFailedPrecondition, "if using terminal, stderr must be empty")
312	}
313	var spec specs.Process
314	if err := json.Unmarshal(req.Spec.Value, &spec); err != nil {
315		return nil, errors.Wrap(err, "request.Spec was not oci process")
316	}
317	err = t.CreateExec(ctx, req, &spec)
318	if err != nil {
319		return nil, err
320	}
321	return empty, nil
322}
323
324func (s *service) diagExecInHostInternal(ctx context.Context, req *shimdiag.ExecProcessRequest) (*shimdiag.ExecProcessResponse, error) {
325	if req.Terminal && req.Stderr != "" {
326		return nil, errors.Wrap(errdefs.ErrFailedPrecondition, "if using terminal, stderr must be empty")
327	}
328	t, err := s.getTask(s.tid)
329	if err != nil {
330		return nil, err
331	}
332	ec, err := t.ExecInHost(ctx, req)
333	if err != nil {
334		return nil, err
335	}
336	return &shimdiag.ExecProcessResponse{ExitCode: int32(ec)}, nil
337}
338
339func (s *service) diagShareInternal(ctx context.Context, req *shimdiag.ShareRequest) (*shimdiag.ShareResponse, error) {
340	t, err := s.getTask(s.tid)
341	if err != nil {
342		return nil, err
343	}
344	if err := t.Share(ctx, req); err != nil {
345		return nil, err
346	}
347	return &shimdiag.ShareResponse{}, nil
348}
349
350func (s *service) resizePtyInternal(ctx context.Context, req *task.ResizePtyRequest) (*google_protobuf1.Empty, error) {
351	t, err := s.getTask(req.ID)
352	if err != nil {
353		return nil, err
354	}
355	e, err := t.GetExec(req.ExecID)
356	if err != nil {
357		return nil, err
358	}
359	err = e.ResizePty(ctx, req.Width, req.Height)
360	if err != nil {
361		return nil, err
362	}
363	return empty, nil
364}
365
366func (s *service) closeIOInternal(ctx context.Context, req *task.CloseIORequest) (*google_protobuf1.Empty, error) {
367	t, err := s.getTask(req.ID)
368	if err != nil {
369		return nil, err
370	}
371	e, err := t.GetExec(req.ExecID)
372	if err != nil {
373		return nil, err
374	}
375	err = e.CloseIO(ctx, req.Stdin)
376	if err != nil {
377		return nil, err
378	}
379	return empty, nil
380}
381
382func (s *service) updateInternal(ctx context.Context, req *task.UpdateTaskRequest) (*google_protobuf1.Empty, error) {
383	return nil, errdefs.ErrNotImplemented
384}
385
386func (s *service) waitInternal(ctx context.Context, req *task.WaitRequest) (*task.WaitResponse, error) {
387	t, err := s.getTask(req.ID)
388	if err != nil {
389		return nil, err
390	}
391	var state *task.StateResponse
392	if req.ExecID != "" {
393		e, err := t.GetExec(req.ExecID)
394		if err != nil {
395			return nil, err
396		}
397		state = e.Wait()
398	} else {
399		state = t.Wait()
400	}
401	return &task.WaitResponse{
402		ExitStatus: state.ExitStatus,
403		ExitedAt:   state.ExitedAt,
404	}, nil
405}
406
407func (s *service) statsInternal(ctx context.Context, req *task.StatsRequest) (*task.StatsResponse, error) {
408	t, err := s.getTask(req.ID)
409	if err != nil {
410		return nil, err
411	}
412	stats, err := t.Stats(ctx)
413	if err != nil {
414		return nil, err
415	}
416	any, err := typeurl.MarshalAny(stats)
417	if err != nil {
418		return nil, errors.Wrapf(err, "failed to marshal Statistics for task: %s", req.ID)
419	}
420	return &task.StatsResponse{Stats: any}, nil
421}
422
423func (s *service) connectInternal(ctx context.Context, req *task.ConnectRequest) (*task.ConnectResponse, error) {
424	// We treat the shim/task as the same pid on the Windows host.
425	pid := uint32(os.Getpid())
426	return &task.ConnectResponse{
427		ShimPid: pid,
428		TaskPid: pid,
429	}, nil
430}
431
432func (s *service) shutdownInternal(ctx context.Context, req *task.ShutdownRequest) (*google_protobuf1.Empty, error) {
433	// Because a pod shim hosts multiple tasks only the init task can issue the
434	// shutdown request.
435	if req.ID != s.tid {
436		return empty, nil
437	}
438
439	if req.Now {
440		os.Exit(0)
441	}
442	// TODO: JTERRY75 if we dont use `now` issue a Shutdown to the ttrpc
443	// connection to drain any active requests.
444	os.Exit(0)
445	return empty, nil
446}
447