123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- package hcs
- import (
- "encoding/json"
- "io"
- "sync"
- "syscall"
- "time"
- "github.com/Microsoft/hcsshim/internal/interop"
- "github.com/Microsoft/hcsshim/internal/logfields"
- "github.com/sirupsen/logrus"
- )
- // ContainerError is an error encountered in HCS
- type Process struct {
- handleLock sync.RWMutex
- handle hcsProcess
- processID int
- system *System
- cachedPipes *cachedPipes
- callbackNumber uintptr
- logctx logrus.Fields
- closedWaitOnce sync.Once
- waitBlock chan struct{}
- waitError error
- }
- func newProcess(process hcsProcess, processID int, computeSystem *System) *Process {
- return &Process{
- handle: process,
- processID: processID,
- system: computeSystem,
- logctx: logrus.Fields{
- logfields.ContainerID: computeSystem.ID(),
- logfields.ProcessID: processID,
- },
- waitBlock: make(chan struct{}),
- }
- }
- 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
- }
- // SystemID returns the ID of the process's compute system.
- func (process *Process) SystemID() string {
- return process.system.ID()
- }
- func (process *Process) logOperationBegin(operation string) {
- logOperationBegin(
- process.logctx,
- operation+" - Begin Operation")
- }
- func (process *Process) logOperationEnd(operation string, err error) {
- var result string
- if err == nil {
- result = "Success"
- } else {
- result = "Error"
- }
- logOperationEnd(
- process.logctx,
- operation+" - End Operation - "+result,
- err)
- }
- // Signal signals the process with `options`.
- //
- // For LCOW `guestrequest.SignalProcessOptionsLCOW`.
- //
- // For WCOW `guestrequest.SignalProcessOptionsWCOW`.
- func (process *Process) Signal(options interface{}) (err error) {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "hcsshim::Process::Signal"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- if process.handle == 0 {
- return makeProcessError(process, operation, ErrAlreadyClosed, nil)
- }
- optionsb, err := json.Marshal(options)
- if err != nil {
- return err
- }
- optionsStr := string(optionsb)
- var resultp *uint16
- syscallWatcher(process.logctx, func() {
- err = hcsSignalProcess(process.handle, optionsStr, &resultp)
- })
- events := processHcsResult(resultp)
- if err != nil {
- return makeProcessError(process, operation, err, events)
- }
- return nil
- }
- // Kill signals the process to terminate but does not wait for it to finish terminating.
- func (process *Process) Kill() (err error) {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "hcsshim::Process::Kill"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- if process.handle == 0 {
- return makeProcessError(process, operation, ErrAlreadyClosed, nil)
- }
- var resultp *uint16
- syscallWatcher(process.logctx, func() {
- err = hcsTerminateProcess(process.handle, &resultp)
- })
- events := processHcsResult(resultp)
- if err != nil {
- return makeProcessError(process, operation, err, events)
- }
- return nil
- }
- // waitBackground waits for the process exit notification. Once received sets
- // `process.waitError` (if any) and unblocks all `Wait` and `WaitTimeout` calls.
- //
- // This MUST be called exactly once per `process.handle` but `Wait` and
- // `WaitTimeout` are safe to call multiple times.
- func (process *Process) waitBackground() {
- process.waitError = waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil)
- process.closedWaitOnce.Do(func() {
- close(process.waitBlock)
- })
- }
- // Wait waits for the process to exit. If the process has already exited returns
- // the pervious error (if any).
- func (process *Process) Wait() (err error) {
- operation := "hcsshim::Process::Wait"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- <-process.waitBlock
- if process.waitError != nil {
- return makeProcessError(process, operation, process.waitError, nil)
- }
- return nil
- }
- // WaitTimeout waits for the process to exit or the duration to elapse. If the
- // process has already exited returns the pervious error (if any). If a timeout
- // occurs returns `ErrTimeout`.
- func (process *Process) WaitTimeout(timeout time.Duration) (err error) {
- operation := "hcssshim::Process::WaitTimeout"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- select {
- case <-process.waitBlock:
- if process.waitError != nil {
- return makeProcessError(process, operation, process.waitError, nil)
- }
- return nil
- case <-time.After(timeout):
- return makeProcessError(process, operation, ErrTimeout, nil)
- }
- }
- // ResizeConsole resizes the console of the process.
- func (process *Process) ResizeConsole(width, height uint16) (err error) {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "hcsshim::Process::ResizeConsole"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- 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
- }
- modifyRequestStr := string(modifyRequestb)
- var resultp *uint16
- err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp)
- events := processHcsResult(resultp)
- if err != nil {
- return makeProcessError(process, operation, err, events)
- }
- return nil
- }
- func (process *Process) Properties() (_ *ProcessStatus, err error) {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "hcsshim::Process::Properties"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- if process.handle == 0 {
- return nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
- }
- var (
- resultp *uint16
- propertiesp *uint16
- )
- syscallWatcher(process.logctx, func() {
- err = hcsGetProcessProperties(process.handle, &propertiesp, &resultp)
- })
- events := processHcsResult(resultp)
- if err != nil {
- return nil, makeProcessError(process, operation, err, events)
- }
- if propertiesp == nil {
- return nil, ErrUnexpectedValue
- }
- propertiesRaw := interop.ConvertAndFreeCoTaskMemBytes(propertiesp)
- properties := &ProcessStatus{}
- if err := json.Unmarshal(propertiesRaw, properties); err != nil {
- return nil, makeProcessError(process, operation, err, nil)
- }
- return properties, nil
- }
- // ExitCode returns the exit code of the process. The process must have
- // already terminated.
- func (process *Process) ExitCode() (_ int, err error) {
- operation := "hcsshim::Process::ExitCode"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- properties, err := process.Properties()
- if err != nil {
- return -1, makeProcessError(process, operation, err, nil)
- }
- if properties.Exited == false {
- return -1, makeProcessError(process, operation, ErrInvalidProcessState, nil)
- }
- if properties.LastWaitResult != 0 {
- logrus.WithFields(logrus.Fields{
- logfields.ContainerID: process.SystemID(),
- logfields.ProcessID: process.processID,
- "wait-result": properties.LastWaitResult,
- }).Warn("hcsshim::Process::ExitCode - Non-zero last wait result")
- return -1, nil
- }
- return int(properties.ExitCode), 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, err error) {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "hcsshim::Process::Stdio"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- if process.handle == 0 {
- return nil, nil, nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
- }
- var stdIn, stdOut, stdErr syscall.Handle
- if process.cachedPipes == nil {
- var (
- processInfo hcsProcessInformation
- resultp *uint16
- )
- err = hcsGetProcessInfo(process.handle, &processInfo, &resultp)
- events := processHcsResult(resultp)
- if err != nil {
- return nil, nil, nil, makeProcessError(process, operation, err, events)
- }
- 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, makeProcessError(process, operation, err, nil)
- }
- 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() (err error) {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "hcsshim::Process::CloseStdin"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- 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
- }
- modifyRequestStr := string(modifyRequestb)
- var resultp *uint16
- err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp)
- events := processHcsResult(resultp)
- if err != nil {
- return makeProcessError(process, operation, err, events)
- }
- return nil
- }
- // Close cleans up any state associated with the process but does not kill
- // or wait on it.
- func (process *Process) Close() (err error) {
- process.handleLock.Lock()
- defer process.handleLock.Unlock()
- operation := "hcsshim::Process::Close"
- process.logOperationBegin(operation)
- defer func() { process.logOperationEnd(operation, err) }()
- // Don't double free this
- if process.handle == 0 {
- return nil
- }
- if err = process.unregisterCallback(); err != nil {
- return makeProcessError(process, operation, err, nil)
- }
- if err = hcsCloseProcess(process.handle); err != nil {
- return makeProcessError(process, operation, err, nil)
- }
- process.handle = 0
- process.closedWaitOnce.Do(func() {
- close(process.waitBlock)
- })
- return nil
- }
- func (process *Process) registerCallback() error {
- context := ¬ifcationWatcherContext{
- channels: newProcessChannels(),
- systemID: process.SystemID(),
- processID: process.processID,
- }
- callbackMapLock.Lock()
- callbackNumber := nextCallback
- nextCallback++
- callbackMap[callbackNumber] = context
- callbackMapLock.Unlock()
- 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.RLock()
- context := callbackMap[callbackNumber]
- callbackMapLock.RUnlock()
- if context == nil {
- return nil
- }
- handle := context.handle
- if handle == 0 {
- return nil
- }
- // hcsUnregisterProcessCallback has its own syncronization
- // to wait for all callbacks to complete. We must NOT hold the callbackMapLock.
- err := hcsUnregisterProcessCallback(handle)
- if err != nil {
- return err
- }
- closeChannels(context.channels)
- callbackMapLock.Lock()
- delete(callbackMap, callbackNumber)
- callbackMapLock.Unlock()
- handle = 0
- return nil
- }
|