osproc.nim 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345
  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. export quoteShell, quoteShellWindows, quoteShellPosix
  15. when defined(windows):
  16. import winlean
  17. else:
  18. import posix
  19. when defined(linux):
  20. import linux
  21. type
  22. ProcessOption* = enum ## options that can be passed `startProcess`
  23. poEchoCmd, ## echo the command before execution
  24. poUsePath, ## Asks system to search for executable using PATH environment
  25. ## variable.
  26. ## On Windows, this is the default.
  27. poEvalCommand, ## Pass `command` directly to the shell, without quoting.
  28. ## Use it only if `command` comes from trusted source.
  29. poStdErrToStdOut, ## merge stdout and stderr to the stdout stream
  30. poParentStreams, ## use the parent's streams
  31. poInteractive, ## optimize the buffer handling for responsiveness for
  32. ## UI applications. Currently this only affects
  33. ## Windows: Named pipes are used so that you can peek
  34. ## at the process' output streams.
  35. poDemon ## Windows: The program creates no Window.
  36. ## Unix: Start the program as a demon. This is still
  37. ## work in progress!
  38. ProcessObj = object of RootObj
  39. when defined(windows):
  40. fProcessHandle: Handle
  41. fThreadHandle: Handle
  42. inHandle, outHandle, errHandle: FileHandle
  43. id: Handle
  44. else:
  45. inHandle, outHandle, errHandle: FileHandle
  46. inStream, outStream, errStream: Stream
  47. id: Pid
  48. exitStatus: cint
  49. exitFlag: bool
  50. options: set[ProcessOption]
  51. Process* = ref ProcessObj ## represents an operating system process
  52. const poUseShell* {.deprecated.} = poUsePath
  53. ## Deprecated alias for poUsePath.
  54. proc execProcess*(command: string,
  55. workingDir: 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. See also ``os.getCurrentProcessId()``.
  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 (absolute) 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. workingDir: string = "",
  315. args: openArray[string] = [],
  316. env: StringTableRef = nil,
  317. options: set[ProcessOption] = {poStdErrToStdOut,
  318. poUsePath,
  319. poEvalCommand}): TaintedString =
  320. var p = startProcess(command, workingDir=workingDir, args=args, env=env, options=options)
  321. var outp = outputStream(p)
  322. result = TaintedString""
  323. var line = newStringOfCap(120).TaintedString
  324. while true:
  325. # FIXME: converts CR-LF to LF.
  326. if outp.readLine(line):
  327. result.string.add(line.string)
  328. result.string.add("\n")
  329. elif not running(p): break
  330. close(p)
  331. template streamAccess(p) =
  332. assert poParentStreams notin p.options, "API usage error: stream access not allowed when you use poParentStreams"
  333. when defined(Windows) and not defined(useNimRtl):
  334. # We need to implement a handle stream for Windows:
  335. type
  336. FileHandleStream = ref object of StreamObj
  337. handle: Handle
  338. atTheEnd: bool
  339. proc hsClose(s: Stream) = discard # nothing to do here
  340. proc hsAtEnd(s: Stream): bool = return FileHandleStream(s).atTheEnd
  341. proc hsReadData(s: Stream, buffer: pointer, bufLen: int): int =
  342. var s = FileHandleStream(s)
  343. if s.atTheEnd: return 0
  344. var br: int32
  345. var a = winlean.readFile(s.handle, buffer, bufLen.cint, addr br, nil)
  346. # TRUE and zero bytes returned (EOF).
  347. # TRUE and n (>0) bytes returned (good data).
  348. # FALSE and bytes returned undefined (system error).
  349. if a == 0 and br != 0: raiseOSError(osLastError())
  350. s.atTheEnd = br == 0 #< bufLen
  351. result = br
  352. proc hsWriteData(s: Stream, buffer: pointer, bufLen: int) =
  353. var s = FileHandleStream(s)
  354. var bytesWritten: int32
  355. var a = winlean.writeFile(s.handle, buffer, bufLen.cint,
  356. addr bytesWritten, nil)
  357. if a == 0: raiseOSError(osLastError())
  358. proc newFileHandleStream(handle: Handle): FileHandleStream =
  359. new(result)
  360. result.handle = handle
  361. result.closeImpl = hsClose
  362. result.atEndImpl = hsAtEnd
  363. result.readDataImpl = hsReadData
  364. result.writeDataImpl = hsWriteData
  365. proc buildCommandLine(a: string, args: openArray[string]): string =
  366. result = quoteShell(a)
  367. for i in 0..high(args):
  368. result.add(' ')
  369. result.add(quoteShell(args[i]))
  370. proc buildEnv(env: StringTableRef): tuple[str: cstring, len: int] =
  371. var L = 0
  372. for key, val in pairs(env): inc(L, key.len + val.len + 2)
  373. var str = cast[cstring](alloc0(L+2))
  374. L = 0
  375. for key, val in pairs(env):
  376. var x = key & "=" & val
  377. copyMem(addr(str[L]), cstring(x), x.len+1) # copy \0
  378. inc(L, x.len+1)
  379. (str, L)
  380. #proc open_osfhandle(osh: Handle, mode: int): int {.
  381. # importc: "_open_osfhandle", header: "<fcntl.h>".}
  382. #var
  383. # O_WRONLY {.importc: "_O_WRONLY", header: "<fcntl.h>".}: int
  384. # O_RDONLY {.importc: "_O_RDONLY", header: "<fcntl.h>".}: int
  385. proc myDup(h: Handle; inherit: WinBool=1): Handle =
  386. let thisProc = getCurrentProcess()
  387. if duplicateHandle(thisProc, h,
  388. thisProc, addr result,0,inherit,
  389. DUPLICATE_SAME_ACCESS) == 0:
  390. raiseOSError(osLastError())
  391. proc createAllPipeHandles(si: var STARTUPINFO;
  392. stdin, stdout, stderr: var Handle;
  393. hash: int) =
  394. var sa: SECURITY_ATTRIBUTES
  395. sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint
  396. sa.lpSecurityDescriptor = nil
  397. sa.bInheritHandle = 1
  398. let pipeOutName = newWideCString(r"\\.\pipe\stdout" & $hash)
  399. let pipeInName = newWideCString(r"\\.\pipe\stdin" & $hash)
  400. let pipeOut = createNamedPipe(pipeOutName,
  401. dwOpenMode=PIPE_ACCESS_INBOUND or FILE_FLAG_WRITE_THROUGH,
  402. dwPipeMode=PIPE_NOWAIT,
  403. nMaxInstances=1,
  404. nOutBufferSize=1024, nInBufferSize=1024,
  405. nDefaultTimeOut=0,addr sa)
  406. if pipeOut == INVALID_HANDLE_VALUE:
  407. raiseOSError(osLastError())
  408. let pipeIn = createNamedPipe(pipeInName,
  409. dwOpenMode=PIPE_ACCESS_OUTBOUND or FILE_FLAG_WRITE_THROUGH,
  410. dwPipeMode=PIPE_NOWAIT,
  411. nMaxInstances=1,
  412. nOutBufferSize=1024, nInBufferSize=1024,
  413. nDefaultTimeOut=0,addr sa)
  414. if pipeIn == INVALID_HANDLE_VALUE:
  415. raiseOSError(osLastError())
  416. si.hStdOutput = createFileW(pipeOutName,
  417. FILE_WRITE_DATA or SYNCHRONIZE, 0, addr sa,
  418. OPEN_EXISTING, # very important flag!
  419. FILE_ATTRIBUTE_NORMAL,
  420. 0 # no template file for OPEN_EXISTING
  421. )
  422. if si.hStdOutput == INVALID_HANDLE_VALUE:
  423. raiseOSError(osLastError())
  424. si.hStdError = myDup(si.hStdOutput)
  425. si.hStdInput = createFileW(pipeInName,
  426. FILE_READ_DATA or SYNCHRONIZE, 0, addr sa,
  427. OPEN_EXISTING, # very important flag!
  428. FILE_ATTRIBUTE_NORMAL,
  429. 0 # no template file for OPEN_EXISTING
  430. )
  431. if si.hStdOutput == INVALID_HANDLE_VALUE:
  432. raiseOSError(osLastError())
  433. stdin = myDup(pipeIn, 0)
  434. stdout = myDup(pipeOut, 0)
  435. discard closeHandle(pipeIn)
  436. discard closeHandle(pipeOut)
  437. stderr = stdout
  438. proc createPipeHandles(rdHandle, wrHandle: var Handle) =
  439. var sa: SECURITY_ATTRIBUTES
  440. sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint
  441. sa.lpSecurityDescriptor = nil
  442. sa.bInheritHandle = 1
  443. if createPipe(rdHandle, wrHandle, sa, 0) == 0'i32:
  444. raiseOSError(osLastError())
  445. proc fileClose(h: Handle) {.inline.} =
  446. if h > 4: discard closeHandle(h)
  447. proc startProcess(command: string,
  448. workingDir: string = "",
  449. args: openArray[string] = [],
  450. env: StringTableRef = nil,
  451. options: set[ProcessOption] = {poStdErrToStdOut}): Process =
  452. var
  453. si: STARTUPINFO
  454. procInfo: PROCESS_INFORMATION
  455. success: int
  456. hi, ho, he: Handle
  457. new(result)
  458. result.options = options
  459. result.exitFlag = true
  460. si.cb = sizeof(si).cint
  461. if poParentStreams notin options:
  462. si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or
  463. if poInteractive notin options:
  464. createPipeHandles(si.hStdInput, hi)
  465. createPipeHandles(ho, si.hStdOutput)
  466. if poStdErrToStdOut in options:
  467. si.hStdError = si.hStdOutput
  468. he = ho
  469. else:
  470. createPipeHandles(he, si.hStdError)
  471. if setHandleInformation(he, DWORD(1), DWORD(0)) == 0'i32:
  472. raiseOsError(osLastError())
  473. if setHandleInformation(hi, DWORD(1), DWORD(0)) == 0'i32:
  474. raiseOsError(osLastError())
  475. if setHandleInformation(ho, DWORD(1), DWORD(0)) == 0'i32:
  476. raiseOsError(osLastError())
  477. else:
  478. createAllPipeHandles(si, hi, ho, he, cast[int](result))
  479. result.inHandle = FileHandle(hi)
  480. result.outHandle = FileHandle(ho)
  481. result.errHandle = FileHandle(he)
  482. else:
  483. si.hStdError = getStdHandle(STD_ERROR_HANDLE)
  484. si.hStdInput = getStdHandle(STD_INPUT_HANDLE)
  485. si.hStdOutput = getStdHandle(STD_OUTPUT_HANDLE)
  486. result.inHandle = FileHandle(si.hStdInput)
  487. result.outHandle = FileHandle(si.hStdOutput)
  488. result.errHandle = FileHandle(si.hStdError)
  489. var cmdl: cstring
  490. var cmdRoot: string
  491. if poEvalCommand in options:
  492. cmdl = command
  493. assert args.len == 0
  494. else:
  495. cmdRoot = buildCommandLine(command, args)
  496. cmdl = cstring(cmdRoot)
  497. var wd: cstring = nil
  498. var e = (str: nil.cstring, len: -1)
  499. if len(workingDir) > 0: wd = workingDir
  500. if env != nil: e = buildEnv(env)
  501. if poEchoCmd in options: echo($cmdl)
  502. when useWinUnicode:
  503. var tmp = newWideCString(cmdl)
  504. var ee =
  505. if e.str.isNil: nil
  506. else: newWideCString(e.str, e.len)
  507. var wwd = newWideCString(wd)
  508. var flags = NORMAL_PRIORITY_CLASS or CREATE_UNICODE_ENVIRONMENT
  509. if poDemon in options: flags = flags or CREATE_NO_WINDOW
  510. success = winlean.createProcessW(nil, tmp, nil, nil, 1, flags,
  511. ee, wwd, si, procInfo)
  512. else:
  513. success = winlean.createProcessA(nil,
  514. cmdl, nil, nil, 1, NORMAL_PRIORITY_CLASS, e, wd, si, procInfo)
  515. let lastError = osLastError()
  516. if poParentStreams notin options:
  517. fileClose(si.hStdInput)
  518. fileClose(si.hStdOutput)
  519. if poStdErrToStdOut notin options:
  520. fileClose(si.hStdError)
  521. if e.str != nil: dealloc(e.str)
  522. if success == 0:
  523. if poInteractive in result.options: close(result)
  524. const errInvalidParameter = 87.int
  525. const errFileNotFound = 2.int
  526. if lastError.int in {errInvalidParameter, errFileNotFound}:
  527. raiseOSError(lastError,
  528. "Requested command not found: '$1'. OS error:" % command)
  529. else:
  530. raiseOSError(lastError, command)
  531. result.fProcessHandle = procInfo.hProcess
  532. result.fThreadHandle = procInfo.hThread
  533. result.id = procInfo.dwProcessId
  534. result.exitFlag = false
  535. proc close(p: Process) =
  536. if poParentStreams notin p.options:
  537. discard closeHandle(p.inHandle)
  538. discard closeHandle(p.outHandle)
  539. discard closeHandle(p.errHandle)
  540. discard closeHandle(p.fThreadHandle)
  541. discard closeHandle(p.fProcessHandle)
  542. proc suspend(p: Process) =
  543. discard suspendThread(p.fThreadHandle)
  544. proc resume(p: Process) =
  545. discard resumeThread(p.fThreadHandle)
  546. proc running(p: Process): bool =
  547. if p.exitFlag:
  548. return false
  549. else:
  550. var x = waitForSingleObject(p.fProcessHandle, 0)
  551. return x == WAIT_TIMEOUT
  552. proc terminate(p: Process) =
  553. if running(p):
  554. discard terminateProcess(p.fProcessHandle, 0)
  555. proc kill(p: Process) =
  556. terminate(p)
  557. proc waitForExit(p: Process, timeout: int = -1): int =
  558. if p.exitFlag:
  559. return p.exitStatus
  560. let res = waitForSingleObject(p.fProcessHandle, timeout.int32)
  561. if res == WAIT_TIMEOUT:
  562. terminate(p)
  563. var status: int32
  564. discard getExitCodeProcess(p.fProcessHandle, status)
  565. if status != STILL_ACTIVE:
  566. p.exitFlag = true
  567. p.exitStatus = status
  568. discard closeHandle(p.fThreadHandle)
  569. discard closeHandle(p.fProcessHandle)
  570. result = status
  571. else:
  572. result = -1
  573. proc peekExitCode(p: Process): int =
  574. if p.exitFlag:
  575. return p.exitStatus
  576. result = -1
  577. var b = waitForSingleObject(p.fProcessHandle, 0) == WAIT_TIMEOUT
  578. if not b:
  579. var status: int32
  580. discard getExitCodeProcess(p.fProcessHandle, status)
  581. p.exitFlag = true
  582. p.exitStatus = status
  583. discard closeHandle(p.fThreadHandle)
  584. discard closeHandle(p.fProcessHandle)
  585. result = status
  586. proc inputStream(p: Process): Stream =
  587. streamAccess(p)
  588. result = newFileHandleStream(p.inHandle)
  589. proc outputStream(p: Process): Stream =
  590. streamAccess(p)
  591. result = newFileHandleStream(p.outHandle)
  592. proc errorStream(p: Process): Stream =
  593. streamAccess(p)
  594. result = newFileHandleStream(p.errHandle)
  595. proc execCmd(command: string): int =
  596. var
  597. si: STARTUPINFO
  598. procInfo: PROCESS_INFORMATION
  599. process: Handle
  600. L: int32
  601. si.cb = sizeof(si).cint
  602. si.hStdError = getStdHandle(STD_ERROR_HANDLE)
  603. si.hStdInput = getStdHandle(STD_INPUT_HANDLE)
  604. si.hStdOutput = getStdHandle(STD_OUTPUT_HANDLE)
  605. when useWinUnicode:
  606. var c = newWideCString(command)
  607. var res = winlean.createProcessW(nil, c, nil, nil, 0,
  608. NORMAL_PRIORITY_CLASS, nil, nil, si, procInfo)
  609. else:
  610. var res = winlean.createProcessA(nil, command, nil, nil, 0,
  611. NORMAL_PRIORITY_CLASS, nil, nil, si, procInfo)
  612. if res == 0:
  613. raiseOSError(osLastError())
  614. else:
  615. process = procInfo.hProcess
  616. discard closeHandle(procInfo.hThread)
  617. if waitForSingleObject(process, INFINITE) != -1:
  618. discard getExitCodeProcess(process, L)
  619. result = int(L)
  620. else:
  621. result = -1
  622. discard closeHandle(process)
  623. proc select(readfds: var seq[Process], timeout = 500): int =
  624. assert readfds.len <= MAXIMUM_WAIT_OBJECTS
  625. var rfds: WOHandleArray
  626. for i in 0..readfds.len()-1:
  627. rfds[i] = readfds[i].outHandle #fProcessHandle
  628. var ret = waitForMultipleObjects(readfds.len.int32,
  629. addr(rfds), 0'i32, timeout.int32)
  630. case ret
  631. of WAIT_TIMEOUT:
  632. return 0
  633. of WAIT_FAILED:
  634. raiseOSError(osLastError())
  635. else:
  636. var i = ret - WAIT_OBJECT_0
  637. readfds.del(i)
  638. return 1
  639. proc hasData*(p: Process): bool =
  640. var x: int32
  641. if peekNamedPipe(p.outHandle, lpTotalBytesAvail=addr x):
  642. result = x > 0
  643. elif not defined(useNimRtl):
  644. const
  645. readIdx = 0
  646. writeIdx = 1
  647. proc isExitStatus(status: cint): bool =
  648. WIFEXITED(status) or WIFSIGNALED(status)
  649. proc exitStatus(status: cint): cint =
  650. if WIFSIGNALED(status):
  651. # like the shell!
  652. 128 + WTERMSIG(status)
  653. else:
  654. WEXITSTATUS(status)
  655. proc envToCStringArray(t: StringTableRef): cstringArray =
  656. result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring)))
  657. var i = 0
  658. for key, val in pairs(t):
  659. var x = key & "=" & val
  660. result[i] = cast[cstring](alloc(x.len+1))
  661. copyMem(result[i], addr(x[0]), x.len+1)
  662. inc(i)
  663. proc envToCStringArray(): cstringArray =
  664. var counter = 0
  665. for key, val in envPairs(): inc counter
  666. result = cast[cstringArray](alloc0((counter + 1) * sizeof(cstring)))
  667. var i = 0
  668. for key, val in envPairs():
  669. var x = key.string & "=" & val.string
  670. result[i] = cast[cstring](alloc(x.len+1))
  671. copyMem(result[i], addr(x[0]), x.len+1)
  672. inc(i)
  673. type
  674. StartProcessData = object
  675. sysCommand: string
  676. sysArgs: cstringArray
  677. sysEnv: cstringArray
  678. workingDir: cstring
  679. pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint]
  680. options: set[ProcessOption]
  681. const useProcessAuxSpawn = declared(posix_spawn) and not defined(useFork) and
  682. not defined(useClone) and not defined(linux)
  683. when useProcessAuxSpawn:
  684. proc startProcessAuxSpawn(data: StartProcessData): Pid {.
  685. tags: [ExecIOEffect, ReadEnvEffect, ReadDirEffect, RootEffect], gcsafe.}
  686. else:
  687. proc startProcessAuxFork(data: StartProcessData): Pid {.
  688. tags: [ExecIOEffect, ReadEnvEffect, ReadDirEffect, RootEffect], gcsafe.}
  689. {.push stacktrace: off, profiler: off.}
  690. proc startProcessAfterFork(data: ptr StartProcessData) {.
  691. tags: [ExecIOEffect, ReadEnvEffect, ReadDirEffect, RootEffect], cdecl, gcsafe.}
  692. {.pop.}
  693. proc startProcess(command: string,
  694. workingDir: string = "",
  695. args: openArray[string] = [],
  696. env: StringTableRef = nil,
  697. options: set[ProcessOption] = {poStdErrToStdOut}): Process =
  698. var
  699. pStdin, pStdout, pStderr: array[0..1, cint]
  700. new(result)
  701. result.options = options
  702. result.exitFlag = true
  703. if poParentStreams notin options:
  704. if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or
  705. pipe(pStderr) != 0'i32:
  706. raiseOSError(osLastError())
  707. var sysCommand: string
  708. var sysArgsRaw: seq[string]
  709. if poEvalCommand in options:
  710. const useShPath {.strdefine.} =
  711. when not defined(android): "/bin/sh"
  712. else: "/system/bin/sh"
  713. sysCommand = useShPath
  714. sysArgsRaw = @[sysCommand, "-c", command]
  715. assert args.len == 0, "`args` has to be empty when using poEvalCommand."
  716. else:
  717. sysCommand = command
  718. sysArgsRaw = @[command]
  719. for arg in args.items:
  720. sysArgsRaw.add arg
  721. var pid: Pid
  722. var sysArgs = allocCStringArray(sysArgsRaw)
  723. defer: deallocCStringArray(sysArgs)
  724. var sysEnv = if env == nil:
  725. envToCStringArray()
  726. else:
  727. envToCStringArray(env)
  728. defer: deallocCStringArray(sysEnv)
  729. var data: StartProcessData
  730. shallowCopy(data.sysCommand, sysCommand)
  731. data.sysArgs = sysArgs
  732. data.sysEnv = sysEnv
  733. data.pStdin = pStdin
  734. data.pStdout = pStdout
  735. data.pStderr = pStderr
  736. data.workingDir = workingDir
  737. data.options = options
  738. when useProcessAuxSpawn:
  739. var currentDir = getCurrentDir()
  740. pid = startProcessAuxSpawn(data)
  741. if workingDir.len > 0:
  742. setCurrentDir(currentDir)
  743. else:
  744. pid = startProcessAuxFork(data)
  745. # Parent process. Copy process information.
  746. if poEchoCmd in options:
  747. when declared(echo): echo(command, " ", join(args, " "))
  748. result.id = pid
  749. result.exitFlag = false
  750. if poParentStreams in options:
  751. # does not make much sense, but better than nothing:
  752. result.inHandle = 0
  753. result.outHandle = 1
  754. if poStdErrToStdOut in options:
  755. result.errHandle = result.outHandle
  756. else:
  757. result.errHandle = 2
  758. else:
  759. result.inHandle = pStdin[writeIdx]
  760. result.outHandle = pStdout[readIdx]
  761. if poStdErrToStdOut in options:
  762. result.errHandle = result.outHandle
  763. discard close(pStderr[readIdx])
  764. else:
  765. result.errHandle = pStderr[readIdx]
  766. discard close(pStderr[writeIdx])
  767. discard close(pStdin[readIdx])
  768. discard close(pStdout[writeIdx])
  769. when useProcessAuxSpawn:
  770. proc startProcessAuxSpawn(data: StartProcessData): Pid =
  771. var attr: Tposix_spawnattr
  772. var fops: Tposix_spawn_file_actions
  773. template chck(e: untyped) =
  774. if e != 0'i32: raiseOSError(osLastError())
  775. chck posix_spawn_file_actions_init(fops)
  776. chck posix_spawnattr_init(attr)
  777. var mask: Sigset
  778. chck sigemptyset(mask)
  779. chck posix_spawnattr_setsigmask(attr, mask)
  780. if poDemon in data.options:
  781. chck posix_spawnattr_setpgroup(attr, 0'i32)
  782. var flags = POSIX_SPAWN_USEVFORK or
  783. POSIX_SPAWN_SETSIGMASK
  784. if poDemon in data.options:
  785. flags = flags or POSIX_SPAWN_SETPGROUP
  786. chck posix_spawnattr_setflags(attr, flags)
  787. if not (poParentStreams in data.options):
  788. chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx])
  789. chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx)
  790. chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx])
  791. chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx)
  792. chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx])
  793. if poStdErrToStdOut in data.options:
  794. chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2)
  795. else:
  796. chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2)
  797. var res: cint
  798. if data.workingDir.len > 0:
  799. setCurrentDir($data.workingDir)
  800. var pid: Pid
  801. if (poUsePath in data.options):
  802. res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
  803. else:
  804. res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
  805. discard posix_spawn_file_actions_destroy(fops)
  806. discard posix_spawnattr_destroy(attr)
  807. if res != 0'i32: raiseOSError(OSErrorCode(res), data.sysCommand)
  808. return pid
  809. else:
  810. proc startProcessAuxFork(data: StartProcessData): Pid =
  811. if pipe(data.pErrorPipe) != 0:
  812. raiseOSError(osLastError())
  813. defer:
  814. discard close(data.pErrorPipe[readIdx])
  815. var pid: Pid
  816. var dataCopy = data
  817. when defined(useClone):
  818. const stackSize = 65536
  819. let stackEnd = cast[clong](alloc(stackSize))
  820. let stack = cast[pointer](stackEnd + stackSize)
  821. let fn: pointer = startProcessAfterFork
  822. pid = clone(fn, stack,
  823. cint(CLONE_VM or CLONE_VFORK or SIGCHLD),
  824. pointer(addr dataCopy), nil, nil, nil)
  825. discard close(data.pErrorPipe[writeIdx])
  826. dealloc(stack)
  827. else:
  828. pid = fork()
  829. if pid == 0:
  830. startProcessAfterFork(addr(dataCopy))
  831. exitnow(1)
  832. discard close(data.pErrorPipe[writeIdx])
  833. if pid < 0: raiseOSError(osLastError())
  834. var error: cint
  835. let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error))
  836. if sizeRead == sizeof(error):
  837. raiseOSError(osLastError(),
  838. "Could not find command: '$1'. OS error: $2" %
  839. [$data.sysCommand, $strerror(error)])
  840. return pid
  841. {.push stacktrace: off, profiler: off.}
  842. proc startProcessFail(data: ptr StartProcessData) =
  843. var error: cint = errno
  844. discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error))
  845. exitnow(1)
  846. when not defined(uClibc) and (not defined(linux) or defined(android)):
  847. var environ {.importc.}: cstringArray
  848. proc startProcessAfterFork(data: ptr StartProcessData) =
  849. # Warning: no GC here!
  850. # Or anything that touches global structures - all called nim procs
  851. # must be marked with stackTrace:off. Inspect C code after making changes.
  852. if not (poParentStreams in data.options):
  853. discard close(data.pStdin[writeIdx])
  854. if dup2(data.pStdin[readIdx], readIdx) < 0:
  855. startProcessFail(data)
  856. discard close(data.pStdout[readIdx])
  857. if dup2(data.pStdout[writeIdx], writeIdx) < 0:
  858. startProcessFail(data)
  859. discard close(data.pStderr[readIdx])
  860. if (poStdErrToStdOut in data.options):
  861. if dup2(data.pStdout[writeIdx], 2) < 0:
  862. startProcessFail(data)
  863. else:
  864. if dup2(data.pStderr[writeIdx], 2) < 0:
  865. startProcessFail(data)
  866. if data.workingDir.len > 0:
  867. if chdir(data.workingDir) < 0:
  868. startProcessFail(data)
  869. discard close(data.pErrorPipe[readIdx])
  870. discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC)
  871. if (poUsePath in data.options):
  872. when defined(uClibc) or defined(linux):
  873. # uClibc environment (OpenWrt included) doesn't have the full execvpe
  874. let exe = findExe(data.sysCommand)
  875. discard execve(exe, data.sysArgs, data.sysEnv)
  876. else:
  877. # MacOSX doesn't have execvpe, so we need workaround.
  878. # On MacOSX we can arrive here only from fork, so this is safe:
  879. environ = data.sysEnv
  880. discard execvp(data.sysCommand, data.sysArgs)
  881. else:
  882. discard execve(data.sysCommand, data.sysArgs, data.sysEnv)
  883. startProcessFail(data)
  884. {.pop}
  885. proc close(p: Process) =
  886. if poParentStreams notin p.options:
  887. if p.inStream != nil:
  888. close(p.inStream)
  889. else:
  890. discard close(p.inHandle)
  891. if p.outStream != nil:
  892. close(p.outStream)
  893. else:
  894. discard close(p.outHandle)
  895. if p.errStream != nil:
  896. close(p.errStream)
  897. else:
  898. discard close(p.errHandle)
  899. proc suspend(p: Process) =
  900. if kill(p.id, SIGSTOP) != 0'i32: raiseOsError(osLastError())
  901. proc resume(p: Process) =
  902. if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError())
  903. proc running(p: Process): bool =
  904. if p.exitFlag:
  905. return false
  906. else:
  907. var status: cint = 1
  908. let ret = waitpid(p.id, status, WNOHANG)
  909. if ret == int(p.id):
  910. if isExitStatus(status):
  911. p.exitFlag = true
  912. p.exitStatus = status
  913. return false
  914. else:
  915. return true
  916. elif ret == 0:
  917. return true # Can't establish status. Assume running.
  918. else:
  919. raiseOSError(osLastError())
  920. proc terminate(p: Process) =
  921. if kill(p.id, SIGTERM) != 0'i32:
  922. raiseOsError(osLastError())
  923. proc kill(p: Process) =
  924. if kill(p.id, SIGKILL) != 0'i32:
  925. raiseOsError(osLastError())
  926. when defined(macosx) or defined(freebsd) or defined(netbsd) or
  927. defined(openbsd) or defined(dragonfly):
  928. import kqueue, times
  929. proc waitForExit(p: Process, timeout: int = -1): int =
  930. if p.exitFlag:
  931. return exitStatus(p.exitStatus)
  932. if timeout == -1:
  933. var status: cint = 1
  934. if waitpid(p.id, status, 0) < 0:
  935. raiseOSError(osLastError())
  936. p.exitFlag = true
  937. p.exitStatus = status
  938. else:
  939. var kqFD = kqueue()
  940. if kqFD == -1:
  941. raiseOSError(osLastError())
  942. var kevIn = KEvent(ident: p.id.uint, filter: EVFILT_PROC,
  943. flags: EV_ADD, fflags: NOTE_EXIT)
  944. var kevOut: KEvent
  945. var tmspec: Timespec
  946. if timeout >= 1000:
  947. tmspec.tv_sec = posix.Time(timeout div 1_000)
  948. tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
  949. else:
  950. tmspec.tv_sec = posix.Time(0)
  951. tmspec.tv_nsec = (timeout * 1_000_000)
  952. try:
  953. while true:
  954. var status: cint = 1
  955. var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1,
  956. addr(tmspec))
  957. if count < 0:
  958. let err = osLastError()
  959. if err.cint != EINTR:
  960. raiseOSError(osLastError())
  961. elif count == 0:
  962. # timeout expired, so we trying to kill process
  963. if posix.kill(p.id, SIGKILL) == -1:
  964. raiseOSError(osLastError())
  965. if waitpid(p.id, status, 0) < 0:
  966. raiseOSError(osLastError())
  967. p.exitFlag = true
  968. p.exitStatus = status
  969. break
  970. else:
  971. if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC:
  972. if waitpid(p.id, status, 0) < 0:
  973. raiseOSError(osLastError())
  974. p.exitFlag = true
  975. p.exitStatus = status
  976. break
  977. else:
  978. raiseOSError(osLastError())
  979. finally:
  980. discard posix.close(kqFD)
  981. result = exitStatus(p.exitStatus)
  982. else:
  983. import times
  984. const
  985. hasThreadSupport = compileOption("threads") and not defined(nimscript)
  986. proc waitForExit(p: Process, timeout: int = -1): int =
  987. template adjustTimeout(t, s, e: Timespec) =
  988. var diff: int
  989. var b: Timespec
  990. b.tv_sec = e.tv_sec
  991. b.tv_nsec = e.tv_nsec
  992. e.tv_sec = e.tv_sec - s.tv_sec
  993. if e.tv_nsec >= s.tv_nsec:
  994. e.tv_nsec -= s.tv_nsec
  995. else:
  996. if e.tv_sec == posix.Time(0):
  997. raise newException(ValueError, "System time was modified")
  998. else:
  999. diff = s.tv_nsec - e.tv_nsec
  1000. e.tv_nsec = 1_000_000_000 - diff
  1001. t.tv_sec = t.tv_sec - e.tv_sec
  1002. if t.tv_nsec >= e.tv_nsec:
  1003. t.tv_nsec -= e.tv_nsec
  1004. else:
  1005. t.tv_sec = t.tv_sec - posix.Time(1)
  1006. diff = e.tv_nsec - t.tv_nsec
  1007. t.tv_nsec = 1_000_000_000 - diff
  1008. s.tv_sec = b.tv_sec
  1009. s.tv_nsec = b.tv_nsec
  1010. if p.exitFlag:
  1011. return exitStatus(p.exitStatus)
  1012. if timeout == -1:
  1013. var status: cint = 1
  1014. if waitpid(p.id, status, 0) < 0:
  1015. raiseOSError(osLastError())
  1016. p.exitFlag = true
  1017. p.exitStatus = status
  1018. else:
  1019. var nmask, omask: Sigset
  1020. var sinfo: SigInfo
  1021. var stspec, enspec, tmspec: Timespec
  1022. discard sigemptyset(nmask)
  1023. discard sigemptyset(omask)
  1024. discard sigaddset(nmask, SIGCHLD)
  1025. when hasThreadSupport:
  1026. if pthread_sigmask(SIG_BLOCK, nmask, omask) == -1:
  1027. raiseOSError(osLastError())
  1028. else:
  1029. if sigprocmask(SIG_BLOCK, nmask, omask) == -1:
  1030. raiseOSError(osLastError())
  1031. if timeout >= 1000:
  1032. tmspec.tv_sec = posix.Time(timeout div 1_000)
  1033. tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
  1034. else:
  1035. tmspec.tv_sec = posix.Time(0)
  1036. tmspec.tv_nsec = (timeout * 1_000_000)
  1037. try:
  1038. if clock_gettime(CLOCK_REALTIME, stspec) == -1:
  1039. raiseOSError(osLastError())
  1040. while true:
  1041. let res = sigtimedwait(nmask, sinfo, tmspec)
  1042. if res == SIGCHLD:
  1043. if sinfo.si_pid == p.id:
  1044. var status: cint = 1
  1045. if waitpid(p.id, status, 0) < 0:
  1046. raiseOSError(osLastError())
  1047. p.exitFlag = true
  1048. p.exitStatus = status
  1049. break
  1050. else:
  1051. # we have SIGCHLD, but not for process we are waiting,
  1052. # so we need to adjust timeout value and continue
  1053. if clock_gettime(CLOCK_REALTIME, enspec) == -1:
  1054. raiseOSError(osLastError())
  1055. adjustTimeout(tmspec, stspec, enspec)
  1056. elif res < 0:
  1057. let err = osLastError()
  1058. if err.cint == EINTR:
  1059. # we have received another signal, so we need to
  1060. # adjust timeout and continue
  1061. if clock_gettime(CLOCK_REALTIME, enspec) == -1:
  1062. raiseOSError(osLastError())
  1063. adjustTimeout(tmspec, stspec, enspec)
  1064. elif err.cint == EAGAIN:
  1065. # timeout expired, so we trying to kill process
  1066. if posix.kill(p.id, SIGKILL) == -1:
  1067. raiseOSError(osLastError())
  1068. var status: cint = 1
  1069. if waitpid(p.id, status, 0) < 0:
  1070. raiseOSError(osLastError())
  1071. p.exitFlag = true
  1072. p.exitStatus = status
  1073. break
  1074. else:
  1075. raiseOSError(err)
  1076. finally:
  1077. when hasThreadSupport:
  1078. if pthread_sigmask(SIG_UNBLOCK, nmask, omask) == -1:
  1079. raiseOSError(osLastError())
  1080. else:
  1081. if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
  1082. raiseOSError(osLastError())
  1083. result = exitStatus(p.exitStatus)
  1084. proc peekExitCode(p: Process): int =
  1085. var status = cint(0)
  1086. result = -1
  1087. if p.exitFlag:
  1088. return exitStatus(p.exitStatus)
  1089. var ret = waitpid(p.id, status, WNOHANG)
  1090. if ret > 0:
  1091. if isExitStatus(status):
  1092. p.exitFlag = true
  1093. p.exitStatus = status
  1094. result = exitStatus(status)
  1095. proc createStream(stream: var Stream, handle: var FileHandle,
  1096. fileMode: FileMode) =
  1097. var f: File
  1098. if not open(f, handle, fileMode): raiseOSError(osLastError())
  1099. stream = newFileStream(f)
  1100. proc inputStream(p: Process): Stream =
  1101. streamAccess(p)
  1102. if p.inStream == nil:
  1103. createStream(p.inStream, p.inHandle, fmWrite)
  1104. return p.inStream
  1105. proc outputStream(p: Process): Stream =
  1106. streamAccess(p)
  1107. if p.outStream == nil:
  1108. createStream(p.outStream, p.outHandle, fmRead)
  1109. return p.outStream
  1110. proc errorStream(p: Process): Stream =
  1111. streamAccess(p)
  1112. if p.errStream == nil:
  1113. createStream(p.errStream, p.errHandle, fmRead)
  1114. return p.errStream
  1115. proc csystem(cmd: cstring): cint {.nodecl, importc: "system",
  1116. header: "<stdlib.h>".}
  1117. proc execCmd(command: string): int =
  1118. when defined(linux):
  1119. let tmp = csystem(command)
  1120. result = if tmp == -1: tmp else: exitStatus(tmp)
  1121. else:
  1122. result = csystem(command)
  1123. proc createFdSet(fd: var TFdSet, s: seq[Process], m: var int) =
  1124. FD_ZERO(fd)
  1125. for i in items(s):
  1126. m = max(m, int(i.outHandle))
  1127. FD_SET(cint(i.outHandle), fd)
  1128. proc pruneProcessSet(s: var seq[Process], fd: var TFdSet) =
  1129. var i = 0
  1130. var L = s.len
  1131. while i < L:
  1132. if FD_ISSET(cint(s[i].outHandle), fd) == 0'i32:
  1133. s[i] = s[L-1]
  1134. dec(L)
  1135. else:
  1136. inc(i)
  1137. setLen(s, L)
  1138. proc select(readfds: var seq[Process], timeout = 500): int =
  1139. var tv: Timeval
  1140. tv.tv_sec = posix.Time(0)
  1141. tv.tv_usec = timeout * 1000
  1142. var rd: TFdSet
  1143. var m = 0
  1144. createFdSet((rd), readfds, m)
  1145. if timeout != -1:
  1146. result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv)))
  1147. else:
  1148. result = int(select(cint(m+1), addr(rd), nil, nil, nil))
  1149. pruneProcessSet(readfds, (rd))
  1150. proc hasData*(p: Process): bool =
  1151. var rd: TFdSet
  1152. FD_ZERO(rd)
  1153. let m = max(0, int(p.outHandle))
  1154. FD_SET(cint(p.outHandle), rd)
  1155. result = int(select(cint(m+1), addr(rd), nil, nil, nil)) == 1
  1156. proc execCmdEx*(command: string, options: set[ProcessOption] = {
  1157. poStdErrToStdOut, poUsePath}): tuple[
  1158. output: TaintedString,
  1159. exitCode: int] {.tags:
  1160. [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} =
  1161. ## a convenience proc that runs the `command`, grabs all its output and
  1162. ## exit code and returns both.
  1163. ##
  1164. ## .. code-block:: Nim
  1165. ##
  1166. ## let (outp, errC) = execCmdEx("nim c -r mytestfile.nim")
  1167. var p = startProcess(command, options=options + {poEvalCommand})
  1168. var outp = outputStream(p)
  1169. # There is no way to provide input for the child process
  1170. # anymore. Closing it will create EOF on stdin instead of eternal
  1171. # blocking.
  1172. close inputStream(p)
  1173. result = (TaintedString"", -1)
  1174. var line = newStringOfCap(120).TaintedString
  1175. while true:
  1176. if outp.readLine(line):
  1177. result[0].string.add(line.string)
  1178. result[0].string.add("\n")
  1179. else:
  1180. result[1] = peekExitCode(p)
  1181. if result[1] != -1: break
  1182. close(p)