osproc.nim 45 KB

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