123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- package hcsshim
- import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "syscall"
- "time"
- "github.com/Sirupsen/logrus"
- )
- var (
- ErrInvalidProcessState = errors.New("the process is in an invalid state for the attempted operation")
- )
- type ProcessError struct {
- Process *process
- Operation string
- Err error
- }
- type process struct {
- handle hcsProcess
- processID int
- container *container
- cachedPipes *cachedPipes
- callbackNumber uintptr
- }
- type cachedPipes struct {
- stdIn syscall.Handle
- stdOut syscall.Handle
- stdErr syscall.Handle
- }
- 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
- }
- type processStatus struct {
- ProcessId uint32
- Exited bool
- ExitCode uint32
- LastWaitResult int32
- }
- const (
- stdIn string = "StdIn"
- stdOut string = "StdOut"
- stdErr string = "StdErr"
- )
- 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
- }
- // Kill signals the process to terminate but does not wait for it to finish terminating.
- func (process *process) Kill() error {
- operation := "Kill"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- var resultp *uint16
- err := hcsTerminateProcess(process.handle, &resultp)
- err = processHcsResult(err, resultp)
- if err == ErrVmcomputeOperationPending {
- return ErrVmcomputeOperationPending
- } else if err != nil {
- err := &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return nil
- }
- // Wait waits for the process to exit.
- func (process *process) Wait() error {
- operation := "Wait"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- if hcsCallbacksSupported {
- err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil)
- if err != nil {
- err := &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- } else {
- _, err := process.waitTimeoutInternal(syscall.INFINITE)
- if err != nil {
- err := &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- }
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return nil
- }
- // WaitTimeout waits for the process to exit or the duration to elapse. It returns
- // false if timeout occurs.
- func (process *process) WaitTimeout(timeout time.Duration) error {
- operation := "WaitTimeout"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- if hcsCallbacksSupported {
- err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, &timeout)
- if err == ErrTimeout {
- return ErrTimeout
- } else if err != nil {
- err := &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- } else {
- finished, err := waitTimeoutHelper(process, timeout)
- if !finished {
- return ErrTimeout
- } else if err != nil {
- err := &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- }
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return nil
- }
- func (process *process) hcsWait(timeout uint32) (bool, error) {
- var (
- resultp *uint16
- exitEvent syscall.Handle
- )
- err := hcsCreateProcessWait(process.handle, &exitEvent, &resultp)
- err = processHcsResult(err, resultp)
- if err != nil {
- return false, err
- }
- defer syscall.CloseHandle(exitEvent)
- return waitForSingleObject(exitEvent, timeout)
- }
- func (process *process) waitTimeoutInternal(timeout uint32) (bool, error) {
- return waitTimeoutInternalHelper(process, timeout)
- }
- // ExitCode returns the exit code of the process. The process must have
- // already terminated.
- func (process *process) ExitCode() (int, error) {
- operation := "ExitCode"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- properties, err := process.properties()
- if err != nil {
- err := &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return 0, err
- }
- if properties.Exited == false {
- return 0, ErrInvalidProcessState
- }
- logrus.Debugf(title+" succeeded processid=%d exitCode=%d", process.processID, properties.ExitCode)
- return int(properties.ExitCode), nil
- }
- // ResizeConsole resizes the console of the process.
- func (process *process) ResizeConsole(width, height uint16) error {
- operation := "ResizeConsole"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- modifyRequest := processModifyRequest{
- Operation: modifyConsoleSize,
- ConsoleSize: &consoleSize{
- Height: height,
- Width: width,
- },
- }
- modifyRequestb, err := json.Marshal(modifyRequest)
- if err != nil {
- return err
- }
- modifyRequestStr := string(modifyRequestb)
- var resultp *uint16
- err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp)
- err = processHcsResult(err, resultp)
- if err != nil {
- err := &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return nil
- }
- func (process *process) properties() (*processStatus, error) {
- operation := "properties"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- var (
- resultp *uint16
- propertiesp *uint16
- )
- err := hcsGetProcessProperties(process.handle, &propertiesp, &resultp)
- err = processHcsResult(err, resultp)
- if err != nil {
- err := &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return nil, err
- }
- if propertiesp == nil {
- return nil, errors.New("Unexpected result from hcsGetProcessProperties, properties should never be nil")
- }
- propertiesRaw := convertAndFreeCoTaskMemBytes(propertiesp)
- properties := &processStatus{}
- if err := json.Unmarshal(propertiesRaw, properties); err != nil {
- return nil, err
- }
- logrus.Debugf(title+" succeeded processid=%d, properties=%s", process.processID, propertiesRaw)
- return properties, nil
- }
- // Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
- // these pipes does not close the underlying pipes; it should be possible to
- // call this multiple times to get multiple interfaces.
- func (process *process) Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error) {
- operation := "Stdio"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- var stdIn, stdOut, stdErr syscall.Handle
- if process.cachedPipes == nil {
- var (
- processInfo hcsProcessInformation
- resultp *uint16
- )
- err := hcsGetProcessInfo(process.handle, &processInfo, &resultp)
- err = processHcsResult(err, resultp)
- if err != nil {
- err = &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return nil, nil, nil, err
- }
- stdIn, stdOut, stdErr = processInfo.StdInput, processInfo.StdOutput, processInfo.StdError
- } else {
- // Use cached pipes
- stdIn, stdOut, stdErr = process.cachedPipes.stdIn, process.cachedPipes.stdOut, process.cachedPipes.stdErr
- // Invalidate the cache
- process.cachedPipes = nil
- }
- pipes, err := makeOpenFiles([]syscall.Handle{stdIn, stdOut, stdErr})
- if err != nil {
- return nil, nil, nil, err
- }
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return pipes[0], pipes[1], pipes[2], nil
- }
- // 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.
- func (process *process) CloseStdin() error {
- operation := "CloseStdin"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- modifyRequest := processModifyRequest{
- Operation: modifyCloseHandle,
- CloseHandle: &closeHandle{
- Handle: stdIn,
- },
- }
- modifyRequestb, err := json.Marshal(modifyRequest)
- if err != nil {
- return err
- }
- modifyRequestStr := string(modifyRequestb)
- var resultp *uint16
- err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp)
- err = processHcsResult(err, resultp)
- if err != nil {
- err = &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return nil
- }
- // Close cleans up any state associated with the process but does not kill
- // or wait on it.
- func (process *process) Close() error {
- operation := "Close"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- // Don't double free this
- if process.handle == 0 {
- return nil
- }
- if hcsCallbacksSupported {
- if err := process.unregisterCallback(); err != nil {
- err = &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- }
- if err := hcsCloseProcess(process.handle); err != nil {
- err = &ProcessError{Operation: operation, Process: process, Err: err}
- logrus.Error(err)
- return err
- }
- process.handle = 0
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return nil
- }
- // closeProcess wraps process.Close for use by a finalizer
- func closeProcess(process *process) {
- process.Close()
- }
- func (process *process) registerCallback() error {
- callbackMapLock.Lock()
- defer callbackMapLock.Unlock()
- callbackNumber := nextCallback
- nextCallback++
- context := ¬ifcationWatcherContext{
- channels: newChannels(),
- }
- callbackMap[callbackNumber] = context
- var callbackHandle hcsCallback
- err := hcsRegisterProcessCallback(process.handle, notificationWatcherCallback, callbackNumber, &callbackHandle)
- if err != nil {
- return err
- }
- context.handle = callbackHandle
- process.callbackNumber = callbackNumber
- return nil
- }
- func (process *process) unregisterCallback() error {
- callbackNumber := process.callbackNumber
- callbackMapLock.Lock()
- defer callbackMapLock.Unlock()
- handle := callbackMap[callbackNumber].handle
- if handle == 0 {
- return nil
- }
- err := hcsUnregisterProcessCallback(handle)
- if err != nil {
- return err
- }
- callbackMap[callbackNumber] = nil
- handle = 0
- return nil
- }
- func (e *ProcessError) Error() string {
- if e == nil {
- return "<nil>"
- }
- if e.Process == nil {
- return "Unexpected nil process for error: " + e.Err.Error()
- }
- s := fmt.Sprintf("process %d", e.Process.processID)
- if e.Process.container != nil {
- s += " in container " + e.Process.container.id
- }
- if e.Operation != "" {
- s += " " + e.Operation
- }
- if e.Err != nil {
- s += fmt.Sprintf(" failed in Win32: %s (0x%x)", e.Err, win32FromError(e.Err))
- }
- return s
- }
|