containerd/windows/process.go
Kunal Kushwaha b12c3215a0 Licence header added
Signed-off-by: Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
2018-02-19 10:32:26 +09:00

234 lines
5.5 KiB
Go

// +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
}