// +build windows /* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 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 }