// +build windows package windows import ( "context" "io" "sync" "syscall" "time" "github.com/Microsoft/hcsshim" eventstypes "github.com/containerd/containerd/api/events" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" "github.com/containerd/containerd/runtime" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // process implements containerd.Process and containerd.State type process struct { sync.Mutex hcs hcsshim.Process id string pid uint32 io *pipeSet task *task exitCh chan struct{} exitCode uint32 exitTime time.Time conf *hcsshim.ProcessConfig } func (p *process) ID() string { return p.id } func (p *process) State(ctx context.Context) (runtime.State, error) { return runtime.State{ Status: p.Status(), Pid: p.pid, Stdin: p.io.src.Stdin, Stdout: p.io.src.Stdout, Stderr: p.io.src.Stderr, Terminal: p.io.src.Terminal, ExitStatus: p.exitCode, }, nil } func (p *process) Status() runtime.Status { p.Lock() defer p.Unlock() if p.task.getStatus() == runtime.PausedStatus { return runtime.PausedStatus } var status runtime.Status select { case <-p.exitCh: status = runtime.StoppedStatus default: if p.hcs == nil { return runtime.CreatedStatus } status = runtime.RunningStatus } return status } func (p *process) Kill(ctx context.Context, sig uint32, all bool) error { // On windows all signals kill the process if p.Status() == runtime.CreatedStatus { return errors.Wrap(errdefs.ErrFailedPrecondition, "process was not started") } return errors.Wrap(p.hcs.Kill(), "failed to kill process") } func (p *process) ResizePty(ctx context.Context, size runtime.ConsoleSize) error { if p.Status() == runtime.CreatedStatus { return errors.Wrap(errdefs.ErrFailedPrecondition, "process was not started") } err := p.hcs.ResizeConsole(uint16(size.Width), uint16(size.Height)) return errors.Wrap(err, "failed to resize process console") } func (p *process) CloseIO(ctx context.Context) error { if p.Status() == runtime.CreatedStatus { return errors.Wrap(errdefs.ErrFailedPrecondition, "process was not started") } return errors.Wrap(p.hcs.CloseStdin(), "failed to close stdin") } func (p *process) Pid() uint32 { return p.pid } func (p *process) HcsPid() uint32 { return uint32(p.hcs.Pid()) } func (p *process) ExitCode() (uint32, time.Time, error) { if s := p.Status(); s != runtime.StoppedStatus && s != runtime.CreatedStatus { return 255, time.Time{}, errors.Wrapf(errdefs.ErrFailedPrecondition, "process is not stopped: %s", s) } return p.exitCode, p.exitTime, nil } func (p *process) Start(ctx context.Context) (err error) { p.Lock() defer p.Unlock() if p.hcs != nil { return errors.Wrap(errdefs.ErrFailedPrecondition, "process already started") } // If we fail, close the io right now defer func() { if err != nil { p.io.Close() } }() var hp hcsshim.Process if hp, err = p.task.hcsContainer.CreateProcess(p.conf); err != nil { return errors.Wrapf(err, "failed to create process") } stdin, stdout, stderr, err := hp.Stdio() if err != nil { hp.Kill() return errors.Wrapf(err, "failed to retrieve init process stdio") } ioCopy := func(name string, dst io.WriteCloser, src io.ReadCloser) { log.G(ctx).WithFields(logrus.Fields{"id": p.id, "pid": p.pid}). Debugf("%s: copy started", name) io.Copy(dst, src) log.G(ctx).WithFields(logrus.Fields{"id": p.id, "pid": p.pid}). Debugf("%s: copy done", name) dst.Close() src.Close() } if p.io.stdin != nil { go ioCopy("stdin", stdin, p.io.stdin) } if p.io.stdout != nil { go ioCopy("stdout", p.io.stdout, stdout) } if p.io.stderr != nil { go ioCopy("stderr", p.io.stderr, stderr) } p.hcs = hp // Wait for the process to exit to get the exit status go func() { if err := hp.Wait(); err != nil { herr, ok := err.(*hcsshim.ProcessError) if ok && herr.Err != syscall.ERROR_BROKEN_PIPE { log.G(ctx). WithError(err). WithFields(logrus.Fields{"id": p.id, "pid": p.pid}). Warnf("hcsshim wait failed (process may have been killed)") } // Try to get the exit code nonetheless } p.exitTime = time.Now() ec, err := hp.ExitCode() if err != nil { log.G(ctx). WithError(err). WithFields(logrus.Fields{"id": p.id, "pid": p.pid}). Warnf("hcsshim could not retrieve exit code") // Use the unknown exit code ec = 255 } p.exitCode = uint32(ec) p.task.publisher.Publish(ctx, runtime.TaskExitEventTopic, &eventstypes.TaskExit{ ContainerID: p.task.id, ID: p.id, Pid: p.pid, ExitStatus: p.exitCode, ExitedAt: p.exitTime, }) close(p.exitCh) // Ensure io's are closed p.io.Close() // Cleanup HCS resources hp.Close() }() return nil } func (p *process) Wait(ctx context.Context) (*runtime.Exit, error) { <-p.exitCh ec, ea, err := p.ExitCode() if err != nil { return nil, err } return &runtime.Exit{ Status: ec, Timestamp: ea, }, nil }