osproc.nim 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements an advanced facility for executing OS processes
  10. ## and process communication.
  11. include "system/inclrtl"
  12. import
  13. strutils, os, strtabs, streams, cpuinfo
  14. from ospaths import quoteShell, quoteShellWindows, quoteShellPosix
  15. export quoteShell, quoteShellWindows, quoteShellPosix
  16. when defined(windows):
  17. import winlean
  18. else:
  19. import posix
  20. when defined(linux):
  21. import linux
  22. type
  23. ProcessOption* = enum ## options that can be passed `startProcess`
  24. poEchoCmd, ## echo the command before execution
  25. poUsePath, ## Asks system to search for executable using PATH environment
  26. ## variable.
  27. ## On Windows, this is the default.
  28. poEvalCommand, ## Pass `command` directly to the shell, without quoting.
  29. ## Use it only if `command` comes from trusted source.
  30. poStdErrToStdOut, ## merge stdout and stderr to the stdout stream
  31. poParentStreams, ## use the parent's streams
  32. poInteractive, ## optimize the buffer handling for responsiveness for
  33. ## UI applications. Currently this only affects
  34. ## Windows: Named pipes are used so that you can peek
  35. ## at the process' output streams.
  36. poDemon ## Windows: The program creates no Window.
  37. ## Unix: Start the program as a demon. This is still
  38. ## work in progress!
  39. ProcessObj = object of RootObj
  40. when defined(windows):
  41. fProcessHandle: Handle
  42. fThreadHandle: Handle
  43. inHandle, outHandle, errHandle: FileHandle
  44. id: Handle
  45. else:
  46. inHandle, outHandle, errHandle: FileHandle
  47. inStream, outStream, errStream: Stream
  48. id: Pid
  49. exitStatus: cint
  50. exitFlag: bool
  51. options: set[ProcessOption]
  52. Process* = ref ProcessObj ## represents an operating system process
  53. const poUseShell* {.deprecated.} = poUsePath
  54. ## Deprecated alias for poUsePath.
  55. proc execProcess*(command: string,
  56. args: openArray[string] = [],
  57. env: StringTableRef = nil,
  58. options: set[ProcessOption] = {poStdErrToStdOut,
  59. poUsePath,
  60. poEvalCommand}): TaintedString {.
  61. rtl, extern: "nosp$1",
  62. tags: [ExecIOEffect, ReadIOEffect,
  63. RootEffect].}
  64. ## A convenience procedure that executes ``command`` with ``startProcess``
  65. ## and returns its output as a string.
  66. ## WARNING: this function uses poEvalCommand by default for backward compatibility.
  67. ## Make sure to pass options explicitly.
  68. ##
  69. ## .. code-block:: Nim
  70. ##
  71. ## let outp = execProcess("nim c -r mytestfile.nim")
  72. ## # Note: outp may have an interleave of text from the nim compile
  73. ## # and any output from mytestfile when it runs
  74. proc execCmd*(command: string): int {.rtl, extern: "nosp$1", tags: [ExecIOEffect,
  75. ReadIOEffect, RootEffect].}
  76. ## Executes ``command`` and returns its error code. Standard input, output,
  77. ## error streams are inherited from the calling process. This operation
  78. ## is also often called `system`:idx:.
  79. ##
  80. ## .. code-block:: Nim
  81. ##
  82. ## let errC = execCmd("nim c -r mytestfile.nim")
  83. proc startProcess*(command: string,
  84. workingDir: string = "",
  85. args: openArray[string] = [],
  86. env: StringTableRef = nil,
  87. options: set[ProcessOption] = {poStdErrToStdOut}):
  88. Process {.rtl, extern: "nosp$1", tags: [ExecIOEffect, ReadEnvEffect,
  89. RootEffect].}
  90. ## Starts a process. `Command` is the executable file, `workingDir` is the
  91. ## process's working directory. If ``workingDir == ""`` the current directory
  92. ## is used. `args` are the command line arguments that are passed to the
  93. ## process. On many operating systems, the first command line argument is the
  94. ## name of the executable. `args` should not contain this argument!
  95. ## `env` is the environment that will be passed to the process.
  96. ## If ``env == nil`` the environment is inherited of
  97. ## the parent process. `options` are additional flags that may be passed
  98. ## to `startProcess`. See the documentation of ``ProcessOption`` for the
  99. ## meaning of these flags. You need to `close` the process when done.
  100. ##
  101. ## Note that you can't pass any `args` if you use the option
  102. ## ``poEvalCommand``, which invokes the system shell to run the specified
  103. ## `command`. In this situation you have to concatenate manually the contents
  104. ## of `args` to `command` carefully escaping/quoting any special characters,
  105. ## since it will be passed *as is* to the system shell. Each system/shell may
  106. ## feature different escaping rules, so try to avoid this kind of shell
  107. ## invocation if possible as it leads to non portable software.
  108. ##
  109. ## Return value: The newly created process object. Nil is never returned,
  110. ## but ``OSError`` is raised in case of an error.
  111. proc startCmd*(command: string, options: set[ProcessOption] = {
  112. poStdErrToStdOut, poUsePath}): Process {.
  113. tags: [ExecIOEffect, ReadEnvEffect, RootEffect], deprecated.} =
  114. ## Deprecated - use `startProcess` directly.
  115. result = startProcess(command=command, options=options + {poEvalCommand})
  116. proc close*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
  117. ## When the process has finished executing, cleanup related handles.
  118. ##
  119. ## **Warning:** If the process has not finished executing, this will forcibly
  120. ## terminate the process. Doing so may result in zombie processes and
  121. ## `pty leaks <http://stackoverflow.com/questions/27021641/how-to-fix-request-failed-on-channel-0>`_.
  122. proc suspend*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
  123. ## Suspends the process `p`.
  124. proc resume*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
  125. ## Resumes the process `p`.
  126. proc terminate*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
  127. ## Stop the process `p`. On Posix OSes the procedure sends ``SIGTERM``
  128. ## to the process. On Windows the Win32 API function ``TerminateProcess()``
  129. ## is called to stop the process.
  130. proc kill*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
  131. ## Kill the process `p`. On Posix OSes the procedure sends ``SIGKILL`` to
  132. ## the process. On Windows ``kill()`` is simply an alias for ``terminate()``.
  133. proc running*(p: Process): bool {.rtl, extern: "nosp$1", tags: [].}
  134. ## Returns true iff the process `p` is still running. Returns immediately.
  135. proc processID*(p: Process): int {.rtl, extern: "nosp$1".} =
  136. ## returns `p`'s process ID.
  137. return p.id
  138. proc waitForExit*(p: Process, timeout: int = -1): int {.rtl,
  139. extern: "nosp$1", tags: [].}
  140. ## waits for the process to finish and returns `p`'s error code.
  141. ##
  142. ## **Warning**: Be careful when using waitForExit for processes created without
  143. ## poParentStreams because they may fill output buffers, causing deadlock.
  144. ##
  145. ## On posix, if the process has exited because of a signal, 128 + signal
  146. ## number will be returned.
  147. proc peekExitCode*(p: Process): int {.rtl, extern: "nosp$1", tags: [].}
  148. ## return -1 if the process is still running. Otherwise the process' exit code
  149. ##
  150. ## On posix, if the process has exited because of a signal, 128 + signal
  151. ## number will be returned.
  152. proc inputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].}
  153. ## returns ``p``'s input stream for writing to.
  154. ##
  155. ## **Warning**: The returned `Stream` should not be closed manually as it
  156. ## is closed when closing the Process ``p``.
  157. proc outputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].}
  158. ## returns ``p``'s output stream for reading from.
  159. ##
  160. ## **Warning**: The returned `Stream` should not be closed manually as it
  161. ## is closed when closing the Process ``p``.
  162. proc errorStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].}
  163. ## returns ``p``'s error stream for reading from.
  164. ##
  165. ## **Warning**: The returned `Stream` should not be closed manually as it
  166. ## is closed when closing the Process ``p``.
  167. proc inputHandle*(p: Process): FileHandle {.rtl, extern: "nosp$1",
  168. tags: [].} =
  169. ## returns ``p``'s input file handle for writing to.
  170. ##
  171. ## **Warning**: The returned `FileHandle` should not be closed manually as
  172. ## it is closed when closing the Process ``p``.
  173. result = p.inHandle
  174. proc outputHandle*(p: Process): FileHandle {.rtl, extern: "nosp$1",
  175. tags: [].} =
  176. ## returns ``p``'s output file handle for reading from.
  177. ##
  178. ## **Warning**: The returned `FileHandle` should not be closed manually as
  179. ## it is closed when closing the Process ``p``.
  180. result = p.outHandle
  181. proc errorHandle*(p: Process): FileHandle {.rtl, extern: "nosp$1",
  182. tags: [].} =
  183. ## returns ``p``'s error file handle for reading from.
  184. ##
  185. ## **Warning**: The returned `FileHandle` should not be closed manually as
  186. ## it is closed when closing the Process ``p``.
  187. result = p.errHandle
  188. proc countProcessors*(): int {.rtl, extern: "nosp$1".} =
  189. ## returns the numer of the processors/cores the machine has.
  190. ## Returns 0 if it cannot be detected.
  191. result = cpuinfo.countProcessors()
  192. proc execProcesses*(cmds: openArray[string],
  193. options = {poStdErrToStdOut, poParentStreams},
  194. n = countProcessors(),
  195. beforeRunEvent: proc(idx: int) = nil,
  196. afterRunEvent: proc(idx: int, p: Process) = nil): int
  197. {.rtl, extern: "nosp$1",
  198. tags: [ExecIOEffect, TimeEffect, ReadEnvEffect, RootEffect]} =
  199. ## executes the commands `cmds` in parallel. Creates `n` processes
  200. ## that execute in parallel. The highest return value of all processes
  201. ## is returned. Runs `beforeRunEvent` before running each command.
  202. assert n > 0
  203. if n > 1:
  204. var i = 0
  205. var q = newSeq[Process](n)
  206. var idxs = newSeq[int](n) # map process index to cmds index
  207. when defined(windows):
  208. var w: WOHandleArray
  209. var m = min(min(n, MAXIMUM_WAIT_OBJECTS), cmds.len)
  210. var wcount = m
  211. else:
  212. var m = min(n, cmds.len)
  213. while i < m:
  214. if beforeRunEvent != nil:
  215. beforeRunEvent(i)
  216. q[i] = startProcess(cmds[i], options = options + {poEvalCommand})
  217. idxs[i] = i
  218. when defined(windows):
  219. w[i] = q[i].fProcessHandle
  220. inc(i)
  221. var ecount = len(cmds)
  222. while ecount > 0:
  223. var rexit = -1
  224. when defined(windows):
  225. # waiting for all children, get result if any child exits
  226. var ret = waitForMultipleObjects(int32(wcount), addr(w), 0'i32,
  227. INFINITE)
  228. if ret == WAIT_TIMEOUT:
  229. # must not be happen
  230. discard
  231. elif ret == WAIT_FAILED:
  232. raiseOSError(osLastError())
  233. else:
  234. var status: int32
  235. for r in 0..m-1:
  236. if not isNil(q[r]) and q[r].fProcessHandle == w[ret]:
  237. discard getExitCodeProcess(q[r].fProcessHandle, status)
  238. q[r].exitFlag = true
  239. q[r].exitStatus = status
  240. rexit = r
  241. break
  242. else:
  243. var status: cint = 1
  244. # waiting for all children, get result if any child exits
  245. let res = waitpid(-1, status, 0)
  246. if res > 0:
  247. for r in 0..m-1:
  248. if not isNil(q[r]) and q[r].id == res:
  249. if WIFEXITED(status) or WIFSIGNALED(status):
  250. q[r].exitFlag = true
  251. q[r].exitStatus = status
  252. rexit = r
  253. break
  254. else:
  255. let err = osLastError()
  256. if err == OSErrorCode(ECHILD):
  257. # some child exits, we need to check our childs exit codes
  258. for r in 0..m-1:
  259. if (not isNil(q[r])) and (not running(q[r])):
  260. q[r].exitFlag = true
  261. q[r].exitStatus = status
  262. rexit = r
  263. break
  264. elif err == OSErrorCode(EINTR):
  265. # signal interrupted our syscall, lets repeat it
  266. continue
  267. else:
  268. # all other errors are exceptions
  269. raiseOSError(err)
  270. if rexit >= 0:
  271. result = max(result, abs(q[rexit].peekExitCode()))
  272. if afterRunEvent != nil: afterRunEvent(idxs[rexit], q[rexit])
  273. close(q[rexit])
  274. if i < len(cmds):
  275. if beforeRunEvent != nil: beforeRunEvent(i)
  276. q[rexit] = startProcess(cmds[i],
  277. options = options + {poEvalCommand})
  278. idxs[rexit] = i
  279. when defined(windows):
  280. w[rexit] = q[rexit].fProcessHandle
  281. inc(i)
  282. else:
  283. when defined(windows):
  284. for k in 0..wcount - 1:
  285. if w[k] == q[rexit].fProcessHandle:
  286. w[k] = w[wcount - 1]
  287. w[wcount - 1] = 0
  288. dec(wcount)
  289. break
  290. q[rexit] = nil
  291. dec(ecount)
  292. else:
  293. for i in 0..high(cmds):
  294. if beforeRunEvent != nil:
  295. beforeRunEvent(i)
  296. var p = startProcess(cmds[i], options=options + {poEvalCommand})
  297. result = max(abs(waitForExit(p)), result)
  298. if afterRunEvent != nil: afterRunEvent(i, p)
  299. close(p)
  300. proc select*(readfds: var seq[Process], timeout = 500): int
  301. {.benign, deprecated.}
  302. ## `select` with a sensible Nim interface. `timeout` is in milliseconds.
  303. ## Specify -1 for no timeout. Returns the number of processes that are
  304. ## ready to read from. The processes that are ready to be read from are
  305. ## removed from `readfds`.
  306. ##
  307. ## **Warning**: This function may give unexpected or completely wrong
  308. ## results on Windows.
  309. ##
  310. ## **Deprecated since version 0.17.0**: This procedure isn't cross-platform
  311. ## and so should not be used in newly written code.
  312. when not defined(useNimRtl):
  313. proc execProcess(command: string,
  314. args: openArray[string] = [],
  315. env: StringTableRef = nil,
  316. options: set[ProcessOption] = {poStdErrToStdOut,
  317. poUsePath,
  318. poEvalCommand}): TaintedString =
  319. var p = startProcess(command, args=args, env=env, options=options)
  320. var outp = outputStream(p)
  321. result = TaintedString""
  322. var line = newStringOfCap(120).TaintedString
  323. while true:
  324. # FIXME: converts CR-LF to LF.
  325. if outp.readLine(line):
  326. result.string.add(line.string)
  327. result.string.add("\n")
  328. elif not running(p): break
  329. close(p)
  330. template streamAccess(p) =
  331. assert poParentStreams notin p.options, "API usage error: stream access not allowed when you use poParentStreams"
  332. when defined(Windows) and not defined(useNimRtl):
  333. # We need to implement a handle stream for Windows:
  334. type
  335. FileHandleStream = ref object of StreamObj
  336. handle: Handle
  337. atTheEnd: bool
  338. proc hsClose(s: Stream) = discard # nothing to do here
  339. proc hsAtEnd(s: Stream): bool = return FileHandleStream(s).atTheEnd
  340. proc hsReadData(s: Stream, buffer: pointer, bufLen: int): int =
  341. var s = FileHandleStream(s)
  342. if s.atTheEnd: return 0
  343. var br: int32
  344. var a = winlean.readFile(s.handle, buffer, bufLen.cint, addr br, nil)
  345. # TRUE and zero bytes returned (EOF).
  346. # TRUE and n (>0) bytes returned (good data).
  347. # FALSE and bytes returned undefined (system error).
  348. if a == 0 and br != 0: raiseOSError(osLastError())
  349. s.atTheEnd = br == 0 #< bufLen
  350. result = br
  351. proc hsWriteData(s: Stream, buffer: pointer, bufLen: int) =
  352. var s = FileHandleStream(s)
  353. var bytesWritten: int32
  354. var a = winlean.writeFile(s.handle, buffer, bufLen.cint,
  355. addr bytesWritten, nil)
  356. if a == 0: raiseOSError(osLastError())
  357. proc newFileHandleStream(handle: Handle): FileHandleStream =
  358. new(result)
  359. result.handle = handle
  360. result.closeImpl = hsClose
  361. result.atEndImpl = hsAtEnd
  362. result.readDataImpl = hsReadData
  363. result.writeDataImpl = hsWriteData
  364. proc buildCommandLine(a: string, args: openArray[string]): string =
  365. result = quoteShell(a)
  366. for i in 0..high(args):
  367. result.add(' ')
  368. result.add(quoteShell(args[i]))
  369. proc buildEnv(env: StringTableRef): tuple[str: cstring, len: int] =
  370. var L = 0
  371. for key, val in pairs(env): inc(L, key.len + val.len + 2)
  372. var str = cast[cstring](alloc0(L+2))
  373. L = 0
  374. for key, val in pairs(env):
  375. var x = key & "=" & val
  376. copyMem(addr(str[L]), cstring(x), x.len+1) # copy \0
  377. inc(L, x.len+1)
  378. (str, L)
  379. #proc open_osfhandle(osh: Handle, mode: int): int {.
  380. # importc: "_open_osfhandle", header: "<fcntl.h>".}
  381. #var
  382. # O_WRONLY {.importc: "_O_WRONLY", header: "<fcntl.h>".}: int
  383. # O_RDONLY {.importc: "_O_RDONLY", header: "<fcntl.h>".}: int
  384. proc myDup(h: Handle; inherit: WinBool=1): Handle =
  385. let thisProc = getCurrentProcess()
  386. if duplicateHandle(thisProc, h,
  387. thisProc, addr result,0,inherit,
  388. DUPLICATE_SAME_ACCESS) == 0:
  389. raiseOSError(osLastError())
  390. proc createAllPipeHandles(si: var STARTUPINFO;
  391. stdin, stdout, stderr: var Handle;
  392. hash: int) =
  393. var sa: SECURITY_ATTRIBUTES
  394. sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint
  395. sa.lpSecurityDescriptor = nil
  396. sa.bInheritHandle = 1
  397. let pipeOutName = newWideCString(r"\\.\pipe\stdout" & $hash)
  398. let pipeInName = newWideCString(r"\\.\pipe\stdin" & $hash)
  399. let pipeOut = createNamedPipe(pipeOutName,
  400. dwOpenMode=PIPE_ACCESS_INBOUND or FILE_FLAG_WRITE_THROUGH,
  401. dwPipeMode=PIPE_NOWAIT,
  402. nMaxInstances=1,
  403. nOutBufferSize=1024, nInBufferSize=1024,
  404. nDefaultTimeOut=0,addr sa)
  405. if pipeOut == INVALID_HANDLE_VALUE:
  406. raiseOSError(osLastError())
  407. let pipeIn = createNamedPipe(pipeInName,
  408. dwOpenMode=PIPE_ACCESS_OUTBOUND or FILE_FLAG_WRITE_THROUGH,
  409. dwPipeMode=PIPE_NOWAIT,
  410. nMaxInstances=1,
  411. nOutBufferSize=1024, nInBufferSize=1024,
  412. nDefaultTimeOut=0,addr sa)
  413. if pipeIn == INVALID_HANDLE_VALUE:
  414. raiseOSError(osLastError())
  415. si.hStdOutput = createFileW(pipeOutName,
  416. FILE_WRITE_DATA or SYNCHRONIZE, 0, addr sa,
  417. OPEN_EXISTING, # very important flag!
  418. FILE_ATTRIBUTE_NORMAL,
  419. 0 # no template file for OPEN_EXISTING
  420. )
  421. if si.hStdOutput == INVALID_HANDLE_VALUE:
  422. raiseOSError(osLastError())
  423. si.hStdError = myDup(si.hStdOutput)
  424. si.hStdInput = createFileW(pipeInName,
  425. FILE_READ_DATA or SYNCHRONIZE, 0, addr sa,
  426. OPEN_EXISTING, # very important flag!
  427. FILE_ATTRIBUTE_NORMAL,
  428. 0 # no template file for OPEN_EXISTING
  429. )
  430. if si.hStdOutput == INVALID_HANDLE_VALUE:
  431. raiseOSError(osLastError())
  432. stdin = myDup(pipeIn, 0)
  433. stdout = myDup(pipeOut, 0)
  434. discard closeHandle(pipeIn)
  435. discard closeHandle(pipeOut)
  436. stderr = stdout
  437. proc createPipeHandles(rdHandle, wrHandle: var Handle) =
  438. var sa: SECURITY_ATTRIBUTES
  439. sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint
  440. sa.lpSecurityDescriptor = nil
  441. sa.bInheritHandle = 1
  442. if createPipe(rdHandle, wrHandle, sa, 0) == 0'i32:
  443. raiseOSError(osLastError())
  444. proc fileClose(h: Handle) {.inline.} =
  445. if h > 4: discard closeHandle(h)
  446. proc startProcess(command: string,
  447. workingDir: string = "",
  448. args: openArray[string] = [],
  449. env: StringTableRef = nil,
  450. options: set[ProcessOption] = {poStdErrToStdOut}): Process =
  451. var
  452. si: STARTUPINFO
  453. procInfo: PROCESS_INFORMATION
  454. success: int
  455. hi, ho, he: Handle
  456. new(result)
  457. result.options = options
  458. result.exitFlag = true
  459. si.cb = sizeof(si).cint
  460. if poParentStreams notin options:
  461. si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or
  462. if poInteractive notin options:
  463. createPipeHandles(si.hStdInput, hi)
  464. createPipeHandles(ho, si.hStdOutput)
  465. if poStdErrToStdOut in options:
  466. si.hStdError = si.hStdOutput
  467. he = ho
  468. else:
  469. createPipeHandles(he, si.hStdError)
  470. if setHandleInformation(he, DWORD(1), DWORD(0)) == 0'i32:
  471. raiseOsError(osLastError())
  472. if setHandleInformation(hi, DWORD(1), DWORD(0)) == 0'i32:
  473. raiseOsError(osLastError())
  474. if setHandleInformation(ho, DWORD(1), DWORD(0)) == 0'i32:
  475. raiseOsError(osLastError())
  476. else:
  477. createAllPipeHandles(si, hi, ho, he, cast[int](result))
  478. result.inHandle = FileHandle(hi)
  479. result.outHandle = FileHandle(ho)
  480. result.errHandle = FileHandle(he)
  481. else:
  482. si.hStdError = getStdHandle(STD_ERROR_HANDLE)
  483. si.hStdInput = getStdHandle(STD_INPUT_HANDLE)
  484. si.hStdOutput = getStdHandle(STD_OUTPUT_HANDLE)
  485. result.inHandle = FileHandle(si.hStdInput)
  486. result.outHandle = FileHandle(si.hStdOutput)
  487. result.errHandle = FileHandle(si.hStdError)
  488. var cmdl: cstring
  489. var cmdRoot: string
  490. if poEvalCommand in options:
  491. cmdl = command
  492. assert args.len == 0
  493. else:
  494. cmdRoot = buildCommandLine(command, args)
  495. cmdl = cstring(cmdRoot)
  496. var wd: cstring = nil
  497. var e = (str: nil.cstring, len: -1)
  498. if len(workingDir) > 0: wd = workingDir
  499. if env != nil: e = buildEnv(env)
  500. if poEchoCmd in options: echo($cmdl)
  501. when useWinUnicode:
  502. var tmp = newWideCString(cmdl)
  503. var ee =
  504. if e.str.isNil: nil
  505. else: newWideCString(e.str, e.len)
  506. var wwd = newWideCString(wd)
  507. var flags = NORMAL_PRIORITY_CLASS or CREATE_UNICODE_ENVIRONMENT
  508. if poDemon in options: flags = flags or CREATE_NO_WINDOW
  509. success = winlean.createProcessW(nil, tmp, nil, nil, 1, flags,
  510. ee, wwd, si, procInfo)
  511. else:
  512. success = winlean.createProcessA(nil,
  513. cmdl, nil, nil, 1, NORMAL_PRIORITY_CLASS, e, wd, si, procInfo)
  514. let lastError = osLastError()
  515. if poParentStreams notin options:
  516. fileClose(si.hStdInput)
  517. fileClose(si.hStdOutput)
  518. if poStdErrToStdOut notin options:
  519. fileClose(si.hStdError)
  520. if e.str != nil: dealloc(e.str)
  521. if success == 0:
  522. if poInteractive in result.options: close(result)
  523. const errInvalidParameter = 87.int
  524. const errFileNotFound = 2.int
  525. if lastError.int in {errInvalidParameter, errFileNotFound}:
  526. raiseOSError(lastError,
  527. "Requested command not found: '$1'. OS error:" % command)
  528. else:
  529. raiseOSError(lastError, command)
  530. result.fProcessHandle = procInfo.hProcess
  531. result.fThreadHandle = procInfo.hThread
  532. result.id = procInfo.dwProcessId
  533. result.exitFlag = false
  534. proc close(p: Process) =
  535. if poParentStreams notin p.options:
  536. discard closeHandle(p.inHandle)
  537. discard closeHandle(p.outHandle)
  538. discard closeHandle(p.errHandle)
  539. discard closeHandle(p.fThreadHandle)
  540. discard closeHandle(p.fProcessHandle)
  541. proc suspend(p: Process) =
  542. discard suspendThread(p.fThreadHandle)
  543. proc resume(p: Process) =
  544. discard resumeThread(p.fThreadHandle)
  545. proc running(p: Process): bool =
  546. if p.exitFlag:
  547. return false
  548. else:
  549. var x = waitForSingleObject(p.fProcessHandle, 0)
  550. return x == WAIT_TIMEOUT
  551. proc terminate(p: Process) =
  552. if running(p):
  553. discard terminateProcess(p.fProcessHandle, 0)
  554. proc kill(p: Process) =
  555. terminate(p)
  556. proc waitForExit(p: Process, timeout: int = -1): int =
  557. if p.exitFlag:
  558. return p.exitStatus
  559. let res = waitForSingleObject(p.fProcessHandle, timeout.int32)
  560. if res == WAIT_TIMEOUT:
  561. terminate(p)
  562. var status: int32
  563. discard getExitCodeProcess(p.fProcessHandle, status)
  564. if status != STILL_ACTIVE:
  565. p.exitFlag = true
  566. p.exitStatus = status
  567. discard closeHandle(p.fThreadHandle)
  568. discard closeHandle(p.fProcessHandle)
  569. result = status
  570. else:
  571. result = -1
  572. proc peekExitCode(p: Process): int =
  573. if p.exitFlag:
  574. return p.exitStatus
  575. result = -1
  576. var b = waitForSingleObject(p.fProcessHandle, 0) == WAIT_TIMEOUT
  577. if not b:
  578. var status: int32
  579. discard getExitCodeProcess(p.fProcessHandle, status)
  580. p.exitFlag = true
  581. p.exitStatus = status
  582. discard closeHandle(p.fThreadHandle)
  583. discard closeHandle(p.fProcessHandle)
  584. result = status
  585. proc inputStream(p: Process): Stream =
  586. streamAccess(p)
  587. result = newFileHandleStream(p.inHandle)
  588. proc outputStream(p: Process): Stream =
  589. streamAccess(p)
  590. result = newFileHandleStream(p.outHandle)
  591. proc errorStream(p: Process): Stream =
  592. streamAccess(p)
  593. result = newFileHandleStream(p.errHandle)
  594. proc execCmd(command: string): int =
  595. var
  596. si: STARTUPINFO
  597. procInfo: PROCESS_INFORMATION
  598. process: Handle
  599. L: int32
  600. si.cb = sizeof(si).cint
  601. si.hStdError = getStdHandle(STD_ERROR_HANDLE)
  602. si.hStdInput = getStdHandle(STD_INPUT_HANDLE)
  603. si.hStdOutput = getStdHandle(STD_OUTPUT_HANDLE)
  604. when useWinUnicode:
  605. var c = newWideCString(command)
  606. var res = winlean.createProcessW(nil, c, nil, nil, 0,
  607. NORMAL_PRIORITY_CLASS, nil, nil, si, procInfo)
  608. else:
  609. var res = winlean.createProcessA(nil, command, nil, nil, 0,
  610. NORMAL_PRIORITY_CLASS, nil, nil, si, procInfo)
  611. if res == 0:
  612. raiseOSError(osLastError())
  613. else:
  614. process = procInfo.hProcess
  615. discard closeHandle(procInfo.hThread)
  616. if waitForSingleObject(process, INFINITE) != -1:
  617. discard getExitCodeProcess(process, L)
  618. result = int(L)
  619. else:
  620. result = -1
  621. discard closeHandle(process)
  622. proc select(readfds: var seq[Process], timeout = 500): int =
  623. assert readfds.len <= MAXIMUM_WAIT_OBJECTS
  624. var rfds: WOHandleArray
  625. for i in 0..readfds.len()-1:
  626. rfds[i] = readfds[i].outHandle #fProcessHandle
  627. var ret = waitForMultipleObjects(readfds.len.int32,
  628. addr(rfds), 0'i32, timeout.int32)
  629. case ret
  630. of WAIT_TIMEOUT:
  631. return 0
  632. of WAIT_FAILED:
  633. raiseOSError(osLastError())
  634. else:
  635. var i = ret - WAIT_OBJECT_0
  636. readfds.del(i)
  637. return 1
  638. proc hasData*(p: Process): bool =
  639. var x: int32
  640. if peekNamedPipe(p.outHandle, lpTotalBytesAvail=addr x):
  641. result = x > 0
  642. elif not defined(useNimRtl):
  643. const
  644. readIdx = 0
  645. writeIdx = 1
  646. proc isExitStatus(status: cint): bool =
  647. WIFEXITED(status) or WIFSIGNALED(status)
  648. proc exitStatus(status: cint): cint =
  649. if WIFSIGNALED(status):
  650. # like the shell!
  651. 128 + WTERMSIG(status)
  652. else:
  653. WEXITSTATUS(status)
  654. proc envToCStringArray(t: StringTableRef): cstringArray =
  655. result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring)))
  656. var i = 0
  657. for key, val in pairs(t):
  658. var x = key & "=" & val
  659. result[i] = cast[cstring](alloc(x.len+1))
  660. copyMem(result[i], addr(x[0]), x.len+1)
  661. inc(i)
  662. proc envToCStringArray(): cstringArray =
  663. var counter = 0
  664. for key, val in envPairs(): inc counter
  665. result = cast[cstringArray](alloc0((counter + 1) * sizeof(cstring)))
  666. var i = 0
  667. for key, val in envPairs():
  668. var x = key.string & "=" & val.string
  669. result[i] = cast[cstring](alloc(x.len+1))
  670. copyMem(result[i], addr(x[0]), x.len+1)
  671. inc(i)
  672. type
  673. StartProcessData = object
  674. sysCommand: string
  675. sysArgs: cstringArray
  676. sysEnv: cstringArray
  677. workingDir: cstring
  678. pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint]
  679. options: set[ProcessOption]
  680. const useProcessAuxSpawn = declared(posix_spawn) and not defined(useFork) and
  681. not defined(useClone) and not defined(linux)
  682. when useProcessAuxSpawn:
  683. proc startProcessAuxSpawn(data: StartProcessData): Pid {.
  684. tags: [ExecIOEffect, ReadEnvEffect, ReadDirEffect, RootEffect], gcsafe.}
  685. else:
  686. proc startProcessAuxFork(data: StartProcessData): Pid {.
  687. tags: [ExecIOEffect, ReadEnvEffect, ReadDirEffect, RootEffect], gcsafe.}
  688. {.push stacktrace: off, profiler: off.}
  689. proc startProcessAfterFork(data: ptr StartProcessData) {.
  690. tags: [ExecIOEffect, ReadEnvEffect, ReadDirEffect, RootEffect], cdecl, gcsafe.}
  691. {.pop.}
  692. proc startProcess(command: string,
  693. workingDir: string = "",
  694. args: openArray[string] = [],
  695. env: StringTableRef = nil,
  696. options: set[ProcessOption] = {poStdErrToStdOut}): Process =
  697. var
  698. pStdin, pStdout, pStderr: array[0..1, cint]
  699. new(result)
  700. result.options = options
  701. result.exitFlag = true
  702. if poParentStreams notin options:
  703. if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or
  704. pipe(pStderr) != 0'i32:
  705. raiseOSError(osLastError())
  706. var sysCommand: string
  707. var sysArgsRaw: seq[string]
  708. if poEvalCommand in options:
  709. const useShPath {.strdefine.} =
  710. when not defined(android): "/bin/sh"
  711. else: "/system/bin/sh"
  712. sysCommand = useShPath
  713. sysArgsRaw = @[sysCommand, "-c", command]
  714. assert args.len == 0, "`args` has to be empty when using poEvalCommand."
  715. else:
  716. sysCommand = command
  717. sysArgsRaw = @[command]
  718. for arg in args.items:
  719. sysArgsRaw.add arg
  720. var pid: Pid
  721. var sysArgs = allocCStringArray(sysArgsRaw)
  722. defer: deallocCStringArray(sysArgs)
  723. var sysEnv = if env == nil:
  724. envToCStringArray()
  725. else:
  726. envToCStringArray(env)
  727. defer: deallocCStringArray(sysEnv)
  728. var data: StartProcessData
  729. shallowCopy(data.sysCommand, sysCommand)
  730. data.sysArgs = sysArgs
  731. data.sysEnv = sysEnv
  732. data.pStdin = pStdin
  733. data.pStdout = pStdout
  734. data.pStderr = pStderr
  735. data.workingDir = workingDir
  736. data.options = options
  737. when useProcessAuxSpawn:
  738. var currentDir = getCurrentDir()
  739. pid = startProcessAuxSpawn(data)
  740. if workingDir.len > 0:
  741. setCurrentDir(currentDir)
  742. else:
  743. pid = startProcessAuxFork(data)
  744. # Parent process. Copy process information.
  745. if poEchoCmd in options:
  746. when declared(echo): echo(command, " ", join(args, " "))
  747. result.id = pid
  748. result.exitFlag = false
  749. if poParentStreams in options:
  750. # does not make much sense, but better than nothing:
  751. result.inHandle = 0
  752. result.outHandle = 1
  753. if poStdErrToStdOut in options:
  754. result.errHandle = result.outHandle
  755. else:
  756. result.errHandle = 2
  757. else:
  758. result.inHandle = pStdin[writeIdx]
  759. result.outHandle = pStdout[readIdx]
  760. if poStdErrToStdOut in options:
  761. result.errHandle = result.outHandle
  762. discard close(pStderr[readIdx])
  763. else:
  764. result.errHandle = pStderr[readIdx]
  765. discard close(pStderr[writeIdx])
  766. discard close(pStdin[readIdx])
  767. discard close(pStdout[writeIdx])
  768. when useProcessAuxSpawn:
  769. proc startProcessAuxSpawn(data: StartProcessData): Pid =
  770. var attr: Tposix_spawnattr
  771. var fops: Tposix_spawn_file_actions
  772. template chck(e: untyped) =
  773. if e != 0'i32: raiseOSError(osLastError())
  774. chck posix_spawn_file_actions_init(fops)
  775. chck posix_spawnattr_init(attr)
  776. var mask: Sigset
  777. chck sigemptyset(mask)
  778. chck posix_spawnattr_setsigmask(attr, mask)
  779. if poDemon in data.options:
  780. chck posix_spawnattr_setpgroup(attr, 0'i32)
  781. var flags = POSIX_SPAWN_USEVFORK or
  782. POSIX_SPAWN_SETSIGMASK
  783. if poDemon in data.options:
  784. flags = flags or POSIX_SPAWN_SETPGROUP
  785. chck posix_spawnattr_setflags(attr, flags)
  786. if not (poParentStreams in data.options):
  787. chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx])
  788. chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx)
  789. chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx])
  790. chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx)
  791. chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx])
  792. if poStdErrToStdOut in data.options:
  793. chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2)
  794. else:
  795. chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2)
  796. var res: cint
  797. if data.workingDir.len > 0:
  798. setCurrentDir($data.workingDir)
  799. var pid: Pid
  800. if (poUsePath in data.options):
  801. res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
  802. else:
  803. res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
  804. discard posix_spawn_file_actions_destroy(fops)
  805. discard posix_spawnattr_destroy(attr)
  806. if res != 0'i32: raiseOSError(OSErrorCode(res), data.sysCommand)
  807. return pid
  808. else:
  809. proc startProcessAuxFork(data: StartProcessData): Pid =
  810. if pipe(data.pErrorPipe) != 0:
  811. raiseOSError(osLastError())
  812. defer:
  813. discard close(data.pErrorPipe[readIdx])
  814. var pid: Pid
  815. var dataCopy = data
  816. when defined(useClone):
  817. const stackSize = 65536
  818. let stackEnd = cast[clong](alloc(stackSize))
  819. let stack = cast[pointer](stackEnd + stackSize)
  820. let fn: pointer = startProcessAfterFork
  821. pid = clone(fn, stack,
  822. cint(CLONE_VM or CLONE_VFORK or SIGCHLD),
  823. pointer(addr dataCopy), nil, nil, nil)
  824. discard close(data.pErrorPipe[writeIdx])
  825. dealloc(stack)
  826. else:
  827. pid = fork()
  828. if pid == 0:
  829. startProcessAfterFork(addr(dataCopy))
  830. exitnow(1)
  831. discard close(data.pErrorPipe[writeIdx])
  832. if pid < 0: raiseOSError(osLastError())
  833. var error: cint
  834. let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error))
  835. if sizeRead == sizeof(error):
  836. raiseOSError(osLastError(),
  837. "Could not find command: '$1'. OS error: $2" %
  838. [$data.sysCommand, $strerror(error)])
  839. return pid
  840. {.push stacktrace: off, profiler: off.}
  841. proc startProcessFail(data: ptr StartProcessData) =
  842. var error: cint = errno
  843. discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error))
  844. exitnow(1)
  845. when not defined(uClibc) and (not defined(linux) or defined(android)):
  846. var environ {.importc.}: cstringArray
  847. proc startProcessAfterFork(data: ptr StartProcessData) =
  848. # Warning: no GC here!
  849. # Or anything that touches global structures - all called nim procs
  850. # must be marked with stackTrace:off. Inspect C code after making changes.
  851. if not (poParentStreams in data.options):
  852. discard close(data.pStdin[writeIdx])
  853. if dup2(data.pStdin[readIdx], readIdx) < 0:
  854. startProcessFail(data)
  855. discard close(data.pStdout[readIdx])
  856. if dup2(data.pStdout[writeIdx], writeIdx) < 0:
  857. startProcessFail(data)
  858. discard close(data.pStderr[readIdx])
  859. if (poStdErrToStdOut in data.options):
  860. if dup2(data.pStdout[writeIdx], 2) < 0:
  861. startProcessFail(data)
  862. else:
  863. if dup2(data.pStderr[writeIdx], 2) < 0:
  864. startProcessFail(data)
  865. if data.workingDir.len > 0:
  866. if chdir(data.workingDir) < 0:
  867. startProcessFail(data)
  868. discard close(data.pErrorPipe[readIdx])
  869. discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC)
  870. if (poUsePath in data.options):
  871. when defined(uClibc) or defined(linux):
  872. # uClibc environment (OpenWrt included) doesn't have the full execvpe
  873. let exe = findExe(data.sysCommand)
  874. discard execve(exe, data.sysArgs, data.sysEnv)
  875. else:
  876. # MacOSX doesn't have execvpe, so we need workaround.
  877. # On MacOSX we can arrive here only from fork, so this is safe:
  878. environ = data.sysEnv
  879. discard execvp(data.sysCommand, data.sysArgs)
  880. else:
  881. discard execve(data.sysCommand, data.sysArgs, data.sysEnv)
  882. startProcessFail(data)
  883. {.pop}
  884. proc close(p: Process) =
  885. if poParentStreams notin p.options:
  886. if p.inStream != nil:
  887. close(p.inStream)
  888. else:
  889. discard close(p.inHandle)
  890. if p.outStream != nil:
  891. close(p.outStream)
  892. else:
  893. discard close(p.outHandle)
  894. if p.errStream != nil:
  895. close(p.errStream)
  896. else:
  897. discard close(p.errHandle)
  898. proc suspend(p: Process) =
  899. if kill(p.id, SIGSTOP) != 0'i32: raiseOsError(osLastError())
  900. proc resume(p: Process) =
  901. if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError())
  902. proc running(p: Process): bool =
  903. if p.exitFlag:
  904. return false
  905. else:
  906. var status: cint = 1
  907. let ret = waitpid(p.id, status, WNOHANG)
  908. if ret == int(p.id):
  909. if isExitStatus(status):
  910. p.exitFlag = true
  911. p.exitStatus = status
  912. return false
  913. else:
  914. return true
  915. elif ret == 0:
  916. return true # Can't establish status. Assume running.
  917. else:
  918. raiseOSError(osLastError())
  919. proc terminate(p: Process) =
  920. if kill(p.id, SIGTERM) != 0'i32:
  921. raiseOsError(osLastError())
  922. proc kill(p: Process) =
  923. if kill(p.id, SIGKILL) != 0'i32:
  924. raiseOsError(osLastError())
  925. when defined(macosx) or defined(freebsd) or defined(netbsd) or
  926. defined(openbsd) or defined(dragonfly):
  927. import kqueue, times
  928. proc waitForExit(p: Process, timeout: int = -1): int =
  929. if p.exitFlag:
  930. return exitStatus(p.exitStatus)
  931. if timeout == -1:
  932. var status: cint = 1
  933. if waitpid(p.id, status, 0) < 0:
  934. raiseOSError(osLastError())
  935. p.exitFlag = true
  936. p.exitStatus = status
  937. else:
  938. var kqFD = kqueue()
  939. if kqFD == -1:
  940. raiseOSError(osLastError())
  941. var kevIn = KEvent(ident: p.id.uint, filter: EVFILT_PROC,
  942. flags: EV_ADD, fflags: NOTE_EXIT)
  943. var kevOut: KEvent
  944. var tmspec: Timespec
  945. if timeout >= 1000:
  946. tmspec.tv_sec = posix.Time(timeout div 1_000)
  947. tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
  948. else:
  949. tmspec.tv_sec = posix.Time(0)
  950. tmspec.tv_nsec = (timeout * 1_000_000)
  951. try:
  952. while true:
  953. var status: cint = 1
  954. var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1,
  955. addr(tmspec))
  956. if count < 0:
  957. let err = osLastError()
  958. if err.cint != EINTR:
  959. raiseOSError(osLastError())
  960. elif count == 0:
  961. # timeout expired, so we trying to kill process
  962. if posix.kill(p.id, SIGKILL) == -1:
  963. raiseOSError(osLastError())
  964. if waitpid(p.id, status, 0) < 0:
  965. raiseOSError(osLastError())
  966. p.exitFlag = true
  967. p.exitStatus = status
  968. break
  969. else:
  970. if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC:
  971. if waitpid(p.id, status, 0) < 0:
  972. raiseOSError(osLastError())
  973. p.exitFlag = true
  974. p.exitStatus = status
  975. break
  976. else:
  977. raiseOSError(osLastError())
  978. finally:
  979. discard posix.close(kqFD)
  980. result = exitStatus(p.exitStatus)
  981. else:
  982. import times
  983. const
  984. hasThreadSupport = compileOption("threads") and not defined(nimscript)
  985. proc waitForExit(p: Process, timeout: int = -1): int =
  986. template adjustTimeout(t, s, e: Timespec) =
  987. var diff: int
  988. var b: Timespec
  989. b.tv_sec = e.tv_sec
  990. b.tv_nsec = e.tv_nsec
  991. e.tv_sec = e.tv_sec - s.tv_sec
  992. if e.tv_nsec >= s.tv_nsec:
  993. e.tv_nsec -= s.tv_nsec
  994. else:
  995. if e.tv_sec == posix.Time(0):
  996. raise newException(ValueError, "System time was modified")
  997. else:
  998. diff = s.tv_nsec - e.tv_nsec
  999. e.tv_nsec = 1_000_000_000 - diff
  1000. t.tv_sec = t.tv_sec - e.tv_sec
  1001. if t.tv_nsec >= e.tv_nsec:
  1002. t.tv_nsec -= e.tv_nsec
  1003. else:
  1004. t.tv_sec = t.tv_sec - posix.Time(1)
  1005. diff = e.tv_nsec - t.tv_nsec
  1006. t.tv_nsec = 1_000_000_000 - diff
  1007. s.tv_sec = b.tv_sec
  1008. s.tv_nsec = b.tv_nsec
  1009. if p.exitFlag:
  1010. return exitStatus(p.exitStatus)
  1011. if timeout == -1:
  1012. var status: cint = 1
  1013. if waitpid(p.id, status, 0) < 0:
  1014. raiseOSError(osLastError())
  1015. p.exitFlag = true
  1016. p.exitStatus = status
  1017. else:
  1018. var nmask, omask: Sigset
  1019. var sinfo: SigInfo
  1020. var stspec, enspec, tmspec: Timespec
  1021. discard sigemptyset(nmask)
  1022. discard sigemptyset(omask)
  1023. discard sigaddset(nmask, SIGCHLD)
  1024. when hasThreadSupport:
  1025. if pthread_sigmask(SIG_BLOCK, nmask, omask) == -1:
  1026. raiseOSError(osLastError())
  1027. else:
  1028. if sigprocmask(SIG_BLOCK, nmask, omask) == -1:
  1029. raiseOSError(osLastError())
  1030. if timeout >= 1000:
  1031. tmspec.tv_sec = posix.Time(timeout div 1_000)
  1032. tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
  1033. else:
  1034. tmspec.tv_sec = posix.Time(0)
  1035. tmspec.tv_nsec = (timeout * 1_000_000)
  1036. try:
  1037. if clock_gettime(CLOCK_REALTIME, stspec) == -1:
  1038. raiseOSError(osLastError())
  1039. while true:
  1040. let res = sigtimedwait(nmask, sinfo, tmspec)
  1041. if res == SIGCHLD:
  1042. if sinfo.si_pid == p.id:
  1043. var status: cint = 1
  1044. if waitpid(p.id, status, 0) < 0:
  1045. raiseOSError(osLastError())
  1046. p.exitFlag = true
  1047. p.exitStatus = status
  1048. break
  1049. else:
  1050. # we have SIGCHLD, but not for process we are waiting,
  1051. # so we need to adjust timeout value and continue
  1052. if clock_gettime(CLOCK_REALTIME, enspec) == -1:
  1053. raiseOSError(osLastError())
  1054. adjustTimeout(tmspec, stspec, enspec)
  1055. elif res < 0:
  1056. let err = osLastError()
  1057. if err.cint == EINTR:
  1058. # we have received another signal, so we need to
  1059. # adjust timeout and continue
  1060. if clock_gettime(CLOCK_REALTIME, enspec) == -1:
  1061. raiseOSError(osLastError())
  1062. adjustTimeout(tmspec, stspec, enspec)
  1063. elif err.cint == EAGAIN:
  1064. # timeout expired, so we trying to kill process
  1065. if posix.kill(p.id, SIGKILL) == -1:
  1066. raiseOSError(osLastError())
  1067. var status: cint = 1
  1068. if waitpid(p.id, status, 0) < 0:
  1069. raiseOSError(osLastError())
  1070. p.exitFlag = true
  1071. p.exitStatus = status
  1072. break
  1073. else:
  1074. raiseOSError(err)
  1075. finally:
  1076. when hasThreadSupport:
  1077. if pthread_sigmask(SIG_UNBLOCK, nmask, omask) == -1:
  1078. raiseOSError(osLastError())
  1079. else:
  1080. if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
  1081. raiseOSError(osLastError())
  1082. result = exitStatus(p.exitStatus)
  1083. proc peekExitCode(p: Process): int =
  1084. var status = cint(0)
  1085. result = -1
  1086. if p.exitFlag:
  1087. return exitStatus(p.exitStatus)
  1088. var ret = waitpid(p.id, status, WNOHANG)
  1089. if ret > 0:
  1090. if isExitStatus(status):
  1091. p.exitFlag = true
  1092. p.exitStatus = status
  1093. result = exitStatus(status)
  1094. proc createStream(stream: var Stream, handle: var FileHandle,
  1095. fileMode: FileMode) =
  1096. var f: File
  1097. if not open(f, handle, fileMode): raiseOSError(osLastError())
  1098. stream = newFileStream(f)
  1099. proc inputStream(p: Process): Stream =
  1100. streamAccess(p)
  1101. if p.inStream == nil:
  1102. createStream(p.inStream, p.inHandle, fmWrite)
  1103. return p.inStream
  1104. proc outputStream(p: Process): Stream =
  1105. streamAccess(p)
  1106. if p.outStream == nil:
  1107. createStream(p.outStream, p.outHandle, fmRead)
  1108. return p.outStream
  1109. proc errorStream(p: Process): Stream =
  1110. streamAccess(p)
  1111. if p.errStream == nil:
  1112. createStream(p.errStream, p.errHandle, fmRead)
  1113. return p.errStream
  1114. proc csystem(cmd: cstring): cint {.nodecl, importc: "system",
  1115. header: "<stdlib.h>".}
  1116. proc execCmd(command: string): int =
  1117. when defined(linux):
  1118. let tmp = csystem(command)
  1119. result = if tmp == -1: tmp else: exitStatus(tmp)
  1120. else:
  1121. result = csystem(command)
  1122. proc createFdSet(fd: var TFdSet, s: seq[Process], m: var int) =
  1123. FD_ZERO(fd)
  1124. for i in items(s):
  1125. m = max(m, int(i.outHandle))
  1126. FD_SET(cint(i.outHandle), fd)
  1127. proc pruneProcessSet(s: var seq[Process], fd: var TFdSet) =
  1128. var i = 0
  1129. var L = s.len
  1130. while i < L:
  1131. if FD_ISSET(cint(s[i].outHandle), fd) == 0'i32:
  1132. s[i] = s[L-1]
  1133. dec(L)
  1134. else:
  1135. inc(i)
  1136. setLen(s, L)
  1137. proc select(readfds: var seq[Process], timeout = 500): int =
  1138. var tv: Timeval
  1139. tv.tv_sec = posix.Time(0)
  1140. tv.tv_usec = timeout * 1000
  1141. var rd: TFdSet
  1142. var m = 0
  1143. createFdSet((rd), readfds, m)
  1144. if timeout != -1:
  1145. result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv)))
  1146. else:
  1147. result = int(select(cint(m+1), addr(rd), nil, nil, nil))
  1148. pruneProcessSet(readfds, (rd))
  1149. proc hasData*(p: Process): bool =
  1150. var rd: TFdSet
  1151. FD_ZERO(rd)
  1152. let m = max(0, int(p.outHandle))
  1153. FD_SET(cint(p.outHandle), rd)
  1154. result = int(select(cint(m+1), addr(rd), nil, nil, nil)) == 1
  1155. proc execCmdEx*(command: string, options: set[ProcessOption] = {
  1156. poStdErrToStdOut, poUsePath}): tuple[
  1157. output: TaintedString,
  1158. exitCode: int] {.tags:
  1159. [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} =
  1160. ## a convenience proc that runs the `command`, grabs all its output and
  1161. ## exit code and returns both.
  1162. ##
  1163. ## .. code-block:: Nim
  1164. ##
  1165. ## let (outp, errC) = execCmdEx("nim c -r mytestfile.nim")
  1166. var p = startProcess(command, options=options + {poEvalCommand})
  1167. var outp = outputStream(p)
  1168. result = (TaintedString"", -1)
  1169. var line = newStringOfCap(120).TaintedString
  1170. while true:
  1171. if outp.readLine(line):
  1172. result[0].string.add(line.string)
  1173. result[0].string.add("\n")
  1174. else:
  1175. result[1] = peekExitCode(p)
  1176. if result[1] != -1: break
  1177. close(p)