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 }