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