123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- package hcsshim
- import (
- "encoding/json"
- "io"
- "sync"
- "syscall"
- "time"
- "github.com/Sirupsen/logrus"
- )
- // ContainerError is an error encountered in HCS
- type process struct {
- handleLock sync.RWMutex
- 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 {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "Kill"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- if process.handle == 0 {
- return makeProcessError(process, operation, "", ErrAlreadyClosed)
- }
- var resultp *uint16
- err := hcsTerminateProcess(process.handle, &resultp)
- err = processHcsResult(err, resultp)
- if err != nil {
- return makeProcessError(process, operation, "", 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)
- err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil)
- if err != nil {
- return makeProcessError(process, operation, "", 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)
- err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, &timeout)
- if err != nil {
- return makeProcessError(process, operation, "", err)
- }
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return nil
- }
- // ExitCode returns the exit code of the process. The process must have
- // already terminated.
- func (process *process) ExitCode() (int, error) {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "ExitCode"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- if process.handle == 0 {
- return 0, makeProcessError(process, operation, "", ErrAlreadyClosed)
- }
- properties, err := process.properties()
- if err != nil {
- return 0, makeProcessError(process, operation, "", err)
- }
- if properties.Exited == false {
- return 0, makeProcessError(process, operation, "", ErrInvalidProcessState)
- }
- if properties.LastWaitResult != 0 {
- return 0, makeProcessError(process, operation, "", syscall.Errno(properties.LastWaitResult))
- }
- 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 {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "ResizeConsole"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- if process.handle == 0 {
- return makeProcessError(process, operation, "", ErrAlreadyClosed)
- }
- 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 {
- return makeProcessError(process, operation, "", 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 {
- return nil, err
- }
- if propertiesp == nil {
- return nil, ErrUnexpectedValue
- }
- 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) {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "Stdio"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- if process.handle == 0 {
- return nil, nil, nil, makeProcessError(process, operation, "", ErrAlreadyClosed)
- }
- 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 {
- return nil, nil, nil, makeProcessError(process, operation, "", 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, makeProcessError(process, operation, "", 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 {
- process.handleLock.RLock()
- defer process.handleLock.RUnlock()
- operation := "CloseStdin"
- title := "HCSShim::Process::" + operation
- logrus.Debugf(title+" processid=%d", process.processID)
- if process.handle == 0 {
- return makeProcessError(process, operation, "", ErrAlreadyClosed)
- }
- 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 {
- return makeProcessError(process, operation, "", 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 {
- process.handleLock.Lock()
- defer process.handleLock.Unlock()
- 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 err := process.unregisterCallback(); err != nil {
- return makeProcessError(process, operation, "", err)
- }
- if err := hcsCloseProcess(process.handle); err != nil {
- return makeProcessError(process, operation, "", err)
- }
- process.handle = 0
- logrus.Debugf(title+" succeeded processid=%d", process.processID)
- return nil
- }
- func (process *process) registerCallback() error {
- context := ¬ifcationWatcherContext{
- channels: newChannels(),
- }
- 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()
- callbackMap[callbackNumber] = nil
- callbackMapLock.Unlock()
- handle = 0
- return nil
- }
|