k3s/vendor/github.com/Microsoft/hcsshim/internal/hcs/process.go

522 lines
14 KiB
Go
Raw Normal View History

2019-01-12 04:58:27 +00:00
package hcs
import (
2019-09-30 23:25:17 +00:00
"context"
2019-01-12 04:58:27 +00:00
"encoding/json"
"io"
"sync"
"syscall"
"time"
2019-09-30 23:25:17 +00:00
"github.com/Microsoft/hcsshim/internal/log"
"github.com/Microsoft/hcsshim/internal/oc"
"github.com/Microsoft/hcsshim/internal/vmcompute"
"go.opencensus.io/trace"
2019-01-12 04:58:27 +00:00
)
// ContainerError is an error encountered in HCS
type Process struct {
handleLock sync.RWMutex
2019-09-30 23:25:17 +00:00
handle vmcompute.HcsProcess
2019-01-12 04:58:27 +00:00
processID int
system *System
2020-08-10 17:43:49 +00:00
hasCachedStdio bool
stdioLock sync.Mutex
2019-09-30 23:25:17 +00:00
stdin io.WriteCloser
stdout io.ReadCloser
stderr io.ReadCloser
2019-01-12 04:58:27 +00:00
callbackNumber uintptr
2019-09-30 23:25:17 +00:00
closedWaitOnce sync.Once
waitBlock chan struct{}
exitCode int
waitError error
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
func newProcess(process vmcompute.HcsProcess, processID int, computeSystem *System) *Process {
2019-01-12 04:58:27 +00:00
return &Process{
handle: process,
processID: processID,
system: computeSystem,
2019-09-30 23:25:17 +00:00
waitBlock: make(chan struct{}),
2019-01-12 04:58:27 +00:00
}
}
type processModifyRequest struct {
Operation string
ConsoleSize *consoleSize `json:",omitempty"`
CloseHandle *closeHandle `json:",omitempty"`
}
type consoleSize struct {
Height uint16
Width uint16
}
type closeHandle struct {
Handle string
}
2019-09-30 23:25:17 +00:00
type processStatus struct {
2019-01-12 04:58:27 +00:00
ProcessID uint32
Exited bool
ExitCode uint32
LastWaitResult int32
}
const stdIn string = "StdIn"
2019-01-12 04:58:27 +00:00
const (
modifyConsoleSize string = "ConsoleSize"
modifyCloseHandle string = "CloseHandle"
)
// Pid returns the process ID of the process within the container.
func (process *Process) Pid() int {
return process.processID
}
// SystemID returns the ID of the process's compute system.
func (process *Process) SystemID() string {
return process.system.ID()
}
2019-09-30 23:25:17 +00:00
func (process *Process) processSignalResult(ctx context.Context, err error) (bool, error) {
switch err {
case nil:
return true, nil
case ErrVmcomputeOperationInvalidState, ErrComputeSystemDoesNotExist, ErrElementNotFound:
select {
case <-process.waitBlock:
// The process exit notification has already arrived.
default:
// The process should be gone, but we have not received the notification.
// After a second, force unblock the process wait to work around a possible
// deadlock in the HCS.
go func() {
time.Sleep(time.Second)
process.closedWaitOnce.Do(func() {
log.G(ctx).WithError(err).Warn("force unblocking process waits")
process.exitCode = -1
process.waitError = err
close(process.waitBlock)
})
}()
}
return false, nil
default:
return false, err
2019-01-12 04:58:27 +00:00
}
}
// Signal signals the process with `options`.
2019-09-30 23:25:17 +00:00
//
// For LCOW `guestrequest.SignalProcessOptionsLCOW`.
//
// For WCOW `guestrequest.SignalProcessOptionsWCOW`.
func (process *Process) Signal(ctx context.Context, options interface{}) (bool, error) {
2019-01-12 04:58:27 +00:00
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "hcs::Process::Signal"
2019-01-12 04:58:27 +00:00
if process.handle == 0 {
2019-09-30 23:25:17 +00:00
return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
2019-01-12 04:58:27 +00:00
}
optionsb, err := json.Marshal(options)
if err != nil {
2019-09-30 23:25:17 +00:00
return false, err
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
resultJSON, err := vmcompute.HcsSignalProcess(ctx, process.handle, string(optionsb))
events := processHcsResult(ctx, resultJSON)
delivered, err := process.processSignalResult(ctx, err)
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-30 23:25:17 +00:00
err = makeProcessError(process, operation, err, events)
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
return delivered, err
2019-01-12 04:58:27 +00:00
}
// Kill signals the process to terminate but does not wait for it to finish terminating.
2019-09-30 23:25:17 +00:00
func (process *Process) Kill(ctx context.Context) (bool, error) {
2019-01-12 04:58:27 +00:00
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "hcs::Process::Kill"
2019-01-12 04:58:27 +00:00
if process.handle == 0 {
2019-09-30 23:25:17 +00:00
return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
resultJSON, err := vmcompute.HcsTerminateProcess(ctx, process.handle)
events := processHcsResult(ctx, resultJSON)
delivered, err := process.processSignalResult(ctx, err)
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-30 23:25:17 +00:00
err = makeProcessError(process, operation, err, events)
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
return delivered, err
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
// waitBackground waits for the process exit notification. Once received sets
// `process.waitError` (if any) and unblocks all `Wait` calls.
//
// This MUST be called exactly once per `process.handle` but `Wait` is safe to
// call multiple times.
func (process *Process) waitBackground() {
operation := "hcs::Process::waitBackground"
2019-09-30 23:25:17 +00:00
ctx, span := trace.StartSpan(context.Background(), operation)
defer span.End()
span.AddAttributes(
trace.StringAttribute("cid", process.SystemID()),
trace.Int64Attribute("pid", int64(process.processID)))
var (
err error
exitCode = -1
propertiesJSON string
resultJSON string
2019-09-30 23:25:17 +00:00
)
2019-01-12 04:58:27 +00:00
2019-09-30 23:25:17 +00:00
err = waitForNotification(ctx, process.callbackNumber, hcsNotificationProcessExited, nil)
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-30 23:25:17 +00:00
err = makeProcessError(process, operation, err, nil)
log.G(ctx).WithError(err).Error("failed wait")
} else {
process.handleLock.RLock()
defer process.handleLock.RUnlock()
// Make sure we didnt race with Close() here
if process.handle != 0 {
propertiesJSON, resultJSON, err = vmcompute.HcsGetProcessProperties(ctx, process.handle)
2019-09-30 23:25:17 +00:00
events := processHcsResult(ctx, resultJSON)
if err != nil {
err = makeProcessError(process, operation, err, events) //nolint:ineffassign
2019-09-30 23:25:17 +00:00
} else {
properties := &processStatus{}
err = json.Unmarshal([]byte(propertiesJSON), properties)
if err != nil {
err = makeProcessError(process, operation, err, nil) //nolint:ineffassign
2019-09-30 23:25:17 +00:00
} else {
if properties.LastWaitResult != 0 {
log.G(ctx).WithField("wait-result", properties.LastWaitResult).Warning("non-zero last wait result")
} else {
exitCode = int(properties.ExitCode)
}
}
}
}
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
log.G(ctx).WithField("exitCode", exitCode).Debug("process exited")
2019-01-12 04:58:27 +00:00
2019-09-30 23:25:17 +00:00
process.closedWaitOnce.Do(func() {
process.exitCode = exitCode
process.waitError = err
close(process.waitBlock)
})
oc.SetSpanStatus(span, err)
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
// Wait waits for the process to exit. If the process has already exited returns
// the pervious error (if any).
func (process *Process) Wait() error {
<-process.waitBlock
return process.waitError
2019-01-12 04:58:27 +00:00
}
// ResizeConsole resizes the console of the process.
2019-09-30 23:25:17 +00:00
func (process *Process) ResizeConsole(ctx context.Context, width, height uint16) error {
2019-01-12 04:58:27 +00:00
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "hcs::Process::ResizeConsole"
2019-01-12 04:58:27 +00:00
if process.handle == 0 {
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
}
modifyRequest := processModifyRequest{
Operation: modifyConsoleSize,
ConsoleSize: &consoleSize{
Height: height,
Width: width,
},
}
modifyRequestb, err := json.Marshal(modifyRequest)
if err != nil {
return err
}
2019-09-30 23:25:17 +00:00
resultJSON, err := vmcompute.HcsModifyProcess(ctx, process.handle, string(modifyRequestb))
events := processHcsResult(ctx, resultJSON)
2019-01-12 04:58:27 +00:00
if err != nil {
return makeProcessError(process, operation, err, events)
}
return nil
}
// ExitCode returns the exit code of the process. The process must have
// already terminated.
2019-09-30 23:25:17 +00:00
func (process *Process) ExitCode() (int, error) {
select {
case <-process.waitBlock:
if process.waitError != nil {
return -1, process.waitError
}
return process.exitCode, nil
default:
return -1, makeProcessError(process, "hcs::Process::ExitCode", ErrInvalidProcessState, nil)
2019-01-12 04:58:27 +00:00
}
}
2019-09-30 23:25:17 +00:00
// StdioLegacy returns the stdin, stdout, and stderr pipes, respectively. Closing
2020-08-10 17:43:49 +00:00
// these pipes does not close the underlying pipes. Once returned, these pipes
// are the responsibility of the caller to close.
2019-09-30 23:25:17 +00:00
func (process *Process) StdioLegacy() (_ io.WriteCloser, _ io.ReadCloser, _ io.ReadCloser, err error) {
operation := "hcs::Process::StdioLegacy"
2019-09-30 23:25:17 +00:00
ctx, span := trace.StartSpan(context.Background(), operation)
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()
span.AddAttributes(
trace.StringAttribute("cid", process.SystemID()),
trace.Int64Attribute("pid", int64(process.processID)))
2019-01-12 04:58:27 +00:00
process.handleLock.RLock()
defer process.handleLock.RUnlock()
if process.handle == 0 {
return nil, nil, nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
}
2020-08-10 17:43:49 +00:00
process.stdioLock.Lock()
defer process.stdioLock.Unlock()
if process.hasCachedStdio {
stdin, stdout, stderr := process.stdin, process.stdout, process.stderr
process.stdin, process.stdout, process.stderr = nil, nil, nil
process.hasCachedStdio = false
return stdin, stdout, stderr, nil
}
2019-09-30 23:25:17 +00:00
processInfo, resultJSON, err := vmcompute.HcsGetProcessInfo(ctx, process.handle)
events := processHcsResult(ctx, resultJSON)
if err != nil {
return nil, nil, nil, makeProcessError(process, operation, err, events)
2019-01-12 04:58:27 +00:00
}
2019-09-30 23:25:17 +00:00
pipes, err := makeOpenFiles([]syscall.Handle{processInfo.StdInput, processInfo.StdOutput, processInfo.StdError})
2019-01-12 04:58:27 +00:00
if err != nil {
return nil, nil, nil, makeProcessError(process, operation, err, nil)
}
return pipes[0], pipes[1], pipes[2], nil
}
2019-09-30 23:25:17 +00:00
// Stdio returns the stdin, stdout, and stderr pipes, respectively.
// To close them, close the process handle.
func (process *Process) Stdio() (stdin io.Writer, stdout, stderr io.Reader) {
2020-08-10 17:43:49 +00:00
process.stdioLock.Lock()
defer process.stdioLock.Unlock()
2019-09-30 23:25:17 +00:00
return process.stdin, process.stdout, process.stderr
}
2019-01-12 04:58:27 +00:00
// CloseStdin closes the write side of the stdin pipe so that the process is
// notified on the read side that there is no more data in stdin.
2019-09-30 23:25:17 +00:00
func (process *Process) CloseStdin(ctx context.Context) error {
2019-01-12 04:58:27 +00:00
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "hcs::Process::CloseStdin"
2019-01-12 04:58:27 +00:00
if process.handle == 0 {
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
}
modifyRequest := processModifyRequest{
Operation: modifyCloseHandle,
CloseHandle: &closeHandle{
Handle: stdIn,
},
}
modifyRequestb, err := json.Marshal(modifyRequest)
if err != nil {
return err
}
2019-09-30 23:25:17 +00:00
resultJSON, err := vmcompute.HcsModifyProcess(ctx, process.handle, string(modifyRequestb))
events := processHcsResult(ctx, resultJSON)
2019-01-12 04:58:27 +00:00
if err != nil {
return makeProcessError(process, operation, err, events)
}
2020-08-10 17:43:49 +00:00
process.stdioLock.Lock()
2019-09-30 23:25:17 +00:00
if process.stdin != nil {
process.stdin.Close()
2020-08-10 17:43:49 +00:00
process.stdin = nil
2019-09-30 23:25:17 +00:00
}
2020-08-10 17:43:49 +00:00
process.stdioLock.Unlock()
2019-01-12 04:58:27 +00:00
return nil
}
func (process *Process) CloseStdout(ctx context.Context) (err error) {
ctx, span := trace.StartSpan(ctx, "hcs::Process::CloseStdout") //nolint:ineffassign,staticcheck
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()
span.AddAttributes(
trace.StringAttribute("cid", process.SystemID()),
trace.Int64Attribute("pid", int64(process.processID)))
process.handleLock.Lock()
defer process.handleLock.Unlock()
if process.handle == 0 {
return nil
}
process.stdioLock.Lock()
defer process.stdioLock.Unlock()
if process.stdout != nil {
process.stdout.Close()
process.stdout = nil
}
return nil
}
func (process *Process) CloseStderr(ctx context.Context) (err error) {
ctx, span := trace.StartSpan(ctx, "hcs::Process::CloseStderr") //nolint:ineffassign,staticcheck
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()
span.AddAttributes(
trace.StringAttribute("cid", process.SystemID()),
trace.Int64Attribute("pid", int64(process.processID)))
process.handleLock.Lock()
defer process.handleLock.Unlock()
if process.handle == 0 {
return nil
}
process.stdioLock.Lock()
defer process.stdioLock.Unlock()
if process.stderr != nil {
process.stderr.Close()
process.stderr = nil
}
return nil
}
2019-01-12 04:58:27 +00:00
// Close cleans up any state associated with the process but does not kill
// or wait on it.
func (process *Process) Close() (err error) {
operation := "hcs::Process::Close"
2019-09-30 23:25:17 +00:00
ctx, span := trace.StartSpan(context.Background(), operation)
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()
span.AddAttributes(
trace.StringAttribute("cid", process.SystemID()),
trace.Int64Attribute("pid", int64(process.processID)))
2019-01-12 04:58:27 +00:00
process.handleLock.Lock()
defer process.handleLock.Unlock()
// Don't double free this
if process.handle == 0 {
return nil
}
2020-08-10 17:43:49 +00:00
process.stdioLock.Lock()
2019-09-30 23:25:17 +00:00
if process.stdin != nil {
process.stdin.Close()
2020-08-10 17:43:49 +00:00
process.stdin = nil
2019-09-30 23:25:17 +00:00
}
if process.stdout != nil {
process.stdout.Close()
2020-08-10 17:43:49 +00:00
process.stdout = nil
2019-09-30 23:25:17 +00:00
}
if process.stderr != nil {
process.stderr.Close()
2020-08-10 17:43:49 +00:00
process.stderr = nil
2019-09-30 23:25:17 +00:00
}
2020-08-10 17:43:49 +00:00
process.stdioLock.Unlock()
2019-09-30 23:25:17 +00:00
if err = process.unregisterCallback(ctx); err != nil {
2019-01-12 04:58:27 +00:00
return makeProcessError(process, operation, err, nil)
}
2019-09-30 23:25:17 +00:00
if err = vmcompute.HcsCloseProcess(ctx, process.handle); err != nil {
2019-01-12 04:58:27 +00:00
return makeProcessError(process, operation, err, nil)
}
process.handle = 0
2019-09-30 23:25:17 +00:00
process.closedWaitOnce.Do(func() {
process.exitCode = -1
process.waitError = ErrAlreadyClosed
close(process.waitBlock)
})
2019-01-12 04:58:27 +00:00
return nil
}
2019-09-30 23:25:17 +00:00
func (process *Process) registerCallback(ctx context.Context) error {
callbackContext := &notificationWatcherContext{
2019-09-30 23:25:17 +00:00
channels: newProcessChannels(),
systemID: process.SystemID(),
processID: process.processID,
2019-01-12 04:58:27 +00:00
}
callbackMapLock.Lock()
callbackNumber := nextCallback
nextCallback++
2019-09-30 23:25:17 +00:00
callbackMap[callbackNumber] = callbackContext
2019-01-12 04:58:27 +00:00
callbackMapLock.Unlock()
2019-09-30 23:25:17 +00:00
callbackHandle, err := vmcompute.HcsRegisterProcessCallback(ctx, process.handle, notificationWatcherCallback, callbackNumber)
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2019-09-30 23:25:17 +00:00
callbackContext.handle = callbackHandle
2019-01-12 04:58:27 +00:00
process.callbackNumber = callbackNumber
return nil
}
2019-09-30 23:25:17 +00:00
func (process *Process) unregisterCallback(ctx context.Context) error {
2019-01-12 04:58:27 +00:00
callbackNumber := process.callbackNumber
callbackMapLock.RLock()
2019-09-30 23:25:17 +00:00
callbackContext := callbackMap[callbackNumber]
2019-01-12 04:58:27 +00:00
callbackMapLock.RUnlock()
2019-09-30 23:25:17 +00:00
if callbackContext == nil {
2019-01-12 04:58:27 +00:00
return nil
}
2019-09-30 23:25:17 +00:00
handle := callbackContext.handle
2019-01-12 04:58:27 +00:00
if handle == 0 {
return nil
}
2019-09-30 23:25:17 +00:00
// vmcompute.HcsUnregisterProcessCallback has its own synchronization to
// wait for all callbacks to complete. We must NOT hold the callbackMapLock.
err := vmcompute.HcsUnregisterProcessCallback(ctx, handle)
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2019-09-30 23:25:17 +00:00
closeChannels(callbackContext.channels)
2019-01-12 04:58:27 +00:00
callbackMapLock.Lock()
2019-09-30 23:25:17 +00:00
delete(callbackMap, callbackNumber)
2019-01-12 04:58:27 +00:00
callbackMapLock.Unlock()
handle = 0 //nolint:ineffassign
2019-01-12 04:58:27 +00:00
return nil
}