osproc.nim 48 KB

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