github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/plugin/builtin/shell/subtree_windows.go (about)

     1  package shell
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"syscall"
     7  	"unsafe"
     8  
     9  	"github.com/evergreen-ci/evergreen/plugin"
    10  	"github.com/mongodb/grip/slogger"
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  const (
    15  	ERROR_SUCCESS syscall.Errno = 0
    16  
    17  	DELETE                   = 0x00010000
    18  	READ_CONTROL             = 0x00020000
    19  	WRITE_DAC                = 0x00040000
    20  	WRITE_OWNER              = 0x00080000
    21  	SYNCHRONIZE              = 0x00100000
    22  	STANDARD_RIGHTS_REQUIRED = 0x000F0000
    23  	STANDARD_RIGHTS_READ     = READ_CONTROL
    24  	STANDARD_RIGHTS_WRITE    = READ_CONTROL
    25  	STANDARD_RIGHTS_EXECUTE  = READ_CONTROL
    26  	STANDARD_RIGHTS_ALL      = 0x001F0000
    27  	SPECIFIC_RIGHTS_ALL      = 0x0000FFFF
    28  	ACCESS_SYSTEM_SECURITY   = 0x01000000
    29  	MAXIMUM_ALLOWED          = 0x02000000
    30  
    31  	// Constants for process permissions
    32  	PROCESS_TERMINATE                 = 0x0001
    33  	PROCESS_CREATE_THREAD             = 0x0002
    34  	PROCESS_SET_SESSIONID             = 0x0004
    35  	PROCESS_VM_OPERATION              = 0x0008
    36  	PROCESS_VM_READ                   = 0x0010
    37  	PROCESS_VM_WRITE                  = 0x0020
    38  	PROCESS_DUP_HANDLE                = 0x0040
    39  	PROCESS_CREATE_PROCESS            = 0x0080
    40  	PROCESS_SET_QUOTA                 = 0x0100
    41  	PROCESS_SET_INFORMATION           = 0x0200
    42  	PROCESS_QUERY_INFORMATION         = 0x0400
    43  	PROCESS_SUSPEND_RESUME            = 0x0800
    44  	PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
    45  	PROCESS_ALL_ACCESS                = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF
    46  )
    47  
    48  var (
    49  	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
    50  	modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
    51  
    52  	procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject")
    53  	procCloseHandle              = modkernel32.NewProc("CloseHandle")
    54  	procCreateJobObjectW         = modkernel32.NewProc("CreateJobObjectW")
    55  	procOpenProcess              = modkernel32.NewProc("OpenProcess")
    56  	procTerminateJobObject       = modkernel32.NewProc("TerminateJobObject")
    57  
    58  	processMapping = newProcessRegistry()
    59  )
    60  
    61  ////////////////////////////////////////////////////////////////////////
    62  //
    63  // implementation of the internals of our process mapping registry
    64  //
    65  ////////////////////////////////////////////////////////////////////////
    66  
    67  type processRegistry struct {
    68  	jobs map[string]*Job
    69  	mu   sync.Mutex
    70  }
    71  
    72  func newProcessRegistry() *processRegistry {
    73  	return &processRegistry{
    74  		jobs: make(map[string]*Job),
    75  	}
    76  }
    77  
    78  func (r *processRegistry) getJob(taskId string) (*Job, error) {
    79  	r.mu.Lock()
    80  	defer r.mu.Unlock()
    81  
    82  	j, ok := r.jobs[taskId]
    83  	if !ok {
    84  		var err error
    85  		j, err = NewJob(taskId)
    86  		if err != nil {
    87  			return nil, errors.Wrapf(err, "problem creating job object for %s", taskId)
    88  		}
    89  
    90  		r.jobs[taskId] = j
    91  
    92  		return j, nil
    93  	}
    94  
    95  	return j, nil
    96  }
    97  
    98  func (r *processRegistry) removeJob(taskId string) error {
    99  	r.mu.Lock()
   100  	defer r.mu.Unlock()
   101  
   102  	job, ok := r.jobs[taskId]
   103  	if !ok {
   104  		return nil
   105  	}
   106  
   107  	var err error
   108  	defer func() {
   109  		err = errors.Wrapf(job.Close(), "problem closing job for task %s", taskId)
   110  	}()
   111  
   112  	delete(r.jobs, taskId)
   113  
   114  	return err
   115  }
   116  
   117  ////////////////////////////////////////////////////////////////////////
   118  //
   119  // Functions used to manage processes used by the shell command
   120  //
   121  ////////////////////////////////////////////////////////////////////////
   122  
   123  // This windows-specific specific implementation of trackProcess associates the given pid with a
   124  // job object, which can later be used by "cleanup" to terminate all members of the job object at
   125  // once. If a job object doesn't already exist, it will create one automatically, scoped by the
   126  // task ID for which the shell process was started.
   127  func trackProcess(taskId string, pid int, log plugin.Logger) error {
   128  	job, err := processMapping.getJob(taskId)
   129  	if err != nil {
   130  		log.LogSystem(slogger.ERROR, "failed creating job object: %v", err)
   131  		return errors.WithStack(err)
   132  	}
   133  
   134  	log.LogSystem(slogger.INFO, "tracking process with pid %v", pid)
   135  
   136  	if err = job.AssignProcess(uint(pid)); err != nil {
   137  		log.LogSystem(slogger.ERROR, "failed assigning process %v to job object: %v", pid, err)
   138  	}
   139  
   140  	return err
   141  }
   142  
   143  // cleanup() has a windows-specific implementation which finds the job object associated with the
   144  // given task key, and if it exists, terminates it. This will guarantee that any shell processes
   145  // started throughout the task run are destroyed, as long as they were captured in trackProcess.
   146  func cleanup(key string, log plugin.Logger) error {
   147  	job, err := processMapping.getJob(key)
   148  	if err != nil {
   149  		return nil
   150  	}
   151  
   152  	if err := job.Terminate(0); err != nil {
   153  		log.LogSystem(slogger.ERROR, "terminating job object [%s] failed: %v", key, err)
   154  		return errors.WithStack(err)
   155  	}
   156  
   157  	return errors.Wrap(processMapping.removeJob(key),
   158  		"problem removing job object from internal evergreen tracking mechanism")
   159  }
   160  
   161  ///////////////////////////////////////////////////////////////////////////////////////////
   162  //
   163  // All the methods below are boilerplate functions for accessing the Windows syscalls for
   164  // working with Job objects.
   165  //
   166  ///////////////////////////////////////////////////////////////////////////////////////////
   167  
   168  type Job struct {
   169  	handle syscall.Handle
   170  }
   171  
   172  func NewJob(name string) (*Job, error) {
   173  	hJob, err := CreateJobObject(syscall.StringToUTF16Ptr(name))
   174  	if err != nil {
   175  		return nil, NewWindowsError("CreateJobObject", err)
   176  	}
   177  	return &Job{handle: hJob}, nil
   178  }
   179  
   180  func (self *Job) AssignProcess(pid uint) error {
   181  	hProcess, err := OpenProcess(PROCESS_ALL_ACCESS, false, uint32(pid))
   182  	if err != nil {
   183  		return NewWindowsError("OpenProcess", err)
   184  	}
   185  	defer CloseHandle(hProcess)
   186  	if err := AssignProcessToJobObject(self.handle, hProcess); err != nil {
   187  		return NewWindowsError("AssignProcessToJobObject", err)
   188  	}
   189  	return nil
   190  }
   191  
   192  func (self *Job) Terminate(exitCode uint) error {
   193  	if err := TerminateJobObject(self.handle, uint32(exitCode)); err != nil {
   194  		return NewWindowsError("TerminateJobObject", err)
   195  	}
   196  	return nil
   197  }
   198  
   199  func OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) (syscall.Handle, error) {
   200  	var inheritHandleRaw int32
   201  	if inheritHandle {
   202  		inheritHandleRaw = 1
   203  	} else {
   204  		inheritHandleRaw = 0
   205  	}
   206  	r1, _, e1 := procOpenProcess.Call(
   207  		uintptr(desiredAccess),
   208  		uintptr(inheritHandleRaw),
   209  		uintptr(processId))
   210  	if r1 == 0 {
   211  		if e1 != ERROR_SUCCESS {
   212  			return 0, e1
   213  		} else {
   214  			return 0, syscall.EINVAL
   215  		}
   216  	}
   217  	return syscall.Handle(r1), nil
   218  }
   219  
   220  func (self *Job) Close() error {
   221  	if self.handle != 0 {
   222  		if err := CloseHandle(self.handle); err != nil {
   223  			return NewWindowsError("CloseHandle", err)
   224  		}
   225  		self.handle = 0
   226  	}
   227  	return nil
   228  }
   229  
   230  func CreateJobObject(name *uint16) (syscall.Handle, error) {
   231  	jobAttributes := &struct {
   232  		Length             uint32
   233  		SecurityDescriptor *byte
   234  		InheritHandle      int32
   235  	}{}
   236  
   237  	r1, _, e1 := procCreateJobObjectW.Call(
   238  		uintptr(unsafe.Pointer(jobAttributes)),
   239  		uintptr(unsafe.Pointer(name)))
   240  	if r1 == 0 {
   241  		if e1 != ERROR_SUCCESS {
   242  			return 0, e1
   243  		} else {
   244  			return 0, syscall.EINVAL
   245  		}
   246  	}
   247  	return syscall.Handle(r1), nil
   248  }
   249  
   250  func AssignProcessToJobObject(job syscall.Handle, process syscall.Handle) error {
   251  	r1, _, e1 := procAssignProcessToJobObject.Call(uintptr(job), uintptr(process))
   252  	if r1 == 0 {
   253  		if e1 != ERROR_SUCCESS {
   254  			return e1
   255  		} else {
   256  			return syscall.EINVAL
   257  		}
   258  	}
   259  	return nil
   260  }
   261  
   262  func TerminateJobObject(job syscall.Handle, exitCode uint32) error {
   263  	r1, _, e1 := procTerminateJobObject.Call(uintptr(job), uintptr(exitCode))
   264  	if r1 == 0 {
   265  		if e1 != ERROR_SUCCESS {
   266  			return e1
   267  		} else {
   268  			return syscall.EINVAL
   269  		}
   270  	}
   271  	return nil
   272  }
   273  
   274  func CloseHandle(object syscall.Handle) error {
   275  	r1, _, e1 := procCloseHandle.Call(uintptr(object))
   276  	if r1 == 0 {
   277  		if e1 != ERROR_SUCCESS {
   278  			return e1
   279  		} else {
   280  			return syscall.EINVAL
   281  		}
   282  	}
   283  	return nil
   284  }
   285  
   286  type WindowsError struct {
   287  	functionName string
   288  	innerError   error
   289  }
   290  
   291  func NewWindowsError(functionName string, innerError error) *WindowsError {
   292  	return &WindowsError{functionName, innerError}
   293  }
   294  
   295  func (self *WindowsError) FunctionName() string {
   296  	return self.functionName
   297  }
   298  
   299  func (self *WindowsError) InnerError() error {
   300  	return self.innerError
   301  }
   302  
   303  func (self *WindowsError) Error() string {
   304  	return fmt.Sprintf("gowin32: %s failed: %v", self.functionName, self.innerError)
   305  }