1package hcsshim 2 3import ( 4 "encoding/json" 5 "io" 6 "sync" 7 "syscall" 8 "time" 9 10 "github.com/sirupsen/logrus" 11) 12 13// ContainerError is an error encountered in HCS 14type process struct { 15 handleLock sync.RWMutex 16 handle hcsProcess 17 processID int 18 container *container 19 cachedPipes *cachedPipes 20 callbackNumber uintptr 21} 22 23type cachedPipes struct { 24 stdIn syscall.Handle 25 stdOut syscall.Handle 26 stdErr syscall.Handle 27} 28 29type processModifyRequest struct { 30 Operation string 31 ConsoleSize *consoleSize `json:",omitempty"` 32 CloseHandle *closeHandle `json:",omitempty"` 33} 34 35type consoleSize struct { 36 Height uint16 37 Width uint16 38} 39 40type closeHandle struct { 41 Handle string 42} 43 44type processStatus struct { 45 ProcessID uint32 46 Exited bool 47 ExitCode uint32 48 LastWaitResult int32 49} 50 51const ( 52 stdIn string = "StdIn" 53 stdOut string = "StdOut" 54 stdErr string = "StdErr" 55) 56 57const ( 58 modifyConsoleSize string = "ConsoleSize" 59 modifyCloseHandle string = "CloseHandle" 60) 61 62// Pid returns the process ID of the process within the container. 63func (process *process) Pid() int { 64 return process.processID 65} 66 67// Kill signals the process to terminate but does not wait for it to finish terminating. 68func (process *process) Kill() error { 69 process.handleLock.RLock() 70 defer process.handleLock.RUnlock() 71 operation := "Kill" 72 title := "HCSShim::Process::" + operation 73 logrus.Debugf(title+" processid=%d", process.processID) 74 75 if process.handle == 0 { 76 return makeProcessError(process, operation, "", ErrAlreadyClosed) 77 } 78 79 var resultp *uint16 80 err := hcsTerminateProcess(process.handle, &resultp) 81 err = processHcsResult(err, resultp) 82 if err != nil { 83 return makeProcessError(process, operation, "", err) 84 } 85 86 logrus.Debugf(title+" succeeded processid=%d", process.processID) 87 return nil 88} 89 90// Wait waits for the process to exit. 91func (process *process) Wait() error { 92 operation := "Wait" 93 title := "HCSShim::Process::" + operation 94 logrus.Debugf(title+" processid=%d", process.processID) 95 96 err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil) 97 if err != nil { 98 return makeProcessError(process, operation, "", err) 99 } 100 101 logrus.Debugf(title+" succeeded processid=%d", process.processID) 102 return nil 103} 104 105// WaitTimeout waits for the process to exit or the duration to elapse. It returns 106// false if timeout occurs. 107func (process *process) WaitTimeout(timeout time.Duration) error { 108 operation := "WaitTimeout" 109 title := "HCSShim::Process::" + operation 110 logrus.Debugf(title+" processid=%d", process.processID) 111 112 err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, &timeout) 113 if err != nil { 114 return makeProcessError(process, operation, "", err) 115 } 116 117 logrus.Debugf(title+" succeeded processid=%d", process.processID) 118 return nil 119} 120 121// ExitCode returns the exit code of the process. The process must have 122// already terminated. 123func (process *process) ExitCode() (int, error) { 124 process.handleLock.RLock() 125 defer process.handleLock.RUnlock() 126 operation := "ExitCode" 127 title := "HCSShim::Process::" + operation 128 logrus.Debugf(title+" processid=%d", process.processID) 129 130 if process.handle == 0 { 131 return 0, makeProcessError(process, operation, "", ErrAlreadyClosed) 132 } 133 134 properties, err := process.properties() 135 if err != nil { 136 return 0, makeProcessError(process, operation, "", err) 137 } 138 139 if properties.Exited == false { 140 return 0, makeProcessError(process, operation, "", ErrInvalidProcessState) 141 } 142 143 if properties.LastWaitResult != 0 { 144 return 0, makeProcessError(process, operation, "", syscall.Errno(properties.LastWaitResult)) 145 } 146 147 logrus.Debugf(title+" succeeded processid=%d exitCode=%d", process.processID, properties.ExitCode) 148 return int(properties.ExitCode), nil 149} 150 151// ResizeConsole resizes the console of the process. 152func (process *process) ResizeConsole(width, height uint16) error { 153 process.handleLock.RLock() 154 defer process.handleLock.RUnlock() 155 operation := "ResizeConsole" 156 title := "HCSShim::Process::" + operation 157 logrus.Debugf(title+" processid=%d", process.processID) 158 159 if process.handle == 0 { 160 return makeProcessError(process, operation, "", ErrAlreadyClosed) 161 } 162 163 modifyRequest := processModifyRequest{ 164 Operation: modifyConsoleSize, 165 ConsoleSize: &consoleSize{ 166 Height: height, 167 Width: width, 168 }, 169 } 170 171 modifyRequestb, err := json.Marshal(modifyRequest) 172 if err != nil { 173 return err 174 } 175 176 modifyRequestStr := string(modifyRequestb) 177 178 var resultp *uint16 179 err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp) 180 err = processHcsResult(err, resultp) 181 if err != nil { 182 return makeProcessError(process, operation, "", err) 183 } 184 185 logrus.Debugf(title+" succeeded processid=%d", process.processID) 186 return nil 187} 188 189func (process *process) properties() (*processStatus, error) { 190 operation := "properties" 191 title := "HCSShim::Process::" + operation 192 logrus.Debugf(title+" processid=%d", process.processID) 193 194 var ( 195 resultp *uint16 196 propertiesp *uint16 197 ) 198 err := hcsGetProcessProperties(process.handle, &propertiesp, &resultp) 199 err = processHcsResult(err, resultp) 200 if err != nil { 201 return nil, err 202 } 203 204 if propertiesp == nil { 205 return nil, ErrUnexpectedValue 206 } 207 propertiesRaw := convertAndFreeCoTaskMemBytes(propertiesp) 208 209 properties := &processStatus{} 210 if err := json.Unmarshal(propertiesRaw, properties); err != nil { 211 return nil, err 212 } 213 214 logrus.Debugf(title+" succeeded processid=%d, properties=%s", process.processID, propertiesRaw) 215 return properties, nil 216} 217 218// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing 219// these pipes does not close the underlying pipes; it should be possible to 220// call this multiple times to get multiple interfaces. 221func (process *process) Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error) { 222 process.handleLock.RLock() 223 defer process.handleLock.RUnlock() 224 operation := "Stdio" 225 title := "HCSShim::Process::" + operation 226 logrus.Debugf(title+" processid=%d", process.processID) 227 228 if process.handle == 0 { 229 return nil, nil, nil, makeProcessError(process, operation, "", ErrAlreadyClosed) 230 } 231 232 var stdIn, stdOut, stdErr syscall.Handle 233 234 if process.cachedPipes == nil { 235 var ( 236 processInfo hcsProcessInformation 237 resultp *uint16 238 ) 239 err := hcsGetProcessInfo(process.handle, &processInfo, &resultp) 240 err = processHcsResult(err, resultp) 241 if err != nil { 242 return nil, nil, nil, makeProcessError(process, operation, "", err) 243 } 244 245 stdIn, stdOut, stdErr = processInfo.StdInput, processInfo.StdOutput, processInfo.StdError 246 } else { 247 // Use cached pipes 248 stdIn, stdOut, stdErr = process.cachedPipes.stdIn, process.cachedPipes.stdOut, process.cachedPipes.stdErr 249 250 // Invalidate the cache 251 process.cachedPipes = nil 252 } 253 254 pipes, err := makeOpenFiles([]syscall.Handle{stdIn, stdOut, stdErr}) 255 if err != nil { 256 return nil, nil, nil, makeProcessError(process, operation, "", err) 257 } 258 259 logrus.Debugf(title+" succeeded processid=%d", process.processID) 260 return pipes[0], pipes[1], pipes[2], nil 261} 262 263// CloseStdin closes the write side of the stdin pipe so that the process is 264// notified on the read side that there is no more data in stdin. 265func (process *process) CloseStdin() error { 266 process.handleLock.RLock() 267 defer process.handleLock.RUnlock() 268 operation := "CloseStdin" 269 title := "HCSShim::Process::" + operation 270 logrus.Debugf(title+" processid=%d", process.processID) 271 272 if process.handle == 0 { 273 return makeProcessError(process, operation, "", ErrAlreadyClosed) 274 } 275 276 modifyRequest := processModifyRequest{ 277 Operation: modifyCloseHandle, 278 CloseHandle: &closeHandle{ 279 Handle: stdIn, 280 }, 281 } 282 283 modifyRequestb, err := json.Marshal(modifyRequest) 284 if err != nil { 285 return err 286 } 287 288 modifyRequestStr := string(modifyRequestb) 289 290 var resultp *uint16 291 err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp) 292 err = processHcsResult(err, resultp) 293 if err != nil { 294 return makeProcessError(process, operation, "", err) 295 } 296 297 logrus.Debugf(title+" succeeded processid=%d", process.processID) 298 return nil 299} 300 301// Close cleans up any state associated with the process but does not kill 302// or wait on it. 303func (process *process) Close() error { 304 process.handleLock.Lock() 305 defer process.handleLock.Unlock() 306 operation := "Close" 307 title := "HCSShim::Process::" + operation 308 logrus.Debugf(title+" processid=%d", process.processID) 309 310 // Don't double free this 311 if process.handle == 0 { 312 return nil 313 } 314 315 if err := process.unregisterCallback(); err != nil { 316 return makeProcessError(process, operation, "", err) 317 } 318 319 if err := hcsCloseProcess(process.handle); err != nil { 320 return makeProcessError(process, operation, "", err) 321 } 322 323 process.handle = 0 324 325 logrus.Debugf(title+" succeeded processid=%d", process.processID) 326 return nil 327} 328 329func (process *process) registerCallback() error { 330 context := ¬ifcationWatcherContext{ 331 channels: newChannels(), 332 } 333 334 callbackMapLock.Lock() 335 callbackNumber := nextCallback 336 nextCallback++ 337 callbackMap[callbackNumber] = context 338 callbackMapLock.Unlock() 339 340 var callbackHandle hcsCallback 341 err := hcsRegisterProcessCallback(process.handle, notificationWatcherCallback, callbackNumber, &callbackHandle) 342 if err != nil { 343 return err 344 } 345 context.handle = callbackHandle 346 process.callbackNumber = callbackNumber 347 348 return nil 349} 350 351func (process *process) unregisterCallback() error { 352 callbackNumber := process.callbackNumber 353 354 callbackMapLock.RLock() 355 context := callbackMap[callbackNumber] 356 callbackMapLock.RUnlock() 357 358 if context == nil { 359 return nil 360 } 361 362 handle := context.handle 363 364 if handle == 0 { 365 return nil 366 } 367 368 // hcsUnregisterProcessCallback has its own syncronization 369 // to wait for all callbacks to complete. We must NOT hold the callbackMapLock. 370 err := hcsUnregisterProcessCallback(handle) 371 if err != nil { 372 return err 373 } 374 375 closeChannels(context.channels) 376 377 callbackMapLock.Lock() 378 callbackMap[callbackNumber] = nil 379 callbackMapLock.Unlock() 380 381 handle = 0 382 383 return nil 384} 385