osproc.nim 55 KB

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