]>
Commit | Line | Data |
---|---|---|
b4c522fa IB |
1 | // Written in the D programming language. |
2 | ||
3 | /** | |
4 | Functions for starting and interacting with other processes, and for | |
5fee5ec3 | 5 | working with the current process' execution environment. |
b4c522fa IB |
6 | |
7 | Process_handling: | |
8 | $(UL $(LI | |
5fee5ec3 | 9 | $(LREF spawnProcess) spawns a new process, optionally assigning it an |
b4c522fa | 10 | arbitrary set of standard input, output, and error streams. |
5fee5ec3 | 11 | The function returns immediately, leaving the child process to execute |
b4c522fa | 12 | in parallel with its parent. All other functions in this module that |
5fee5ec3 | 13 | spawn processes are built around `spawnProcess`.) |
b4c522fa | 14 | $(LI |
5fee5ec3 | 15 | $(LREF wait) makes the parent process wait for a child process to |
b4c522fa | 16 | terminate. In general one should always do this, to avoid |
5fee5ec3 | 17 | child processes becoming "zombies" when the parent process exits. |
b4c522fa | 18 | Scope guards are perfect for this – see the $(LREF spawnProcess) |
5fee5ec3 IB |
19 | documentation for examples. $(LREF tryWait) is similar to `wait`, |
20 | but does not block if the process has not yet terminated.) | |
b4c522fa | 21 | $(LI |
5fee5ec3 | 22 | $(LREF pipeProcess) also spawns a child process which runs |
b4c522fa IB |
23 | in parallel with its parent. However, instead of taking |
24 | arbitrary streams, it automatically creates a set of | |
25 | pipes that allow the parent to communicate with the child | |
26 | through the child's standard input, output, and/or error streams. | |
5fee5ec3 | 27 | This function corresponds roughly to C's `popen` function.) |
b4c522fa | 28 | $(LI |
5fee5ec3 | 29 | $(LREF execute) starts a new process and waits for it |
b4c522fa | 30 | to complete before returning. Additionally, it captures |
5fee5ec3 | 31 | the process' standard output and error streams and returns |
b4c522fa IB |
32 | the output of these as a string.) |
33 | $(LI | |
34 | $(LREF spawnShell), $(LREF pipeShell) and $(LREF executeShell) work like | |
5fee5ec3 | 35 | `spawnProcess`, `pipeProcess` and `execute`, respectively, |
b4c522fa IB |
36 | except that they take a single command string and run it through |
37 | the current user's default command interpreter. | |
5fee5ec3 | 38 | `executeShell` corresponds roughly to C's `system` function.) |
b4c522fa | 39 | $(LI |
5fee5ec3 | 40 | $(LREF kill) attempts to terminate a running process.) |
b4c522fa IB |
41 | ) |
42 | ||
5fee5ec3 | 43 | The following table compactly summarises the different process creation |
b4c522fa IB |
44 | functions and how they relate to each other: |
45 | $(BOOKTABLE, | |
46 | $(TR $(TH ) | |
47 | $(TH Runs program directly) | |
48 | $(TH Runs shell command)) | |
5fee5ec3 | 49 | $(TR $(TD Low-level process creation) |
b4c522fa IB |
50 | $(TD $(LREF spawnProcess)) |
51 | $(TD $(LREF spawnShell))) | |
52 | $(TR $(TD Automatic input/output redirection using pipes) | |
53 | $(TD $(LREF pipeProcess)) | |
54 | $(TD $(LREF pipeShell))) | |
55 | $(TR $(TD Execute and wait for completion, collect output) | |
56 | $(TD $(LREF execute)) | |
57 | $(TD $(LREF executeShell))) | |
58 | ) | |
59 | ||
60 | Other_functionality: | |
61 | $(UL | |
62 | $(LI | |
63 | $(LREF pipe) is used to create unidirectional pipes.) | |
64 | $(LI | |
5fee5ec3 | 65 | $(LREF environment) is an interface through which the current process' |
b4c522fa IB |
66 | environment variables can be read and manipulated.) |
67 | $(LI | |
68 | $(LREF escapeShellCommand) and $(LREF escapeShellFileName) are useful | |
69 | for constructing shell command lines in a portable way.) | |
70 | ) | |
71 | ||
72 | Authors: | |
73 | $(LINK2 https://github.com/kyllingstad, Lars Tandle Kyllingstad), | |
74 | $(LINK2 https://github.com/schveiguy, Steven Schveighoffer), | |
75 | $(HTTP thecybershadow.net, Vladimir Panteleev) | |
76 | Copyright: | |
77 | Copyright (c) 2013, the authors. All rights reserved. | |
78 | License: | |
79 | $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
80 | Source: | |
5fee5ec3 | 81 | $(PHOBOSSRC std/process.d) |
b4c522fa | 82 | Macros: |
5fee5ec3 IB |
83 | OBJECTREF=$(REF1 $0, object) |
84 | ||
85 | Note: | |
86 | Most of the functionality in this module is not available on iOS, tvOS | |
87 | and watchOS. The only functions available on those platforms are: | |
88 | $(LREF environment), $(LREF thisProcessID) and $(LREF thisThreadID). | |
b4c522fa IB |
89 | */ |
90 | module std.process; | |
91 | ||
5fee5ec3 IB |
92 | import core.thread : ThreadID; |
93 | ||
b4c522fa IB |
94 | version (Posix) |
95 | { | |
96 | import core.sys.posix.sys.wait; | |
97 | import core.sys.posix.unistd; | |
98 | } | |
99 | version (Windows) | |
100 | { | |
101 | import core.stdc.stdio; | |
5fee5ec3 IB |
102 | import core.sys.windows.winbase; |
103 | import core.sys.windows.winnt; | |
b4c522fa IB |
104 | import std.utf; |
105 | import std.windows.syserror; | |
106 | } | |
107 | ||
108 | import std.internal.cstring; | |
109 | import std.range.primitives; | |
110 | import std.stdio; | |
5fee5ec3 | 111 | import std.traits : isSomeChar; |
b4c522fa | 112 | |
5fee5ec3 IB |
113 | version (OSX) |
114 | version = Darwin; | |
115 | else version (iOS) | |
116 | { | |
117 | version = Darwin; | |
118 | version = iOSDerived; | |
119 | } | |
120 | else version (TVOS) | |
121 | { | |
122 | version = Darwin; | |
123 | version = iOSDerived; | |
124 | } | |
125 | else version (WatchOS) | |
126 | { | |
127 | version = Darwin; | |
128 | version = iOSDerived; | |
129 | } | |
b4c522fa IB |
130 | |
131 | // When the DMC runtime is used, we have to use some custom functions | |
132 | // to convert between Windows file handles and FILE*s. | |
133 | version (Win32) version (CRuntime_DigitalMars) version = DMC_RUNTIME; | |
134 | ||
135 | ||
136 | // Some of the following should be moved to druntime. | |
137 | private | |
138 | { | |
139 | // Microsoft Visual C Runtime (MSVCRT) declarations. | |
140 | version (Windows) | |
141 | { | |
142 | version (DMC_RUNTIME) { } else | |
143 | { | |
144 | import core.stdc.stdint; | |
145 | enum | |
146 | { | |
147 | STDIN_FILENO = 0, | |
148 | STDOUT_FILENO = 1, | |
149 | STDERR_FILENO = 2, | |
150 | } | |
151 | } | |
152 | } | |
153 | ||
154 | // POSIX API declarations. | |
155 | version (Posix) | |
156 | { | |
5fee5ec3 | 157 | version (Darwin) |
b4c522fa IB |
158 | { |
159 | extern(C) char*** _NSGetEnviron() nothrow; | |
160 | const(char**) getEnvironPtr() @trusted | |
161 | { | |
162 | return *_NSGetEnviron; | |
163 | } | |
164 | } | |
165 | else | |
166 | { | |
167 | // Made available by the C runtime: | |
168 | extern(C) extern __gshared const char** environ; | |
169 | const(char**) getEnvironPtr() @trusted | |
170 | { | |
171 | return environ; | |
172 | } | |
173 | } | |
174 | ||
175 | @system unittest | |
176 | { | |
5fee5ec3 | 177 | import core.thread : Thread; |
b4c522fa IB |
178 | new Thread({assert(getEnvironPtr !is null);}).start(); |
179 | } | |
180 | } | |
181 | } // private | |
182 | ||
b4c522fa | 183 | // ============================================================================= |
5fee5ec3 | 184 | // Environment variable manipulation. |
b4c522fa IB |
185 | // ============================================================================= |
186 | ||
b4c522fa | 187 | /** |
5fee5ec3 IB |
188 | Manipulates _environment variables using an associative-array-like |
189 | interface. | |
b4c522fa | 190 | |
5fee5ec3 IB |
191 | This class contains only static methods, and cannot be instantiated. |
192 | See below for examples of use. | |
b4c522fa | 193 | */ |
5fee5ec3 | 194 | abstract final class environment |
b4c522fa | 195 | { |
5fee5ec3 IB |
196 | static import core.sys.posix.stdlib; |
197 | import core.stdc.errno : errno, EINVAL; | |
b4c522fa | 198 | |
5fee5ec3 IB |
199 | static: |
200 | /** | |
201 | Retrieves the value of the environment variable with the given `name`. | |
202 | --- | |
203 | auto path = environment["PATH"]; | |
204 | --- | |
b4c522fa | 205 | |
5fee5ec3 IB |
206 | Throws: |
207 | $(OBJECTREF Exception) if the environment variable does not exist, | |
208 | or $(REF UTFException, std,utf) if the variable contains invalid UTF-16 | |
209 | characters (Windows only). | |
b4c522fa | 210 | |
5fee5ec3 IB |
211 | See_also: |
212 | $(LREF environment.get), which doesn't throw on failure. | |
213 | */ | |
214 | string opIndex(scope const(char)[] name) @safe | |
b4c522fa | 215 | { |
5fee5ec3 IB |
216 | import std.exception : enforce; |
217 | return get(name, null).enforce("Environment variable not found: "~name); | |
b4c522fa | 218 | } |
5fee5ec3 IB |
219 | |
220 | /** | |
221 | Retrieves the value of the environment variable with the given `name`, | |
222 | or a default value if the variable doesn't exist. | |
223 | ||
224 | Unlike $(LREF environment.opIndex), this function never throws on Posix. | |
225 | --- | |
226 | auto sh = environment.get("SHELL", "/bin/sh"); | |
227 | --- | |
228 | This function is also useful in checking for the existence of an | |
229 | environment variable. | |
230 | --- | |
231 | auto myVar = environment.get("MYVAR"); | |
232 | if (myVar is null) | |
b4c522fa | 233 | { |
5fee5ec3 IB |
234 | // Environment variable doesn't exist. |
235 | // Note that we have to use 'is' for the comparison, since | |
236 | // myVar == null is also true if the variable exists but is | |
237 | // empty. | |
b4c522fa | 238 | } |
5fee5ec3 IB |
239 | --- |
240 | Params: | |
241 | name = name of the environment variable to retrieve | |
242 | defaultValue = default value to return if the environment variable doesn't exist. | |
b4c522fa | 243 | |
5fee5ec3 IB |
244 | Returns: |
245 | the value of the environment variable if found, otherwise | |
246 | `null` if the environment doesn't exist. | |
b4c522fa | 247 | |
5fee5ec3 IB |
248 | Throws: |
249 | $(REF UTFException, std,utf) if the variable contains invalid UTF-16 | |
250 | characters (Windows only). | |
251 | */ | |
252 | string get(scope const(char)[] name, string defaultValue = null) @safe | |
b4c522fa | 253 | { |
5fee5ec3 IB |
254 | string value; |
255 | getImpl(name, (result) { value = result ? cachedToString(result) : defaultValue; }); | |
256 | return value; | |
b4c522fa IB |
257 | } |
258 | ||
5fee5ec3 IB |
259 | /** |
260 | Assigns the given `value` to the environment variable with the given | |
261 | `name`. | |
262 | If `value` is null the variable is removed from environment. | |
b4c522fa | 263 | |
5fee5ec3 IB |
264 | If the variable does not exist, it will be created. If it already exists, |
265 | it will be overwritten. | |
266 | --- | |
267 | environment["foo"] = "bar"; | |
268 | --- | |
b4c522fa | 269 | |
5fee5ec3 IB |
270 | Throws: |
271 | $(OBJECTREF Exception) if the environment variable could not be added | |
272 | (e.g. if the name is invalid). | |
273 | ||
274 | Note: | |
275 | On some platforms, modifying environment variables may not be allowed in | |
276 | multi-threaded programs. See e.g. | |
277 | $(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc). | |
b4c522fa | 278 | */ |
9c7d5e88 | 279 | inout(char)[] opIndexAssign(return scope inout char[] value, scope const(char)[] name) @trusted |
b4c522fa | 280 | { |
5fee5ec3 IB |
281 | version (Posix) |
282 | { | |
283 | import std.exception : enforce, errnoEnforce; | |
284 | if (value is null) | |
285 | { | |
286 | remove(name); | |
287 | return value; | |
288 | } | |
289 | if (core.sys.posix.stdlib.setenv(name.tempCString(), value.tempCString(), 1) != -1) | |
290 | { | |
291 | return value; | |
292 | } | |
293 | // The default errno error message is very uninformative | |
294 | // in the most common case, so we handle it manually. | |
295 | enforce(errno != EINVAL, | |
296 | "Invalid environment variable name: '"~name~"'"); | |
297 | errnoEnforce(false, | |
298 | "Failed to add environment variable"); | |
299 | assert(0); | |
300 | } | |
301 | else version (Windows) | |
302 | { | |
303 | import std.exception : enforce; | |
304 | enforce( | |
305 | SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW()), | |
306 | sysErrorString(GetLastError()) | |
307 | ); | |
308 | return value; | |
309 | } | |
310 | else static assert(0); | |
b4c522fa | 311 | } |
b4c522fa | 312 | |
5fee5ec3 IB |
313 | /** |
314 | Removes the environment variable with the given `name`. | |
b4c522fa | 315 | |
5fee5ec3 IB |
316 | If the variable isn't in the environment, this function returns |
317 | successfully without doing anything. | |
318 | ||
319 | Note: | |
320 | On some platforms, modifying environment variables may not be allowed in | |
321 | multi-threaded programs. See e.g. | |
322 | $(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc). | |
323 | */ | |
324 | void remove(scope const(char)[] name) @trusted nothrow @nogc | |
b4c522fa | 325 | { |
5fee5ec3 IB |
326 | version (Windows) SetEnvironmentVariableW(name.tempCStringW(), null); |
327 | else version (Posix) core.sys.posix.stdlib.unsetenv(name.tempCString()); | |
328 | else static assert(0); | |
b4c522fa IB |
329 | } |
330 | ||
5fee5ec3 IB |
331 | /** |
332 | Identify whether a variable is defined in the environment. | |
333 | ||
334 | Because it doesn't return the value, this function is cheaper than `get`. | |
335 | However, if you do need the value as well, you should just check the | |
336 | return of `get` for `null` instead of using this function first. | |
337 | ||
338 | Example: | |
339 | ------------- | |
340 | // good usage | |
341 | if ("MY_ENV_FLAG" in environment) | |
342 | doSomething(); | |
343 | ||
344 | // bad usage | |
345 | if ("MY_ENV_VAR" in environment) | |
346 | doSomething(environment["MY_ENV_VAR"]); | |
347 | ||
348 | // do this instead | |
349 | if (auto var = environment.get("MY_ENV_VAR")) | |
350 | doSomething(var); | |
351 | ------------- | |
352 | */ | |
353 | bool opBinaryRight(string op : "in")(scope const(char)[] name) @trusted | |
b4c522fa | 354 | { |
5fee5ec3 IB |
355 | version (Posix) |
356 | return core.sys.posix.stdlib.getenv(name.tempCString()) !is null; | |
357 | else version (Windows) | |
358 | { | |
359 | SetLastError(NO_ERROR); | |
360 | if (GetEnvironmentVariableW(name.tempCStringW, null, 0) > 0) | |
361 | return true; | |
362 | immutable err = GetLastError(); | |
363 | if (err == NO_ERROR) | |
364 | return true; // zero-length environment variable on Wine / XP | |
365 | if (err == ERROR_ENVVAR_NOT_FOUND) | |
366 | return false; | |
367 | // Some other Windows error, throw. | |
368 | throw new WindowsException(err); | |
369 | } | |
370 | else static assert(0); | |
b4c522fa IB |
371 | } |
372 | ||
5fee5ec3 IB |
373 | /** |
374 | Copies all environment variables into an associative array. | |
375 | ||
376 | Windows_specific: | |
377 | While Windows environment variable names are case insensitive, D's | |
378 | built-in associative arrays are not. This function will store all | |
379 | variable names in uppercase (e.g. `PATH`). | |
380 | ||
381 | Throws: | |
382 | $(OBJECTREF Exception) if the environment variables could not | |
383 | be retrieved (Windows only). | |
384 | */ | |
385 | string[string] toAA() @trusted | |
b4c522fa | 386 | { |
5fee5ec3 IB |
387 | import std.conv : to; |
388 | string[string] aa; | |
389 | version (Posix) | |
390 | { | |
391 | auto environ = getEnvironPtr; | |
392 | for (int i=0; environ[i] != null; ++i) | |
393 | { | |
394 | import std.string : indexOf; | |
b4c522fa | 395 | |
5fee5ec3 IB |
396 | immutable varDef = to!string(environ[i]); |
397 | immutable eq = indexOf(varDef, '='); | |
398 | assert(eq >= 0); | |
b4c522fa | 399 | |
5fee5ec3 IB |
400 | immutable name = varDef[0 .. eq]; |
401 | immutable value = varDef[eq+1 .. $]; | |
b4c522fa | 402 | |
5fee5ec3 IB |
403 | // In POSIX, environment variables may be defined more |
404 | // than once. This is a security issue, which we avoid | |
405 | // by checking whether the key already exists in the array. | |
406 | // For more info: | |
407 | // http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/environment-variables.html | |
408 | if (name !in aa) aa[name] = value; | |
409 | } | |
410 | } | |
411 | else version (Windows) | |
b4c522fa | 412 | { |
5fee5ec3 IB |
413 | import std.exception : enforce; |
414 | import std.uni : toUpper; | |
415 | auto envBlock = GetEnvironmentStringsW(); | |
416 | enforce(envBlock, "Failed to retrieve environment variables."); | |
417 | scope(exit) FreeEnvironmentStringsW(envBlock); | |
418 | ||
419 | for (int i=0; envBlock[i] != '\0'; ++i) | |
b4c522fa | 420 | { |
5fee5ec3 IB |
421 | auto start = i; |
422 | while (envBlock[i] != '=') ++i; | |
423 | immutable name = toUTF8(toUpper(envBlock[start .. i])); | |
424 | ||
425 | start = i+1; | |
426 | while (envBlock[i] != '\0') ++i; | |
427 | ||
428 | // Ignore variables with empty names. These are used internally | |
429 | // by Windows to keep track of each drive's individual current | |
430 | // directory. | |
431 | if (!name.length) | |
432 | continue; | |
433 | ||
434 | // Just like in POSIX systems, environment variables may be | |
435 | // defined more than once in an environment block on Windows, | |
436 | // and it is just as much of a security issue there. Moreso, | |
437 | // in fact, due to the case insensensitivity of variable names, | |
438 | // which is not handled correctly by all programs. | |
439 | auto val = toUTF8(envBlock[start .. i]); | |
440 | if (name !in aa) aa[name] = val is null ? "" : val; | |
b4c522fa | 441 | } |
b4c522fa | 442 | } |
5fee5ec3 IB |
443 | else static assert(0); |
444 | return aa; | |
445 | } | |
b4c522fa | 446 | |
5fee5ec3 IB |
447 | private: |
448 | version (Windows) alias OSChar = WCHAR; | |
449 | else version (Posix) alias OSChar = char; | |
450 | ||
451 | // Retrieves the environment variable. Calls `sink` with a | |
452 | // temporary buffer of OS characters, or `null` if the variable | |
453 | // doesn't exist. | |
454 | void getImpl(scope const(char)[] name, scope void delegate(const(OSChar)[]) @safe sink) @trusted | |
455 | { | |
456 | version (Windows) | |
b4c522fa | 457 | { |
5fee5ec3 IB |
458 | // first we ask windows how long the environment variable is, |
459 | // then we try to read it in to a buffer of that length. Lots | |
460 | // of error conditions because the windows API is nasty. | |
b4c522fa | 461 | |
5fee5ec3 IB |
462 | import std.conv : to; |
463 | const namezTmp = name.tempCStringW(); | |
464 | WCHAR[] buf; | |
b4c522fa | 465 | |
5fee5ec3 IB |
466 | // clear error because GetEnvironmentVariable only says it sets it |
467 | // if the environment variable is missing, not on other errors. | |
468 | SetLastError(NO_ERROR); | |
469 | // len includes terminating null | |
470 | immutable len = GetEnvironmentVariableW(namezTmp, null, 0); | |
471 | if (len == 0) | |
b4c522fa | 472 | { |
5fee5ec3 IB |
473 | immutable err = GetLastError(); |
474 | if (err == ERROR_ENVVAR_NOT_FOUND) | |
475 | return sink(null); | |
476 | if (err != NO_ERROR) // Some other Windows error, throw. | |
477 | throw new WindowsException(err); | |
478 | } | |
479 | if (len <= 1) | |
480 | return sink(""); | |
481 | buf.length = len; | |
b4c522fa | 482 | |
5fee5ec3 IB |
483 | while (true) |
484 | { | |
485 | // lenRead is either the number of bytes read w/o null - if buf was long enough - or | |
486 | // the number of bytes necessary *including* null if buf wasn't long enough | |
487 | immutable lenRead = GetEnvironmentVariableW(namezTmp, buf.ptr, to!DWORD(buf.length)); | |
488 | if (lenRead == 0) | |
b4c522fa | 489 | { |
5fee5ec3 IB |
490 | immutable err = GetLastError(); |
491 | if (err == NO_ERROR) // sucessfully read a 0-length variable | |
492 | return sink(""); | |
493 | if (err == ERROR_ENVVAR_NOT_FOUND) // variable didn't exist | |
494 | return sink(null); | |
495 | // some other windows error | |
496 | throw new WindowsException(err); | |
b4c522fa | 497 | } |
5fee5ec3 IB |
498 | assert(lenRead != buf.length, "impossible according to msft docs"); |
499 | if (lenRead < buf.length) // the buffer was long enough | |
500 | return sink(buf[0 .. lenRead]); | |
501 | // resize and go around again, because the environment variable grew | |
502 | buf.length = lenRead; | |
b4c522fa IB |
503 | } |
504 | } | |
5fee5ec3 | 505 | else version (Posix) |
b4c522fa | 506 | { |
5fee5ec3 | 507 | import core.stdc.string : strlen; |
b4c522fa | 508 | |
5fee5ec3 IB |
509 | const vz = core.sys.posix.stdlib.getenv(name.tempCString()); |
510 | if (vz == null) return sink(null); | |
511 | return sink(vz[0 .. strlen(vz)]); | |
512 | } | |
513 | else static assert(0); | |
b4c522fa | 514 | } |
b4c522fa | 515 | |
5fee5ec3 IB |
516 | string cachedToString(C)(scope const(C)[] v) @safe |
517 | { | |
518 | import std.algorithm.comparison : equal; | |
b4c522fa | 519 | |
5fee5ec3 IB |
520 | // Cache the last call's result. |
521 | static string lastResult; | |
522 | if (v.empty) | |
b4c522fa | 523 | { |
5fee5ec3 IB |
524 | // Return non-null array for blank result to distinguish from |
525 | // not-present result. | |
526 | lastResult = ""; | |
b4c522fa | 527 | } |
5fee5ec3 | 528 | else if (!v.equal(lastResult)) |
b4c522fa | 529 | { |
5fee5ec3 IB |
530 | import std.conv : to; |
531 | lastResult = v.to!string; | |
b4c522fa | 532 | } |
5fee5ec3 | 533 | return lastResult; |
b4c522fa IB |
534 | } |
535 | } | |
536 | ||
5fee5ec3 | 537 | @safe unittest |
b4c522fa | 538 | { |
5fee5ec3 IB |
539 | import std.exception : assertThrown; |
540 | // New variable | |
541 | environment["std_process"] = "foo"; | |
542 | assert(environment["std_process"] == "foo"); | |
543 | assert("std_process" in environment); | |
b4c522fa | 544 | |
5fee5ec3 IB |
545 | // Set variable again (also tests length 1 case) |
546 | environment["std_process"] = "b"; | |
547 | assert(environment["std_process"] == "b"); | |
548 | assert("std_process" in environment); | |
b4c522fa | 549 | |
5fee5ec3 IB |
550 | // Remove variable |
551 | environment.remove("std_process"); | |
552 | assert("std_process" !in environment); | |
b4c522fa | 553 | |
5fee5ec3 IB |
554 | // Remove again, should succeed |
555 | environment.remove("std_process"); | |
556 | assert("std_process" !in environment); | |
b4c522fa | 557 | |
5fee5ec3 IB |
558 | // Throw on not found. |
559 | assertThrown(environment["std_process"]); | |
b4c522fa | 560 | |
5fee5ec3 IB |
561 | // get() without default value |
562 | assert(environment.get("std_process") is null); | |
b4c522fa | 563 | |
5fee5ec3 IB |
564 | // get() with default value |
565 | assert(environment.get("std_process", "baz") == "baz"); | |
b4c522fa | 566 | |
5fee5ec3 IB |
567 | // get() on an empty (but present) value |
568 | environment["std_process"] = ""; | |
569 | auto res = environment.get("std_process"); | |
570 | assert(res !is null); | |
571 | assert(res == ""); | |
572 | assert("std_process" in environment); | |
b4c522fa | 573 | |
5fee5ec3 IB |
574 | // Important to do the following round-trip after the previous test |
575 | // because it tests toAA with an empty var | |
b4c522fa | 576 | |
5fee5ec3 IB |
577 | // Convert to associative array |
578 | auto aa = environment.toAA(); | |
579 | assert(aa.length > 0); | |
580 | foreach (n, v; aa) | |
b4c522fa | 581 | { |
5fee5ec3 IB |
582 | // Wine has some bugs related to environment variables: |
583 | // - Wine allows the existence of an env. variable with the name | |
584 | // "\0", but GetEnvironmentVariable refuses to retrieve it. | |
585 | // As of 2.067 we filter these out anyway (see comment in toAA). | |
586 | ||
587 | assert(v == environment[n]); | |
b4c522fa | 588 | } |
b4c522fa | 589 | |
5fee5ec3 IB |
590 | // ... and back again. |
591 | foreach (n, v; aa) | |
592 | environment[n] = v; | |
b4c522fa | 593 | |
5fee5ec3 IB |
594 | // Complete the roundtrip |
595 | auto aa2 = environment.toAA(); | |
596 | import std.conv : text; | |
597 | assert(aa == aa2, text(aa, " != ", aa2)); | |
598 | assert("std_process" in environment); | |
b4c522fa | 599 | |
5fee5ec3 IB |
600 | // Setting null must have the same effect as remove |
601 | environment["std_process"] = null; | |
602 | assert("std_process" !in environment); | |
b4c522fa IB |
603 | } |
604 | ||
5fee5ec3 IB |
605 | // ============================================================================= |
606 | // Functions and classes for process management. | |
607 | // ============================================================================= | |
b4c522fa | 608 | |
5fee5ec3 IB |
609 | /** |
610 | * Returns the process ID of the current process, | |
611 | * which is guaranteed to be unique on the system. | |
612 | * | |
613 | * Example: | |
614 | * --- | |
615 | * writefln("Current process ID: %d", thisProcessID); | |
616 | * --- | |
617 | */ | |
618 | @property int thisProcessID() @trusted nothrow //TODO: @safe | |
619 | { | |
620 | version (Windows) return GetCurrentProcessId(); | |
621 | else version (Posix) return core.sys.posix.unistd.getpid(); | |
b4c522fa IB |
622 | } |
623 | ||
624 | ||
5fee5ec3 IB |
625 | /** |
626 | * Returns the process ID of the current thread, | |
627 | * which is guaranteed to be unique within the current process. | |
628 | * | |
629 | * Returns: | |
630 | * A $(REF ThreadID, core,thread) value for the calling thread. | |
631 | * | |
632 | * Example: | |
633 | * --- | |
634 | * writefln("Current thread ID: %s", thisThreadID); | |
635 | * --- | |
636 | */ | |
637 | @property ThreadID thisThreadID() @trusted nothrow //TODO: @safe | |
b4c522fa | 638 | { |
5fee5ec3 IB |
639 | version (Windows) |
640 | return GetCurrentThreadId(); | |
641 | else | |
642 | version (Posix) | |
b4c522fa | 643 | { |
5fee5ec3 IB |
644 | import core.sys.posix.pthread : pthread_self; |
645 | return pthread_self(); | |
b4c522fa | 646 | } |
b4c522fa IB |
647 | } |
648 | ||
b4c522fa | 649 | |
5fee5ec3 | 650 | @system unittest |
b4c522fa | 651 | { |
5fee5ec3 IB |
652 | int pidA, pidB; |
653 | ThreadID tidA, tidB; | |
654 | pidA = thisProcessID; | |
655 | tidA = thisThreadID; | |
b4c522fa | 656 | |
5fee5ec3 IB |
657 | import core.thread; |
658 | auto t = new Thread({ | |
659 | pidB = thisProcessID; | |
660 | tidB = thisThreadID; | |
661 | }); | |
662 | t.start(); | |
663 | t.join(); | |
b4c522fa | 664 | |
5fee5ec3 IB |
665 | assert(pidA == pidB); |
666 | assert(tidA != tidB); | |
b4c522fa IB |
667 | } |
668 | ||
b4c522fa | 669 | |
5fee5ec3 | 670 | package(std) string uniqueTempPath() @safe |
b4c522fa | 671 | { |
5fee5ec3 IB |
672 | import std.file : tempDir; |
673 | import std.path : buildPath; | |
674 | import std.uuid : randomUUID; | |
675 | // Path should contain spaces to test escaping whitespace | |
676 | return buildPath(tempDir(), "std.process temporary file " ~ | |
677 | randomUUID().toString()); | |
b4c522fa IB |
678 | } |
679 | ||
b4c522fa | 680 | |
5fee5ec3 IB |
681 | version (iOSDerived) {} |
682 | else: | |
b4c522fa | 683 | |
5fee5ec3 IB |
684 | /** |
685 | Spawns a new process, optionally assigning it an arbitrary set of standard | |
686 | input, output, and error streams. | |
b4c522fa | 687 | |
5fee5ec3 IB |
688 | The function returns immediately, leaving the child process to execute |
689 | in parallel with its parent. It is recommended to always call $(LREF wait) | |
690 | on the returned $(LREF Pid) unless the process was spawned with | |
691 | `Config.detached` flag, as detailed in the documentation for `wait`. | |
b4c522fa | 692 | |
5fee5ec3 IB |
693 | Command_line: |
694 | There are four overloads of this function. The first two take an array | |
695 | of strings, `args`, which should contain the program name as the | |
696 | zeroth element and any command-line arguments in subsequent elements. | |
697 | The third and fourth versions are included for convenience, and may be | |
698 | used when there are no command-line arguments. They take a single string, | |
699 | `program`, which specifies the program name. | |
b4c522fa | 700 | |
5fee5ec3 IB |
701 | Unless a directory is specified in `args[0]` or `program`, |
702 | `spawnProcess` will search for the program in a platform-dependent | |
703 | manner. On POSIX systems, it will look for the executable in the | |
704 | directories listed in the PATH environment variable, in the order | |
705 | they are listed. On Windows, it will search for the executable in | |
706 | the following sequence: | |
707 | $(OL | |
708 | $(LI The directory from which the application loaded.) | |
709 | $(LI The current directory for the parent process.) | |
710 | $(LI The 32-bit Windows system directory.) | |
711 | $(LI The 16-bit Windows system directory.) | |
712 | $(LI The Windows directory.) | |
713 | $(LI The directories listed in the PATH environment variable.) | |
714 | ) | |
715 | --- | |
716 | // Run an executable called "prog" located in the current working | |
717 | // directory: | |
718 | auto pid = spawnProcess("./prog"); | |
719 | scope(exit) wait(pid); | |
720 | // We can do something else while the program runs. The scope guard | |
721 | // ensures that the process is waited for at the end of the scope. | |
722 | ... | |
b4c522fa | 723 | |
5fee5ec3 IB |
724 | // Run DMD on the file "myprog.d", specifying a few compiler switches: |
725 | auto dmdPid = spawnProcess(["dmd", "-O", "-release", "-inline", "myprog.d" ]); | |
726 | if (wait(dmdPid) != 0) | |
727 | writeln("Compilation failed!"); | |
728 | --- | |
b4c522fa | 729 | |
5fee5ec3 IB |
730 | Environment_variables: |
731 | By default, the child process inherits the environment of the parent | |
732 | process, along with any additional variables specified in the `env` | |
733 | parameter. If the same variable exists in both the parent's environment | |
734 | and in `env`, the latter takes precedence. | |
b4c522fa | 735 | |
5fee5ec3 IB |
736 | If the $(LREF Config.newEnv) flag is set in `config`, the child |
737 | process will $(I not) inherit the parent's environment. Its entire | |
738 | environment will then be determined by `env`. | |
739 | --- | |
740 | wait(spawnProcess("myapp", ["foo" : "bar"], Config.newEnv)); | |
741 | --- | |
b4c522fa | 742 | |
5fee5ec3 IB |
743 | Standard_streams: |
744 | The optional arguments `stdin`, `stdout` and `stderr` may | |
745 | be used to assign arbitrary $(REF File, std,stdio) objects as the standard | |
746 | input, output and error streams, respectively, of the child process. The | |
747 | former must be opened for reading, while the latter two must be opened for | |
748 | writing. The default is for the child process to inherit the standard | |
749 | streams of its parent. | |
750 | --- | |
751 | // Run DMD on the file myprog.d, logging any error messages to a | |
752 | // file named errors.log. | |
753 | auto logFile = File("errors.log", "w"); | |
754 | auto pid = spawnProcess(["dmd", "myprog.d"], | |
755 | std.stdio.stdin, | |
756 | std.stdio.stdout, | |
757 | logFile); | |
758 | if (wait(pid) != 0) | |
759 | writeln("Compilation failed. See errors.log for details."); | |
760 | --- | |
b4c522fa | 761 | |
5fee5ec3 IB |
762 | Note that if you pass a `File` object that is $(I not) |
763 | one of the standard input/output/error streams of the parent process, | |
764 | that stream will by default be $(I closed) in the parent process when | |
765 | this function returns. See the $(LREF Config) documentation below for | |
766 | information about how to disable this behaviour. | |
b4c522fa | 767 | |
5fee5ec3 IB |
768 | Beware of buffering issues when passing `File` objects to |
769 | `spawnProcess`. The child process will inherit the low-level raw | |
770 | read/write offset associated with the underlying file descriptor, but | |
771 | it will not be aware of any buffered data. In cases where this matters | |
772 | (e.g. when a file should be aligned before being passed on to the | |
773 | child process), it may be a good idea to use unbuffered streams, or at | |
774 | least ensure all relevant buffers are flushed. | |
b4c522fa | 775 | |
5fee5ec3 IB |
776 | Params: |
777 | args = An array which contains the program name as the zeroth element | |
778 | and any command-line arguments in the following elements. | |
779 | stdin = The standard input stream of the child process. | |
780 | This can be any $(REF File, std,stdio) that is opened for reading. | |
781 | By default the child process inherits the parent's input | |
782 | stream. | |
783 | stdout = The standard output stream of the child process. | |
784 | This can be any $(REF File, std,stdio) that is opened for writing. | |
785 | By default the child process inherits the parent's output stream. | |
786 | stderr = The standard error stream of the child process. | |
787 | This can be any $(REF File, std,stdio) that is opened for writing. | |
788 | By default the child process inherits the parent's error stream. | |
789 | env = Additional environment variables for the child process. | |
790 | config = Flags that control process creation. See $(LREF Config) | |
791 | for an overview of available flags. | |
792 | workDir = The working directory for the new process. | |
793 | By default the child process inherits the parent's working | |
794 | directory. | |
b4c522fa | 795 | |
5fee5ec3 IB |
796 | Returns: |
797 | A $(LREF Pid) object that corresponds to the spawned process. | |
b4c522fa | 798 | |
5fee5ec3 IB |
799 | Throws: |
800 | $(LREF ProcessException) on failure to start the process.$(BR) | |
801 | $(REF StdioException, std,stdio) on failure to pass one of the streams | |
802 | to the child process (Windows only).$(BR) | |
803 | $(REF RangeError, core,exception) if `args` is empty. | |
804 | */ | |
805 | Pid spawnProcess(scope const(char[])[] args, | |
806 | File stdin = std.stdio.stdin, | |
807 | File stdout = std.stdio.stdout, | |
808 | File stderr = std.stdio.stderr, | |
809 | const string[string] env = null, | |
810 | Config config = Config.none, | |
811 | scope const char[] workDir = null) | |
812 | @safe | |
813 | { | |
814 | version (Windows) | |
b4c522fa | 815 | { |
5fee5ec3 IB |
816 | const commandLine = escapeShellArguments(args); |
817 | const program = args.length ? args[0] : null; | |
818 | return spawnProcessWin(commandLine, program, stdin, stdout, stderr, env, config, workDir); | |
b4c522fa | 819 | } |
5fee5ec3 | 820 | else version (Posix) |
b4c522fa | 821 | { |
5fee5ec3 | 822 | return spawnProcessPosix(args, stdin, stdout, stderr, env, config, workDir); |
b4c522fa | 823 | } |
5fee5ec3 IB |
824 | else |
825 | static assert(0); | |
b4c522fa IB |
826 | } |
827 | ||
5fee5ec3 IB |
828 | /// ditto |
829 | Pid spawnProcess(scope const(char[])[] args, | |
830 | const string[string] env, | |
831 | Config config = Config.none, | |
832 | scope const(char)[] workDir = null) | |
833 | @trusted // TODO: Should be @safe | |
b4c522fa | 834 | { |
5fee5ec3 IB |
835 | return spawnProcess(args, |
836 | std.stdio.stdin, | |
837 | std.stdio.stdout, | |
838 | std.stdio.stderr, | |
839 | env, | |
840 | config, | |
841 | workDir); | |
b4c522fa IB |
842 | } |
843 | ||
5fee5ec3 IB |
844 | /// ditto |
845 | Pid spawnProcess(scope const(char)[] program, | |
846 | File stdin = std.stdio.stdin, | |
847 | File stdout = std.stdio.stdout, | |
848 | File stderr = std.stdio.stderr, | |
849 | const string[string] env = null, | |
850 | Config config = Config.none, | |
851 | scope const(char)[] workDir = null) | |
852 | @trusted | |
b4c522fa | 853 | { |
5fee5ec3 IB |
854 | return spawnProcess((&program)[0 .. 1], |
855 | stdin, stdout, stderr, env, config, workDir); | |
b4c522fa IB |
856 | } |
857 | ||
5fee5ec3 IB |
858 | /// ditto |
859 | Pid spawnProcess(scope const(char)[] program, | |
860 | const string[string] env, | |
861 | Config config = Config.none, | |
862 | scope const(char)[] workDir = null) | |
863 | @trusted | |
b4c522fa | 864 | { |
5fee5ec3 | 865 | return spawnProcess((&program)[0 .. 1], env, config, workDir); |
b4c522fa IB |
866 | } |
867 | ||
5fee5ec3 | 868 | version (Posix) private enum InternalError : ubyte |
b4c522fa | 869 | { |
5fee5ec3 IB |
870 | noerror, |
871 | exec, | |
872 | chdir, | |
873 | getrlimit, | |
874 | doubleFork, | |
875 | malloc, | |
876 | preExec, | |
b4c522fa IB |
877 | } |
878 | ||
5fee5ec3 IB |
879 | /* |
880 | Implementation of spawnProcess() for POSIX. | |
881 | ||
882 | envz should be a zero-terminated array of zero-terminated strings | |
883 | on the form "var=value". | |
884 | */ | |
885 | version (Posix) | |
886 | private Pid spawnProcessPosix(scope const(char[])[] args, | |
887 | File stdin, | |
888 | File stdout, | |
889 | File stderr, | |
890 | scope const string[string] env, | |
891 | Config config, | |
892 | scope const(char)[] workDir) | |
893 | @trusted // TODO: Should be @safe | |
b4c522fa | 894 | { |
5fee5ec3 IB |
895 | import core.exception : RangeError; |
896 | import std.algorithm.searching : any; | |
897 | import std.conv : text; | |
898 | import std.path : isDirSeparator; | |
899 | import std.string : toStringz; | |
900 | ||
901 | if (args.empty) throw new RangeError(); | |
902 | const(char)[] name = args[0]; | |
903 | if (!any!isDirSeparator(name)) | |
b4c522fa | 904 | { |
5fee5ec3 IB |
905 | name = searchPathFor(name); |
906 | if (name is null) | |
907 | throw new ProcessException(text("Executable file not found: ", args[0])); | |
b4c522fa IB |
908 | } |
909 | ||
5fee5ec3 IB |
910 | // Convert program name and arguments to C-style strings. |
911 | auto argz = new const(char)*[args.length+1]; | |
912 | argz[0] = toStringz(name); | |
913 | foreach (i; 1 .. args.length) argz[i] = toStringz(args[i]); | |
914 | argz[$-1] = null; | |
b4c522fa | 915 | |
5fee5ec3 IB |
916 | // Prepare environment. |
917 | auto envz = createEnv(env, !(config.flags & Config.Flags.newEnv)); | |
b4c522fa | 918 | |
5fee5ec3 IB |
919 | // Open the working directory. |
920 | // We use open in the parent and fchdir in the child | |
921 | // so that most errors (directory doesn't exist, not a directory) | |
922 | // can be propagated as exceptions before forking. | |
923 | int workDirFD = -1; | |
924 | scope(exit) if (workDirFD >= 0) close(workDirFD); | |
925 | if (workDir.length) | |
926 | { | |
927 | import core.sys.posix.fcntl : open, O_RDONLY, stat_t, fstat, S_ISDIR; | |
928 | workDirFD = open(workDir.tempCString(), O_RDONLY); | |
929 | if (workDirFD < 0) | |
930 | throw ProcessException.newFromErrno("Failed to open working directory"); | |
931 | stat_t s; | |
932 | if (fstat(workDirFD, &s) < 0) | |
933 | throw ProcessException.newFromErrno("Failed to stat working directory"); | |
934 | if (!S_ISDIR(s.st_mode)) | |
935 | throw new ProcessException("Not a directory: " ~ cast(string) workDir); | |
b4c522fa IB |
936 | } |
937 | ||
5fee5ec3 | 938 | static int getFD(ref File f) { return core.stdc.stdio.fileno(f.getFP()); } |
b4c522fa | 939 | |
5fee5ec3 IB |
940 | // Get the file descriptors of the streams. |
941 | // These could potentially be invalid, but that is OK. If so, later calls | |
942 | // to dup2() and close() will just silently fail without causing any harm. | |
943 | auto stdinFD = getFD(stdin); | |
944 | auto stdoutFD = getFD(stdout); | |
945 | auto stderrFD = getFD(stderr); | |
b4c522fa | 946 | |
5fee5ec3 IB |
947 | // We don't have direct access to the errors that may happen in a child process. |
948 | // So we use this pipe to deliver them. | |
949 | int[2] forkPipe; | |
950 | if (core.sys.posix.unistd.pipe(forkPipe) == 0) | |
951 | setCLOEXEC(forkPipe[1], true); | |
952 | else | |
953 | throw ProcessException.newFromErrno("Could not create pipe to check startup of child"); | |
954 | scope(exit) close(forkPipe[0]); | |
b4c522fa | 955 | |
5fee5ec3 IB |
956 | /* |
957 | To create detached process, we use double fork technique | |
958 | but we don't have a direct access to the second fork pid from the caller side thus use a pipe. | |
959 | We also can't reuse forkPipe for that purpose | |
960 | because we can't predict the order in which pid and possible error will be written | |
961 | since the first and the second forks will run in parallel. | |
962 | */ | |
963 | int[2] pidPipe; | |
964 | if (config.flags & Config.Flags.detached) | |
965 | { | |
966 | if (core.sys.posix.unistd.pipe(pidPipe) != 0) | |
967 | throw ProcessException.newFromErrno("Could not create pipe to get process pid"); | |
968 | setCLOEXEC(pidPipe[1], true); | |
969 | } | |
970 | scope(exit) if (config.flags & Config.Flags.detached) close(pidPipe[0]); | |
b4c522fa | 971 | |
5fee5ec3 IB |
972 | static void abortOnError(int forkPipeOut, InternalError errorType, int error) nothrow |
973 | { | |
974 | core.sys.posix.unistd.write(forkPipeOut, &errorType, errorType.sizeof); | |
975 | core.sys.posix.unistd.write(forkPipeOut, &error, error.sizeof); | |
976 | close(forkPipeOut); | |
977 | core.sys.posix.unistd._exit(1); | |
978 | assert(0); | |
979 | } | |
b4c522fa | 980 | |
5fee5ec3 | 981 | void closePipeWriteEnds() |
b4c522fa | 982 | { |
5fee5ec3 IB |
983 | close(forkPipe[1]); |
984 | if (config.flags & Config.Flags.detached) | |
985 | close(pidPipe[1]); | |
b4c522fa | 986 | } |
5fee5ec3 IB |
987 | |
988 | auto id = core.sys.posix.unistd.fork(); | |
989 | if (id < 0) | |
b4c522fa | 990 | { |
5fee5ec3 IB |
991 | closePipeWriteEnds(); |
992 | throw ProcessException.newFromErrno("Failed to spawn new process"); | |
b4c522fa | 993 | } |
b4c522fa | 994 | |
5fee5ec3 IB |
995 | void forkChild() nothrow @nogc |
996 | { | |
997 | static import core.sys.posix.stdio; | |
b4c522fa | 998 | |
5fee5ec3 | 999 | // Child process |
b4c522fa | 1000 | |
5fee5ec3 IB |
1001 | // no need for the read end of pipe on child side |
1002 | if (config.flags & Config.Flags.detached) | |
1003 | close(pidPipe[0]); | |
1004 | close(forkPipe[0]); | |
1005 | immutable forkPipeOut = forkPipe[1]; | |
1006 | immutable pidPipeOut = pidPipe[1]; | |
b4c522fa | 1007 | |
5fee5ec3 IB |
1008 | // Set the working directory. |
1009 | if (workDirFD >= 0) | |
1010 | { | |
1011 | if (fchdir(workDirFD) < 0) | |
1012 | { | |
1013 | // Fail. It is dangerous to run a program | |
1014 | // in an unexpected working directory. | |
1015 | abortOnError(forkPipeOut, InternalError.chdir, .errno); | |
1016 | } | |
1017 | close(workDirFD); | |
1018 | } | |
b4c522fa | 1019 | |
5fee5ec3 IB |
1020 | void execProcess() |
1021 | { | |
1022 | // Redirect streams and close the old file descriptors. | |
1023 | // In the case that stderr is redirected to stdout, we need | |
1024 | // to backup the file descriptor since stdout may be redirected | |
1025 | // as well. | |
1026 | if (stderrFD == STDOUT_FILENO) stderrFD = dup(stderrFD); | |
1027 | dup2(stdinFD, STDIN_FILENO); | |
1028 | dup2(stdoutFD, STDOUT_FILENO); | |
1029 | dup2(stderrFD, STDERR_FILENO); | |
b4c522fa | 1030 | |
5fee5ec3 IB |
1031 | // Ensure that the standard streams aren't closed on execute, and |
1032 | // optionally close all other file descriptors. | |
1033 | setCLOEXEC(STDIN_FILENO, false); | |
1034 | setCLOEXEC(STDOUT_FILENO, false); | |
1035 | setCLOEXEC(STDERR_FILENO, false); | |
b4c522fa | 1036 | |
5fee5ec3 IB |
1037 | if (!(config.flags & Config.Flags.inheritFDs)) |
1038 | { | |
1039 | // NOTE: malloc() and getrlimit() are not on the POSIX async | |
1040 | // signal safe functions list, but practically this should | |
1041 | // not be a problem. Java VM and CPython also use malloc() | |
1042 | // in its own implementation via opendir(). | |
1043 | import core.stdc.stdlib : malloc; | |
1044 | import core.sys.posix.poll : pollfd, poll, POLLNVAL; | |
1045 | import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE; | |
b4c522fa | 1046 | |
5fee5ec3 IB |
1047 | // Get the maximum number of file descriptors that could be open. |
1048 | rlimit r; | |
1049 | if (getrlimit(RLIMIT_NOFILE, &r) != 0) | |
1050 | { | |
1051 | abortOnError(forkPipeOut, InternalError.getrlimit, .errno); | |
1052 | } | |
1053 | immutable maxDescriptors = cast(int) r.rlim_cur; | |
b4c522fa | 1054 | |
5fee5ec3 IB |
1055 | // The above, less stdin, stdout, and stderr |
1056 | immutable maxToClose = maxDescriptors - 3; | |
b4c522fa | 1057 | |
5fee5ec3 IB |
1058 | // Call poll() to see which ones are actually open: |
1059 | auto pfds = cast(pollfd*) malloc(pollfd.sizeof * maxToClose); | |
1060 | if (pfds is null) | |
b4c522fa | 1061 | { |
5fee5ec3 IB |
1062 | abortOnError(forkPipeOut, InternalError.malloc, .errno); |
1063 | } | |
1064 | foreach (i; 0 .. maxToClose) | |
1065 | { | |
1066 | pfds[i].fd = i + 3; | |
1067 | pfds[i].events = 0; | |
1068 | pfds[i].revents = 0; | |
1069 | } | |
1070 | if (poll(pfds, maxToClose, 0) >= 0) | |
1071 | { | |
1072 | foreach (i; 0 .. maxToClose) | |
1073 | { | |
1074 | // don't close pipe write end | |
1075 | if (pfds[i].fd == forkPipeOut) continue; | |
1076 | // POLLNVAL will be set if the file descriptor is invalid. | |
1077 | if (!(pfds[i].revents & POLLNVAL)) close(pfds[i].fd); | |
1078 | } | |
b4c522fa IB |
1079 | } |
1080 | else | |
1081 | { | |
5fee5ec3 IB |
1082 | // Fall back to closing everything. |
1083 | foreach (i; 3 .. maxDescriptors) | |
1084 | { | |
1085 | if (i == forkPipeOut) continue; | |
1086 | close(i); | |
1087 | } | |
b4c522fa IB |
1088 | } |
1089 | } | |
5fee5ec3 | 1090 | else // This is already done if we don't inherit descriptors. |
b4c522fa | 1091 | { |
5fee5ec3 IB |
1092 | // Close the old file descriptors, unless they are |
1093 | // either of the standard streams. | |
1094 | if (stdinFD > STDERR_FILENO) close(stdinFD); | |
1095 | if (stdoutFD > STDERR_FILENO) close(stdoutFD); | |
1096 | if (stderrFD > STDERR_FILENO) close(stderrFD); | |
b4c522fa | 1097 | } |
5fee5ec3 IB |
1098 | |
1099 | if (config.preExecFunction !is null) | |
b4c522fa | 1100 | { |
5fee5ec3 IB |
1101 | if (config.preExecFunction() != true) |
1102 | { | |
1103 | abortOnError(forkPipeOut, InternalError.preExec, .errno); | |
1104 | } | |
b4c522fa | 1105 | } |
b4c522fa | 1106 | |
5fee5ec3 IB |
1107 | // Execute program. |
1108 | core.sys.posix.unistd.execve(argz[0], argz.ptr, envz); | |
1109 | ||
1110 | // If execution fails, exit as quickly as possible. | |
1111 | abortOnError(forkPipeOut, InternalError.exec, .errno); | |
b4c522fa | 1112 | } |
5fee5ec3 IB |
1113 | |
1114 | if (config.flags & Config.Flags.detached) | |
b4c522fa | 1115 | { |
5fee5ec3 IB |
1116 | auto secondFork = core.sys.posix.unistd.fork(); |
1117 | if (secondFork == 0) | |
b4c522fa | 1118 | { |
5fee5ec3 IB |
1119 | close(pidPipeOut); |
1120 | execProcess(); | |
b4c522fa | 1121 | } |
5fee5ec3 | 1122 | else if (secondFork == -1) |
b4c522fa | 1123 | { |
5fee5ec3 IB |
1124 | auto secondForkErrno = .errno; |
1125 | close(pidPipeOut); | |
1126 | abortOnError(forkPipeOut, InternalError.doubleFork, secondForkErrno); | |
1127 | } | |
1128 | else | |
1129 | { | |
1130 | core.sys.posix.unistd.write(pidPipeOut, &secondFork, pid_t.sizeof); | |
1131 | close(pidPipeOut); | |
1132 | close(forkPipeOut); | |
1133 | _exit(0); | |
b4c522fa IB |
1134 | } |
1135 | } | |
5fee5ec3 IB |
1136 | else |
1137 | { | |
1138 | execProcess(); | |
1139 | } | |
b4c522fa IB |
1140 | } |
1141 | ||
5fee5ec3 | 1142 | if (id == 0) |
b4c522fa | 1143 | { |
5fee5ec3 IB |
1144 | forkChild(); |
1145 | assert(0); | |
b4c522fa IB |
1146 | } |
1147 | else | |
1148 | { | |
5fee5ec3 IB |
1149 | closePipeWriteEnds(); |
1150 | auto status = InternalError.noerror; | |
1151 | auto readExecResult = core.sys.posix.unistd.read(forkPipe[0], &status, status.sizeof); | |
1152 | // Save error number just in case if subsequent "waitpid" fails and overrides errno | |
1153 | immutable lastError = .errno; | |
1154 | ||
1155 | if (config.flags & Config.Flags.detached) | |
b4c522fa | 1156 | { |
5fee5ec3 IB |
1157 | // Forked child exits right after creating second fork. So it should be safe to wait here. |
1158 | import core.sys.posix.sys.wait : waitpid; | |
1159 | int waitResult; | |
1160 | waitpid(id, &waitResult, 0); | |
b4c522fa | 1161 | } |
b4c522fa | 1162 | |
5fee5ec3 IB |
1163 | if (readExecResult == -1) |
1164 | throw ProcessException.newFromErrno(lastError, "Could not read from pipe to get child status"); | |
b4c522fa | 1165 | |
5fee5ec3 IB |
1166 | bool owned = true; |
1167 | if (status != InternalError.noerror) | |
1168 | { | |
1169 | int error; | |
1170 | readExecResult = read(forkPipe[0], &error, error.sizeof); | |
1171 | string errorMsg; | |
1172 | final switch (status) | |
1173 | { | |
1174 | case InternalError.chdir: | |
1175 | errorMsg = "Failed to set working directory"; | |
1176 | break; | |
1177 | case InternalError.getrlimit: | |
1178 | errorMsg = "getrlimit failed"; | |
1179 | break; | |
1180 | case InternalError.exec: | |
1181 | errorMsg = "Failed to execute '" ~ cast(string) name ~ "'"; | |
1182 | break; | |
1183 | case InternalError.doubleFork: | |
1184 | // Can happen only when starting detached process | |
1185 | assert(config.flags & Config.Flags.detached); | |
1186 | errorMsg = "Failed to fork twice"; | |
1187 | break; | |
1188 | case InternalError.malloc: | |
1189 | errorMsg = "Failed to allocate memory"; | |
1190 | break; | |
1191 | case InternalError.preExec: | |
1192 | errorMsg = "Failed to execute preExecFunction"; | |
1193 | break; | |
1194 | case InternalError.noerror: | |
1195 | assert(false); | |
1196 | } | |
1197 | if (readExecResult == error.sizeof) | |
1198 | throw ProcessException.newFromErrno(error, errorMsg); | |
1199 | throw new ProcessException(errorMsg); | |
1200 | } | |
1201 | else if (config.flags & Config.Flags.detached) | |
1202 | { | |
1203 | owned = false; | |
1204 | if (read(pidPipe[0], &id, id.sizeof) != id.sizeof) | |
1205 | throw ProcessException.newFromErrno("Could not read from pipe to get detached process id"); | |
1206 | } | |
b4c522fa | 1207 | |
5fee5ec3 IB |
1208 | // Parent process: Close streams and return. |
1209 | if (!(config.flags & Config.Flags.retainStdin ) && stdinFD > STDERR_FILENO | |
1210 | && stdinFD != getFD(std.stdio.stdin )) | |
1211 | stdin.close(); | |
1212 | if (!(config.flags & Config.Flags.retainStdout) && stdoutFD > STDERR_FILENO | |
1213 | && stdoutFD != getFD(std.stdio.stdout)) | |
1214 | stdout.close(); | |
1215 | if (!(config.flags & Config.Flags.retainStderr) && stderrFD > STDERR_FILENO | |
1216 | && stderrFD != getFD(std.stdio.stderr)) | |
1217 | stderr.close(); | |
1218 | return new Pid(id, owned); | |
1219 | } | |
1220 | } | |
b4c522fa | 1221 | |
5fee5ec3 IB |
1222 | version (Posix) |
1223 | @system unittest | |
1224 | { | |
1225 | import std.concurrency : ownerTid, receiveTimeout, send, spawn; | |
1226 | import std.datetime : seconds; | |
1227 | ||
1228 | sigset_t ss; | |
1229 | sigemptyset(&ss); | |
1230 | sigaddset(&ss, SIGINT); | |
1231 | pthread_sigmask(SIG_BLOCK, &ss, null); | |
1232 | ||
1233 | Config config = { | |
1234 | preExecFunction: () @trusted @nogc nothrow { | |
1235 | // Reset signal handlers | |
1236 | sigset_t ss; | |
1237 | if (sigfillset(&ss) != 0) | |
1238 | { | |
1239 | return false; | |
1240 | } | |
1241 | if (sigprocmask(SIG_UNBLOCK, &ss, null) != 0) | |
1242 | { | |
1243 | return false; | |
1244 | } | |
1245 | return true; | |
1246 | }, | |
1247 | }; | |
1248 | ||
1249 | auto pid = spawnProcess(["sleep", "10000"], | |
1250 | std.stdio.stdin, | |
1251 | std.stdio.stdout, | |
1252 | std.stdio.stderr, | |
1253 | null, | |
1254 | config, | |
1255 | null); | |
1256 | scope(failure) | |
1257 | { | |
1258 | kill(pid, SIGKILL); | |
1259 | wait(pid); | |
1260 | } | |
b4c522fa | 1261 | |
5fee5ec3 IB |
1262 | // kill the spawned process with SIGINT |
1263 | // and send its return code | |
1264 | spawn((shared Pid pid) { | |
1265 | auto p = cast() pid; | |
1266 | kill(p, SIGINT); | |
1267 | auto code = wait(p); | |
1268 | assert(code < 0); | |
1269 | send(ownerTid, code); | |
1270 | }, cast(shared) pid); | |
1271 | ||
1272 | auto received = receiveTimeout(3.seconds, (int) {}); | |
1273 | assert(received); | |
1274 | } | |
b4c522fa | 1275 | |
5fee5ec3 IB |
1276 | /* |
1277 | Implementation of spawnProcess() for Windows. | |
b4c522fa | 1278 | |
5fee5ec3 IB |
1279 | commandLine must contain the entire command line, properly |
1280 | quoted/escaped as required by CreateProcessW(). | |
1281 | ||
1282 | envz must be a pointer to a block of UTF-16 characters on the form | |
1283 | "var1=value1\0var2=value2\0...varN=valueN\0\0". | |
b4c522fa | 1284 | */ |
5fee5ec3 IB |
1285 | version (Windows) |
1286 | private Pid spawnProcessWin(scope const(char)[] commandLine, | |
1287 | scope const(char)[] program, | |
1288 | File stdin, | |
1289 | File stdout, | |
1290 | File stderr, | |
1291 | scope const string[string] env, | |
1292 | Config config, | |
1293 | scope const(char)[] workDir) | |
1294 | @trusted | |
b4c522fa | 1295 | { |
5fee5ec3 IB |
1296 | import core.exception : RangeError; |
1297 | import std.conv : text; | |
b4c522fa | 1298 | |
5fee5ec3 | 1299 | if (commandLine.empty) throw new RangeError("Command line is empty"); |
b4c522fa | 1300 | |
5fee5ec3 IB |
1301 | // Prepare environment. |
1302 | auto envz = createEnv(env, !(config.flags & Config.Flags.newEnv)); | |
b4c522fa | 1303 | |
5fee5ec3 IB |
1304 | // Startup info for CreateProcessW(). |
1305 | STARTUPINFO_W startinfo; | |
1306 | startinfo.cb = startinfo.sizeof; | |
1307 | static int getFD(ref File f) { return f.isOpen ? f.fileno : -1; } | |
b4c522fa | 1308 | |
5fee5ec3 IB |
1309 | // Extract file descriptors and HANDLEs from the streams and make the |
1310 | // handles inheritable. | |
1311 | static void prepareStream(ref File file, DWORD stdHandle, string which, | |
1312 | out int fileDescriptor, out HANDLE handle) | |
1313 | { | |
1314 | enum _NO_CONSOLE_FILENO = cast(HANDLE)-2; | |
1315 | fileDescriptor = getFD(file); | |
1316 | handle = null; | |
1317 | if (fileDescriptor >= 0) | |
1318 | handle = file.windowsHandle; | |
1319 | // Windows GUI applications have a fd but not a valid Windows HANDLE. | |
1320 | if (handle is null || handle == INVALID_HANDLE_VALUE || handle == _NO_CONSOLE_FILENO) | |
1321 | handle = GetStdHandle(stdHandle); | |
b4c522fa | 1322 | |
5fee5ec3 IB |
1323 | DWORD dwFlags; |
1324 | if (GetHandleInformation(handle, &dwFlags)) | |
1325 | { | |
1326 | if (!(dwFlags & HANDLE_FLAG_INHERIT)) | |
1327 | { | |
1328 | if (!SetHandleInformation(handle, | |
1329 | HANDLE_FLAG_INHERIT, | |
1330 | HANDLE_FLAG_INHERIT)) | |
1331 | { | |
1332 | throw new StdioException( | |
1333 | "Failed to make "~which~" stream inheritable by child process (" | |
1334 | ~sysErrorString(GetLastError()) ~ ')', | |
1335 | 0); | |
1336 | } | |
1337 | } | |
1338 | } | |
1339 | } | |
1340 | int stdinFD = -1, stdoutFD = -1, stderrFD = -1; | |
1341 | prepareStream(stdin, STD_INPUT_HANDLE, "stdin" , stdinFD, startinfo.hStdInput ); | |
1342 | prepareStream(stdout, STD_OUTPUT_HANDLE, "stdout", stdoutFD, startinfo.hStdOutput); | |
1343 | prepareStream(stderr, STD_ERROR_HANDLE, "stderr", stderrFD, startinfo.hStdError ); | |
b4c522fa | 1344 | |
5fee5ec3 IB |
1345 | if ((startinfo.hStdInput != null && startinfo.hStdInput != INVALID_HANDLE_VALUE) |
1346 | || (startinfo.hStdOutput != null && startinfo.hStdOutput != INVALID_HANDLE_VALUE) | |
1347 | || (startinfo.hStdError != null && startinfo.hStdError != INVALID_HANDLE_VALUE)) | |
1348 | startinfo.dwFlags = STARTF_USESTDHANDLES; | |
b4c522fa | 1349 | |
5fee5ec3 IB |
1350 | // Create process. |
1351 | PROCESS_INFORMATION pi; | |
1352 | DWORD dwCreationFlags = | |
1353 | CREATE_UNICODE_ENVIRONMENT | | |
1354 | ((config.flags & Config.Flags.suppressConsole) ? CREATE_NO_WINDOW : 0); | |
1355 | // workaround until https://issues.dlang.org/show_bug.cgi?id=14696 is fixed | |
1356 | auto pworkDir = workDir.tempCStringW(); | |
1357 | if (!CreateProcessW(null, commandLine.tempCStringW().buffPtr, | |
1358 | null, null, true, dwCreationFlags, | |
1359 | envz, workDir.length ? pworkDir : null, &startinfo, &pi)) | |
1360 | throw ProcessException.newFromLastError("Failed to spawn process \"" ~ cast(string) program ~ '"'); | |
b4c522fa | 1361 | |
5fee5ec3 IB |
1362 | // figure out if we should close any of the streams |
1363 | if (!(config.flags & Config.Flags.retainStdin ) && stdinFD > STDERR_FILENO | |
1364 | && stdinFD != getFD(std.stdio.stdin )) | |
1365 | stdin.close(); | |
1366 | if (!(config.flags & Config.Flags.retainStdout) && stdoutFD > STDERR_FILENO | |
1367 | && stdoutFD != getFD(std.stdio.stdout)) | |
1368 | stdout.close(); | |
1369 | if (!(config.flags & Config.Flags.retainStderr) && stderrFD > STDERR_FILENO | |
1370 | && stderrFD != getFD(std.stdio.stderr)) | |
1371 | stderr.close(); | |
b4c522fa | 1372 | |
5fee5ec3 IB |
1373 | // close the thread handle in the process info structure |
1374 | CloseHandle(pi.hThread); | |
1375 | if (config.flags & Config.Flags.detached) | |
1376 | { | |
1377 | CloseHandle(pi.hProcess); | |
1378 | return new Pid(pi.dwProcessId); | |
1379 | } | |
1380 | return new Pid(pi.dwProcessId, pi.hProcess); | |
b4c522fa | 1381 | } |
b4c522fa | 1382 | |
5fee5ec3 IB |
1383 | // Converts childEnv to a zero-terminated array of zero-terminated strings |
1384 | // on the form "name=value", optionally adding those of the current process' | |
1385 | // environment strings that are not present in childEnv. If the parent's | |
1386 | // environment should be inherited without modification, this function | |
1387 | // returns environ directly. | |
1388 | version (Posix) | |
1389 | private const(char*)* createEnv(const string[string] childEnv, | |
1390 | bool mergeWithParentEnv) | |
1391 | { | |
1392 | // Determine the number of strings in the parent's environment. | |
1393 | int parentEnvLength = 0; | |
1394 | auto environ = getEnvironPtr; | |
1395 | if (mergeWithParentEnv) | |
1396 | { | |
1397 | if (childEnv.length == 0) return environ; | |
1398 | while (environ[parentEnvLength] != null) ++parentEnvLength; | |
1399 | } | |
b4c522fa | 1400 | |
5fee5ec3 IB |
1401 | // Convert the "new" variables to C-style strings. |
1402 | auto envz = new const(char)*[parentEnvLength + childEnv.length + 1]; | |
1403 | int pos = 0; | |
1404 | foreach (var, val; childEnv) | |
1405 | envz[pos++] = (var~'='~val~'\0').ptr; | |
b4c522fa | 1406 | |
5fee5ec3 IB |
1407 | // Add the parent's environment. |
1408 | foreach (environStr; environ[0 .. parentEnvLength]) | |
1409 | { | |
1410 | int eqPos = 0; | |
1411 | while (environStr[eqPos] != '=' && environStr[eqPos] != '\0') ++eqPos; | |
1412 | if (environStr[eqPos] != '=') continue; | |
1413 | auto var = environStr[0 .. eqPos]; | |
1414 | if (var in childEnv) continue; | |
1415 | envz[pos++] = environStr; | |
1416 | } | |
1417 | envz[pos] = null; | |
1418 | return envz.ptr; | |
1419 | } | |
b4c522fa | 1420 | |
5fee5ec3 IB |
1421 | version (Posix) @system unittest |
1422 | { | |
1423 | auto e1 = createEnv(null, false); | |
1424 | assert(e1 != null && *e1 == null); | |
b4c522fa | 1425 | |
5fee5ec3 IB |
1426 | auto e2 = createEnv(null, true); |
1427 | assert(e2 != null); | |
1428 | int i = 0; | |
1429 | auto environ = getEnvironPtr; | |
1430 | for (; environ[i] != null; ++i) | |
b4c522fa | 1431 | { |
5fee5ec3 IB |
1432 | assert(e2[i] != null); |
1433 | import core.stdc.string; | |
1434 | assert(strcmp(e2[i], environ[i]) == 0); | |
b4c522fa | 1435 | } |
5fee5ec3 | 1436 | assert(e2[i] == null); |
b4c522fa | 1437 | |
5fee5ec3 IB |
1438 | auto e3 = createEnv(["foo" : "bar", "hello" : "world"], false); |
1439 | assert(e3 != null && e3[0] != null && e3[1] != null && e3[2] == null); | |
1440 | assert((e3[0][0 .. 8] == "foo=bar\0" && e3[1][0 .. 12] == "hello=world\0") | |
1441 | || (e3[0][0 .. 12] == "hello=world\0" && e3[1][0 .. 8] == "foo=bar\0")); | |
b4c522fa IB |
1442 | } |
1443 | ||
5fee5ec3 IB |
1444 | |
1445 | // Converts childEnv to a Windows environment block, which is on the form | |
1446 | // "name1=value1\0name2=value2\0...nameN=valueN\0\0", optionally adding | |
1447 | // those of the current process' environment strings that are not present | |
1448 | // in childEnv. Returns null if the parent's environment should be | |
1449 | // inherited without modification, as this is what is expected by | |
1450 | // CreateProcess(). | |
1451 | version (Windows) | |
1452 | private LPVOID createEnv(const string[string] childEnv, | |
1453 | bool mergeWithParentEnv) | |
b4c522fa | 1454 | { |
5fee5ec3 IB |
1455 | if (mergeWithParentEnv && childEnv.length == 0) return null; |
1456 | import std.array : appender; | |
1457 | import std.uni : toUpper; | |
1458 | auto envz = appender!(wchar[])(); | |
1459 | void put(string var, string val) | |
b4c522fa | 1460 | { |
5fee5ec3 IB |
1461 | envz.put(var); |
1462 | envz.put('='); | |
1463 | envz.put(val); | |
1464 | envz.put(cast(wchar) '\0'); | |
b4c522fa | 1465 | } |
5fee5ec3 IB |
1466 | |
1467 | // Add the variables in childEnv, removing them from parentEnv | |
1468 | // if they exist there too. | |
1469 | auto parentEnv = mergeWithParentEnv ? environment.toAA() : null; | |
1470 | foreach (k, v; childEnv) | |
b4c522fa | 1471 | { |
5fee5ec3 IB |
1472 | auto uk = toUpper(k); |
1473 | put(uk, v); | |
1474 | if (uk in parentEnv) parentEnv.remove(uk); | |
b4c522fa | 1475 | } |
b4c522fa | 1476 | |
5fee5ec3 IB |
1477 | // Add remaining parent environment variables. |
1478 | foreach (k, v; parentEnv) put(k, v); | |
1479 | ||
1480 | // Two final zeros are needed in case there aren't any environment vars, | |
1481 | // and the last one does no harm when there are. | |
1482 | envz.put("\0\0"w); | |
1483 | return envz.data.ptr; | |
b4c522fa IB |
1484 | } |
1485 | ||
5fee5ec3 | 1486 | version (Windows) @system unittest |
b4c522fa | 1487 | { |
5fee5ec3 IB |
1488 | assert(createEnv(null, true) == null); |
1489 | assert((cast(wchar*) createEnv(null, false))[0 .. 2] == "\0\0"w); | |
1490 | auto e1 = (cast(wchar*) createEnv(["foo":"bar", "ab":"c"], false))[0 .. 14]; | |
1491 | assert(e1 == "FOO=bar\0AB=c\0\0"w || e1 == "AB=c\0FOO=bar\0\0"w); | |
b4c522fa IB |
1492 | } |
1493 | ||
5fee5ec3 IB |
1494 | // Searches the PATH variable for the given executable file, |
1495 | // (checking that it is in fact executable). | |
1496 | version (Posix) | |
1497 | package(std) string searchPathFor(scope const(char)[] executable) | |
1498 | @safe | |
1499 | { | |
1500 | import std.algorithm.iteration : splitter; | |
0fb57034 | 1501 | import std.conv : to; |
5fee5ec3 | 1502 | import std.path : chainPath; |
b4c522fa | 1503 | |
0fb57034 | 1504 | typeof(return) result; |
b4c522fa | 1505 | |
5fee5ec3 IB |
1506 | environment.getImpl("PATH", |
1507 | (scope const(char)[] path) | |
1508 | { | |
1509 | if (!path) | |
1510 | return; | |
b4c522fa | 1511 | |
5fee5ec3 IB |
1512 | foreach (dir; splitter(path, ":")) |
1513 | { | |
1514 | auto execPath = chainPath(dir, executable); | |
1515 | if (isExecutable(execPath)) | |
1516 | { | |
0fb57034 | 1517 | result = execPath.to!(typeof(result)); |
5fee5ec3 IB |
1518 | return; |
1519 | } | |
1520 | } | |
1521 | }); | |
b4c522fa | 1522 | |
5fee5ec3 IB |
1523 | return result; |
1524 | } | |
1525 | ||
1526 | // Checks whether the file exists and can be executed by the | |
1527 | // current user. | |
b4c522fa | 1528 | version (Posix) |
5fee5ec3 IB |
1529 | private bool isExecutable(R)(R path) @trusted nothrow @nogc |
1530 | if (isInputRange!R && isSomeChar!(ElementEncodingType!R)) | |
b4c522fa | 1531 | { |
5fee5ec3 | 1532 | return (access(path.tempCString(), X_OK) == 0); |
b4c522fa | 1533 | } |
5fee5ec3 IB |
1534 | |
1535 | version (Posix) @safe unittest | |
b4c522fa | 1536 | { |
5fee5ec3 IB |
1537 | import std.algorithm; |
1538 | auto lsPath = searchPathFor("ls"); | |
1539 | assert(!lsPath.empty); | |
1540 | assert(lsPath[0] == '/'); | |
1541 | assert(lsPath.endsWith("ls")); | |
1542 | auto unlikely = searchPathFor("lkmqwpoialhggyaofijadsohufoiqezm"); | |
1543 | assert(unlikely is null, "Are you kidding me?"); | |
1544 | } | |
b4c522fa | 1545 | |
5fee5ec3 IB |
1546 | // Sets or unsets the FD_CLOEXEC flag on the given file descriptor. |
1547 | version (Posix) | |
1548 | private void setCLOEXEC(int fd, bool on) nothrow @nogc | |
1549 | { | |
1550 | import core.sys.posix.fcntl : fcntl, F_GETFD, FD_CLOEXEC, F_SETFD; | |
1551 | auto flags = fcntl(fd, F_GETFD); | |
1552 | if (flags >= 0) | |
b4c522fa | 1553 | { |
5fee5ec3 IB |
1554 | if (on) flags |= FD_CLOEXEC; |
1555 | else flags &= ~(cast(typeof(flags)) FD_CLOEXEC); | |
1556 | flags = fcntl(fd, F_SETFD, flags); | |
b4c522fa | 1557 | } |
5fee5ec3 IB |
1558 | assert(flags != -1 || .errno == EBADF); |
1559 | } | |
b4c522fa | 1560 | |
5fee5ec3 IB |
1561 | @system unittest // Command line arguments in spawnProcess(). |
1562 | { | |
1563 | version (Windows) TestScript prog = | |
1564 | "if not [%~1]==[foo] ( exit 1 ) | |
1565 | if not [%~2]==[bar] ( exit 2 ) | |
1566 | exit 0"; | |
1567 | else version (Posix) TestScript prog = | |
1568 | `if test "$1" != "foo"; then exit 1; fi | |
1569 | if test "$2" != "bar"; then exit 2; fi | |
1570 | exit 0`; | |
1571 | assert(wait(spawnProcess(prog.path)) == 1); | |
1572 | assert(wait(spawnProcess([prog.path])) == 1); | |
1573 | assert(wait(spawnProcess([prog.path, "foo"])) == 2); | |
1574 | assert(wait(spawnProcess([prog.path, "foo", "baz"])) == 2); | |
1575 | assert(wait(spawnProcess([prog.path, "foo", "bar"])) == 0); | |
b4c522fa IB |
1576 | } |
1577 | ||
5fee5ec3 IB |
1578 | // test that file descriptors are correctly closed / left open. |
1579 | // ideally this would be done by the child process making libc | |
1580 | // calls, but we make do... | |
1581 | version (Posix) @system unittest | |
b4c522fa | 1582 | { |
0fb57034 | 1583 | import core.stdc.errno : errno; |
5fee5ec3 IB |
1584 | import core.sys.posix.fcntl : open, O_RDONLY; |
1585 | import core.sys.posix.unistd : close; | |
1586 | import std.algorithm.searching : canFind, findSplitBefore; | |
1587 | import std.array : split; | |
1588 | import std.conv : to; | |
1589 | static import std.file; | |
1590 | import std.functional : reverseArgs; | |
1591 | import std.path : buildPath; | |
b4c522fa | 1592 | |
5fee5ec3 IB |
1593 | auto directory = uniqueTempPath(); |
1594 | std.file.mkdir(directory); | |
1595 | scope(exit) std.file.rmdirRecurse(directory); | |
1596 | auto path = buildPath(directory, "tmp"); | |
1597 | std.file.write(path, null); | |
0fb57034 | 1598 | errno = 0; |
5fee5ec3 | 1599 | auto fd = open(path.tempCString, O_RDONLY); |
0fb57034 IB |
1600 | if (fd == -1) |
1601 | { | |
1602 | import core.stdc.string : strerror; | |
1603 | import std.stdio : stderr; | |
1604 | import std.string : fromStringz; | |
1605 | ||
1606 | // For the CI logs | |
1607 | stderr.writefln("%s: could not open '%s': %s", | |
1608 | __FUNCTION__, path, strerror(errno).fromStringz); | |
1609 | // TODO: should we retry here instead? | |
1610 | return; | |
1611 | } | |
5fee5ec3 | 1612 | scope(exit) close(fd); |
b4c522fa | 1613 | |
5fee5ec3 IB |
1614 | // command >&2 (or any other number) checks whethether that number |
1615 | // file descriptor is open. | |
1616 | // Can't use this for arbitrary descriptors as many shells only support | |
1617 | // single digit fds. | |
1618 | TestScript testDefaults = `command >&0 && command >&1 && command >&2`; | |
1619 | assert(execute(testDefaults.path).status == 0); | |
1620 | assert(execute(testDefaults.path, null, Config.inheritFDs).status == 0); | |
b4c522fa | 1621 | |
5fee5ec3 IB |
1622 | // Try a few different methods to check whether there are any |
1623 | // incorrectly-open files. | |
1624 | void testFDs() | |
1625 | { | |
1626 | // try /proc/<pid>/fd/ on linux | |
1627 | version (linux) | |
1628 | { | |
1629 | TestScript proc = "ls /proc/$$/fd"; | |
1630 | auto procRes = execute(proc.path, null); | |
1631 | if (procRes.status == 0) | |
1632 | { | |
1633 | auto fdStr = fd.to!string; | |
1634 | assert(!procRes.output.split.canFind(fdStr)); | |
1635 | assert(execute(proc.path, null, Config.inheritFDs) | |
1636 | .output.split.canFind(fdStr)); | |
1637 | return; | |
1638 | } | |
1639 | } | |
b4c522fa | 1640 | |
5fee5ec3 IB |
1641 | // try fuser (might sometimes need permissions) |
1642 | TestScript fuser = "echo $$ && fuser -f " ~ path; | |
1643 | auto fuserRes = execute(fuser.path, null); | |
1644 | if (fuserRes.status == 0) | |
1645 | { | |
1646 | assert(!reverseArgs!canFind(fuserRes | |
1647 | .output.findSplitBefore("\n").expand)); | |
1648 | assert(reverseArgs!canFind(execute(fuser.path, null, Config.inheritFDs) | |
1649 | .output.findSplitBefore("\n").expand)); | |
1650 | return; | |
1651 | } | |
b4c522fa | 1652 | |
5fee5ec3 IB |
1653 | // last resort, try lsof (not available on all Posix) |
1654 | TestScript lsof = "lsof -p$$"; | |
1655 | auto lsofRes = execute(lsof.path, null); | |
1656 | if (lsofRes.status == 0) | |
1657 | { | |
1658 | assert(!lsofRes.output.canFind(path)); | |
0fb57034 IB |
1659 | auto lsofOut = execute(lsof.path, null, Config.inheritFDs).output; |
1660 | if (!lsofOut.canFind(path)) | |
1661 | { | |
1662 | std.stdio.stderr.writeln(__FILE__, ':', __LINE__, | |
1663 | ": Warning: unexpected lsof output:", lsofOut); | |
1664 | } | |
5fee5ec3 IB |
1665 | return; |
1666 | } | |
b4c522fa | 1667 | |
5fee5ec3 IB |
1668 | std.stdio.stderr.writeln(__FILE__, ':', __LINE__, |
1669 | ": Warning: Couldn't find any way to check open files"); | |
b4c522fa | 1670 | } |
5fee5ec3 | 1671 | testFDs(); |
b4c522fa IB |
1672 | } |
1673 | ||
5fee5ec3 | 1674 | @system unittest // Environment variables in spawnProcess(). |
b4c522fa | 1675 | { |
5fee5ec3 IB |
1676 | // We really should use set /a on Windows, but Wine doesn't support it. |
1677 | version (Windows) TestScript envProg = | |
1678 | `if [%STD_PROCESS_UNITTEST1%] == [1] ( | |
1679 | if [%STD_PROCESS_UNITTEST2%] == [2] (exit 3) | |
1680 | exit 1 | |
1681 | ) | |
1682 | if [%STD_PROCESS_UNITTEST1%] == [4] ( | |
1683 | if [%STD_PROCESS_UNITTEST2%] == [2] (exit 6) | |
1684 | exit 4 | |
1685 | ) | |
1686 | if [%STD_PROCESS_UNITTEST2%] == [2] (exit 2) | |
1687 | exit 0`; | |
1688 | version (Posix) TestScript envProg = | |
1689 | `if test "$std_process_unittest1" = ""; then | |
1690 | std_process_unittest1=0 | |
1691 | fi | |
1692 | if test "$std_process_unittest2" = ""; then | |
1693 | std_process_unittest2=0 | |
1694 | fi | |
1695 | exit $(($std_process_unittest1+$std_process_unittest2))`; | |
b4c522fa | 1696 | |
5fee5ec3 IB |
1697 | environment.remove("std_process_unittest1"); // Just in case. |
1698 | environment.remove("std_process_unittest2"); | |
1699 | assert(wait(spawnProcess(envProg.path)) == 0); | |
1700 | assert(wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0); | |
b4c522fa | 1701 | |
5fee5ec3 IB |
1702 | environment["std_process_unittest1"] = "1"; |
1703 | assert(wait(spawnProcess(envProg.path)) == 1); | |
1704 | assert(wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0); | |
b4c522fa | 1705 | |
5fee5ec3 IB |
1706 | auto env = ["std_process_unittest2" : "2"]; |
1707 | assert(wait(spawnProcess(envProg.path, env)) == 3); | |
1708 | assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 2); | |
b4c522fa | 1709 | |
5fee5ec3 IB |
1710 | env["std_process_unittest1"] = "4"; |
1711 | assert(wait(spawnProcess(envProg.path, env)) == 6); | |
1712 | assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); | |
b4c522fa | 1713 | |
5fee5ec3 IB |
1714 | environment.remove("std_process_unittest1"); |
1715 | assert(wait(spawnProcess(envProg.path, env)) == 6); | |
1716 | assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); | |
1717 | } | |
b4c522fa | 1718 | |
5fee5ec3 IB |
1719 | @system unittest // Stream redirection in spawnProcess(). |
1720 | { | |
1721 | import std.path : buildPath; | |
1722 | import std.string; | |
1723 | version (Windows) TestScript prog = | |
1724 | "set /p INPUT= | |
1725 | echo %INPUT% output %~1 | |
1726 | echo %INPUT% error %~2 1>&2 | |
1727 | echo done > %3"; | |
1728 | else version (Posix) TestScript prog = | |
1729 | "read INPUT | |
1730 | echo $INPUT output $1 | |
1731 | echo $INPUT error $2 >&2 | |
1732 | echo done > \"$3\""; | |
b4c522fa | 1733 | |
5fee5ec3 IB |
1734 | // Pipes |
1735 | void testPipes(Config config) | |
1736 | { | |
1737 | import std.file, std.uuid, core.thread, std.exception; | |
1738 | auto pipei = pipe(); | |
1739 | auto pipeo = pipe(); | |
1740 | auto pipee = pipe(); | |
1741 | auto done = buildPath(tempDir(), randomUUID().toString()); | |
1742 | auto pid = spawnProcess([prog.path, "foo", "bar", done], | |
1743 | pipei.readEnd, pipeo.writeEnd, pipee.writeEnd, null, config); | |
1744 | pipei.writeEnd.writeln("input"); | |
1745 | pipei.writeEnd.flush(); | |
1746 | assert(pipeo.readEnd.readln().chomp() == "input output foo"); | |
1747 | assert(pipee.readEnd.readln().chomp().stripRight() == "input error bar"); | |
1748 | if (config.flags & Config.Flags.detached) | |
1749 | while (!done.exists) Thread.sleep(10.msecs); | |
1750 | else | |
1751 | wait(pid); | |
1752 | while (remove(done).collectException) Thread.sleep(10.msecs); | |
1753 | } | |
b4c522fa | 1754 | |
5fee5ec3 IB |
1755 | // Files |
1756 | void testFiles(Config config) | |
1757 | { | |
1758 | import std.ascii, std.file, std.uuid, core.thread, std.exception; | |
1759 | auto pathi = buildPath(tempDir(), randomUUID().toString()); | |
1760 | auto patho = buildPath(tempDir(), randomUUID().toString()); | |
1761 | auto pathe = buildPath(tempDir(), randomUUID().toString()); | |
1762 | std.file.write(pathi, "INPUT"~std.ascii.newline); | |
1763 | auto filei = File(pathi, "r"); | |
1764 | auto fileo = File(patho, "w"); | |
1765 | auto filee = File(pathe, "w"); | |
1766 | auto done = buildPath(tempDir(), randomUUID().toString()); | |
1767 | auto pid = spawnProcess([prog.path, "bar", "baz", done], filei, fileo, filee, null, config); | |
1768 | if (config.flags & Config.Flags.detached) | |
1769 | while (!done.exists) Thread.sleep(10.msecs); | |
1770 | else | |
1771 | wait(pid); | |
1772 | assert(readText(patho).chomp() == "INPUT output bar"); | |
1773 | assert(readText(pathe).chomp().stripRight() == "INPUT error baz"); | |
1774 | while (remove(pathi).collectException) Thread.sleep(10.msecs); | |
1775 | while (remove(patho).collectException) Thread.sleep(10.msecs); | |
1776 | while (remove(pathe).collectException) Thread.sleep(10.msecs); | |
1777 | while (remove(done).collectException) Thread.sleep(10.msecs); | |
1778 | } | |
b4c522fa | 1779 | |
5fee5ec3 IB |
1780 | testPipes(Config.none); |
1781 | testFiles(Config.none); | |
1782 | testPipes(Config.detached); | |
1783 | testFiles(Config.detached); | |
1784 | } | |
b4c522fa | 1785 | |
5fee5ec3 IB |
1786 | @system unittest // Error handling in spawnProcess() |
1787 | { | |
1788 | import std.algorithm.searching : canFind; | |
1789 | import std.exception : assertThrown, collectExceptionMsg; | |
b4c522fa | 1790 | |
5fee5ec3 IB |
1791 | static void testNotFoundException(string program) |
1792 | { | |
1793 | assert(collectExceptionMsg!ProcessException(spawnProcess(program)).canFind(program)); | |
1794 | assert(collectExceptionMsg!ProcessException(spawnProcess(program, null, Config.detached)).canFind(program)); | |
1795 | } | |
1796 | testNotFoundException("ewrgiuhrifuheiohnmnvqweoijwf"); | |
1797 | testNotFoundException("./rgiuhrifuheiohnmnvqweoijwf"); | |
b4c522fa | 1798 | |
5fee5ec3 IB |
1799 | // can't execute malformed file with executable permissions |
1800 | version (Posix) | |
1801 | { | |
1802 | import std.path : buildPath; | |
1803 | import std.file : remove, write, setAttributes, tempDir; | |
1804 | import core.sys.posix.sys.stat : S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH; | |
1805 | import std.conv : to; | |
1806 | string deleteme = buildPath(tempDir(), "deleteme.std.process.unittest.pid") ~ to!string(thisProcessID); | |
1807 | write(deleteme, ""); | |
1808 | scope(exit) remove(deleteme); | |
1809 | setAttributes(deleteme, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); | |
1810 | assertThrown!ProcessException(spawnProcess(deleteme)); | |
1811 | assertThrown!ProcessException(spawnProcess(deleteme, null, Config.detached)); | |
1812 | } | |
1813 | } | |
b4c522fa | 1814 | |
5fee5ec3 IB |
1815 | @system unittest // Specifying a working directory. |
1816 | { | |
1817 | import std.path; | |
1818 | import std.file; | |
1819 | TestScript prog = "echo foo>bar"; | |
b4c522fa | 1820 | |
5fee5ec3 IB |
1821 | auto directory = uniqueTempPath(); |
1822 | mkdir(directory); | |
1823 | scope(exit) rmdirRecurse(directory); | |
b4c522fa | 1824 | |
5fee5ec3 IB |
1825 | auto pid = spawnProcess([prog.path], null, Config.none, directory); |
1826 | wait(pid); | |
1827 | assert(exists(buildPath(directory, "bar"))); | |
b4c522fa IB |
1828 | } |
1829 | ||
5fee5ec3 | 1830 | @system unittest // Specifying a bad working directory. |
b4c522fa | 1831 | { |
5fee5ec3 IB |
1832 | import std.exception : assertThrown; |
1833 | import std.file; | |
1834 | TestScript prog = "echo"; | |
1835 | ||
1836 | auto directory = uniqueTempPath(); | |
1837 | assertThrown!ProcessException(spawnProcess([prog.path], null, Config.none, directory)); | |
1838 | assertThrown!ProcessException(spawnProcess([prog.path], null, Config.detached, directory)); | |
1839 | ||
1840 | std.file.write(directory, "foo"); | |
1841 | scope(exit) remove(directory); | |
1842 | assertThrown!ProcessException(spawnProcess([prog.path], null, Config.none, directory)); | |
1843 | assertThrown!ProcessException(spawnProcess([prog.path], null, Config.detached, directory)); | |
1844 | ||
1845 | // can't run in directory if user does not have search permission on this directory | |
1846 | version (Posix) | |
1847 | { | |
1848 | if (core.sys.posix.unistd.getuid() != 0) | |
1849 | { | |
1850 | import core.sys.posix.sys.stat : S_IRUSR; | |
1851 | auto directoryNoSearch = uniqueTempPath(); | |
1852 | mkdir(directoryNoSearch); | |
1853 | scope(exit) rmdirRecurse(directoryNoSearch); | |
1854 | setAttributes(directoryNoSearch, S_IRUSR); | |
1855 | assertThrown!ProcessException(spawnProcess(prog.path, null, Config.none, directoryNoSearch)); | |
1856 | assertThrown!ProcessException(spawnProcess(prog.path, null, Config.detached, directoryNoSearch)); | |
1857 | } | |
1858 | } | |
b4c522fa IB |
1859 | } |
1860 | ||
5fee5ec3 | 1861 | @system unittest // Specifying empty working directory. |
b4c522fa | 1862 | { |
5fee5ec3 IB |
1863 | TestScript prog = ""; |
1864 | ||
1865 | string directory = ""; | |
1866 | assert(directory.ptr && !directory.length); | |
1867 | spawnProcess([prog.path], null, Config.none, directory).wait(); | |
b4c522fa IB |
1868 | } |
1869 | ||
5fee5ec3 IB |
1870 | // Reopening the standard streams (https://issues.dlang.org/show_bug.cgi?id=13258) |
1871 | @system unittest | |
b4c522fa | 1872 | { |
5fee5ec3 IB |
1873 | import std.string; |
1874 | import std.file; | |
1875 | void fun() | |
b4c522fa | 1876 | { |
5fee5ec3 IB |
1877 | spawnShell("echo foo").wait(); |
1878 | spawnShell("echo bar").wait(); | |
b4c522fa IB |
1879 | } |
1880 | ||
5fee5ec3 IB |
1881 | auto tmpFile = uniqueTempPath(); |
1882 | scope(exit) if (exists(tmpFile)) remove(tmpFile); | |
b4c522fa | 1883 | |
b4c522fa | 1884 | { |
5fee5ec3 IB |
1885 | auto oldOut = std.stdio.stdout; |
1886 | scope(exit) std.stdio.stdout = oldOut; | |
b4c522fa | 1887 | |
5fee5ec3 IB |
1888 | std.stdio.stdout = File(tmpFile, "w"); |
1889 | fun(); | |
1890 | std.stdio.stdout.close(); | |
b4c522fa IB |
1891 | } |
1892 | ||
5fee5ec3 IB |
1893 | auto lines = readText(tmpFile).splitLines(); |
1894 | assert(lines == ["foo", "bar"]); | |
b4c522fa IB |
1895 | } |
1896 | ||
5fee5ec3 IB |
1897 | // MSVCRT workaround (https://issues.dlang.org/show_bug.cgi?id=14422) |
1898 | version (Windows) | |
1899 | @system unittest | |
b4c522fa | 1900 | { |
5fee5ec3 IB |
1901 | auto fn = uniqueTempPath(); |
1902 | scope(exit) if (exists(fn)) remove(fn); | |
1903 | std.file.write(fn, "AAAAAAAAAA"); | |
b4c522fa | 1904 | |
5fee5ec3 IB |
1905 | auto f = File(fn, "a"); |
1906 | spawnProcess(["cmd", "/c", "echo BBBBB"], std.stdio.stdin, f).wait(); | |
b4c522fa | 1907 | |
5fee5ec3 IB |
1908 | auto data = readText(fn); |
1909 | assert(data == "AAAAAAAAAABBBBB\r\n", data); | |
b4c522fa IB |
1910 | } |
1911 | ||
5fee5ec3 IB |
1912 | // https://issues.dlang.org/show_bug.cgi?id=20765 |
1913 | // Test that running processes with relative path works in conjunction | |
1914 | // with indicating a workDir. | |
1915 | version (Posix) @system unittest | |
b4c522fa | 1916 | { |
5fee5ec3 IB |
1917 | import std.file : mkdir, write, setAttributes, rmdirRecurse; |
1918 | import std.conv : octal; | |
b4c522fa | 1919 | |
5fee5ec3 IB |
1920 | auto dir = uniqueTempPath(); |
1921 | mkdir(dir); | |
1922 | scope(exit) rmdirRecurse(dir); | |
1923 | write(dir ~ "/program", "#!/bin/sh\necho Hello"); | |
1924 | setAttributes(dir ~ "/program", octal!700); | |
b4c522fa | 1925 | |
5fee5ec3 | 1926 | assert(execute(["./program"], null, Config.none, size_t.max, dir).output == "Hello\n"); |
b4c522fa IB |
1927 | } |
1928 | ||
1929 | /** | |
5fee5ec3 IB |
1930 | A variation on $(LREF spawnProcess) that runs the given _command through |
1931 | the current user's preferred _command interpreter (aka. shell). | |
b4c522fa | 1932 | |
5fee5ec3 IB |
1933 | The string `command` is passed verbatim to the shell, and is therefore |
1934 | subject to its rules about _command structure, argument/filename quoting | |
1935 | and escaping of special characters. | |
1936 | The path to the shell executable defaults to $(LREF nativeShell). | |
b4c522fa | 1937 | |
5fee5ec3 IB |
1938 | In all other respects this function works just like `spawnProcess`. |
1939 | Please refer to the $(LREF spawnProcess) documentation for descriptions | |
1940 | of the other function parameters, the return value and any exceptions | |
1941 | that may be thrown. | |
1942 | --- | |
1943 | // Run the command/program "foo" on the file named "my file.txt", and | |
1944 | // redirect its output into foo.log. | |
1945 | auto pid = spawnShell(`foo "my file.txt" > foo.log`); | |
1946 | wait(pid); | |
1947 | --- | |
b4c522fa | 1948 | |
5fee5ec3 IB |
1949 | See_also: |
1950 | $(LREF escapeShellCommand), which may be helpful in constructing a | |
1951 | properly quoted and escaped shell _command line for the current platform. | |
1952 | */ | |
1953 | Pid spawnShell(scope const(char)[] command, | |
1954 | File stdin = std.stdio.stdin, | |
1955 | File stdout = std.stdio.stdout, | |
1956 | File stderr = std.stdio.stderr, | |
1957 | scope const string[string] env = null, | |
1958 | Config config = Config.none, | |
1959 | scope const(char)[] workDir = null, | |
1960 | scope string shellPath = nativeShell) | |
1961 | @trusted // See reason below | |
1962 | { | |
1963 | version (Windows) | |
b4c522fa | 1964 | { |
5fee5ec3 IB |
1965 | // CMD does not parse its arguments like other programs. |
1966 | // It does not use CommandLineToArgvW. | |
1967 | // Instead, it treats the first and last quote specially. | |
1968 | // See CMD.EXE /? for details. | |
1969 | const commandLine = escapeShellFileName(shellPath) | |
1970 | ~ ` ` ~ shellSwitch ~ ` "` ~ command ~ `"`; | |
1971 | return spawnProcessWin(commandLine, shellPath, stdin, stdout, stderr, env, config, workDir); | |
b4c522fa | 1972 | } |
5fee5ec3 | 1973 | else version (Posix) |
b4c522fa | 1974 | { |
5fee5ec3 IB |
1975 | const(char)[][3] args; |
1976 | args[0] = shellPath; | |
1977 | args[1] = shellSwitch; | |
1978 | args[2] = command; | |
1979 | /* The passing of args converts the static array, which is initialized with `scope` pointers, | |
1980 | * to a dynamic array, which is also a scope parameter. So, it is a scope pointer to a | |
1981 | * scope pointer, which although is safely used here, D doesn't allow transitive scope. | |
1982 | * See https://github.com/dlang/dmd/pull/10951 | |
1983 | */ | |
1984 | return spawnProcessPosix(args, stdin, stdout, stderr, env, config, workDir); | |
b4c522fa | 1985 | } |
5fee5ec3 IB |
1986 | else |
1987 | static assert(0); | |
1988 | } | |
b4c522fa | 1989 | |
5fee5ec3 IB |
1990 | /// ditto |
1991 | Pid spawnShell(scope const(char)[] command, | |
1992 | scope const string[string] env, | |
1993 | Config config = Config.none, | |
1994 | scope const(char)[] workDir = null, | |
1995 | scope string shellPath = nativeShell) | |
1996 | @trusted // TODO: Should be @safe | |
1997 | { | |
1998 | return spawnShell(command, | |
1999 | std.stdio.stdin, | |
2000 | std.stdio.stdout, | |
2001 | std.stdio.stderr, | |
2002 | env, | |
2003 | config, | |
2004 | workDir, | |
2005 | shellPath); | |
2006 | } | |
2007 | ||
2008 | @system unittest | |
2009 | { | |
2010 | version (Windows) | |
2011 | auto cmd = "echo %FOO%"; | |
2012 | else version (Posix) | |
2013 | auto cmd = "echo $foo"; | |
2014 | import std.file; | |
2015 | auto tmpFile = uniqueTempPath(); | |
2016 | scope(exit) if (exists(tmpFile)) remove(tmpFile); | |
2017 | auto redir = "> \""~tmpFile~'"'; | |
2018 | auto env = ["foo" : "bar"]; | |
2019 | assert(wait(spawnShell(cmd~redir, env)) == 0); | |
2020 | auto f = File(tmpFile, "a"); | |
2021 | version (CRuntime_Microsoft) f.seek(0, SEEK_END); // MSVCRT probably seeks to the end when writing, not before | |
2022 | assert(wait(spawnShell(cmd, std.stdio.stdin, f, std.stdio.stderr, env)) == 0); | |
2023 | f.close(); | |
2024 | auto output = std.file.readText(tmpFile); | |
2025 | assert(output == "bar\nbar\n" || output == "bar\r\nbar\r\n"); | |
b4c522fa IB |
2026 | } |
2027 | ||
5fee5ec3 IB |
2028 | version (Windows) |
2029 | @system unittest | |
2030 | { | |
2031 | import std.string; | |
2032 | import std.conv : text; | |
2033 | TestScript prog = "echo %0 %*"; | |
2034 | auto outputFn = uniqueTempPath(); | |
2035 | scope(exit) if (exists(outputFn)) remove(outputFn); | |
2036 | auto args = [`a b c`, `a\b\c\`, `a"b"c"`]; | |
2037 | auto result = executeShell( | |
2038 | escapeShellCommand([prog.path] ~ args) | |
2039 | ~ " > " ~ | |
2040 | escapeShellFileName(outputFn)); | |
2041 | assert(result.status == 0); | |
2042 | auto args2 = outputFn.readText().strip().parseCommandLine()[1..$]; | |
2043 | assert(args == args2, text(args2)); | |
2044 | } | |
b4c522fa IB |
2045 | |
2046 | ||
2047 | /** | |
5fee5ec3 IB |
2048 | Options that control the behaviour of process creation functions in this |
2049 | module. Most options only apply to $(LREF spawnProcess) and | |
2050 | $(LREF spawnShell). | |
b4c522fa | 2051 | |
5fee5ec3 | 2052 | Example: |
b4c522fa | 2053 | --- |
5fee5ec3 | 2054 | auto logFile = File("myapp_error.log", "w"); |
b4c522fa | 2055 | |
5fee5ec3 IB |
2056 | // Start program, suppressing the console window (Windows only), |
2057 | // redirect its error stream to logFile, and leave logFile open | |
2058 | // in the parent process as well. | |
2059 | auto pid = spawnProcess("myapp", stdin, stdout, logFile, | |
2060 | Config.retainStderr | Config.suppressConsole); | |
2061 | scope(exit) | |
2062 | { | |
2063 | auto exitCode = wait(pid); | |
2064 | logFile.writeln("myapp exited with code ", exitCode); | |
2065 | logFile.close(); | |
2066 | } | |
b4c522fa | 2067 | --- |
5fee5ec3 IB |
2068 | */ |
2069 | struct Config | |
2070 | { | |
2071 | /** | |
2072 | Flag options. | |
2073 | Use bitwise OR to combine flags. | |
2074 | **/ | |
2075 | enum Flags | |
2076 | { | |
2077 | none = 0, | |
2078 | ||
2079 | /** | |
2080 | By default, the child process inherits the parent's environment, | |
2081 | and any environment variables passed to $(LREF spawnProcess) will | |
2082 | be added to it. If this flag is set, the only variables in the | |
2083 | child process' environment will be those given to spawnProcess. | |
2084 | */ | |
2085 | newEnv = 1, | |
2086 | ||
2087 | /** | |
2088 | Unless the child process inherits the standard input/output/error | |
2089 | streams of its parent, one almost always wants the streams closed | |
2090 | in the parent when $(LREF spawnProcess) returns. Therefore, by | |
2091 | default, this is done. If this is not desirable, pass any of these | |
2092 | options to spawnProcess. | |
2093 | */ | |
2094 | retainStdin = 2, | |
2095 | retainStdout = 4, /// ditto | |
2096 | retainStderr = 8, /// ditto | |
2097 | ||
2098 | /** | |
2099 | On Windows, if the child process is a console application, this | |
2100 | flag will prevent the creation of a console window. Otherwise, | |
2101 | it will be ignored. On POSIX, `suppressConsole` has no effect. | |
2102 | */ | |
2103 | suppressConsole = 16, | |
2104 | ||
2105 | /** | |
2106 | On POSIX, open $(LINK2 http://en.wikipedia.org/wiki/File_descriptor,file descriptors) | |
2107 | are by default inherited by the child process. As this may lead | |
2108 | to subtle bugs when pipes or multiple threads are involved, | |
2109 | $(LREF spawnProcess) ensures that all file descriptors except the | |
2110 | ones that correspond to standard input/output/error are closed | |
2111 | in the child process when it starts. Use `inheritFDs` to prevent | |
2112 | this. | |
2113 | ||
2114 | On Windows, this option has no effect, and any handles which have been | |
2115 | explicitly marked as inheritable will always be inherited by the child | |
2116 | process. | |
2117 | */ | |
2118 | inheritFDs = 32, | |
2119 | ||
2120 | /** | |
2121 | Spawn process in detached state. This removes the need in calling | |
2122 | $(LREF wait) to clean up the process resources. | |
2123 | ||
2124 | Note: | |
2125 | Calling $(LREF wait) or $(LREF kill) with the resulting `Pid` is invalid. | |
2126 | */ | |
2127 | detached = 64, | |
2128 | ||
2129 | /** | |
2130 | By default, the $(LREF execute) and $(LREF executeShell) functions | |
2131 | will capture child processes' both stdout and stderr. This can be | |
2132 | undesirable if the standard output is to be processed or otherwise | |
2133 | used by the invoking program, as `execute`'s result would then | |
2134 | contain a mix of output and warning/error messages. | |
2135 | ||
2136 | Specify this flag when calling `execute` or `executeShell` to | |
2137 | cause invoked processes' stderr stream to be sent to $(REF stderr, | |
2138 | std,stdio), and only capture and return standard output. | |
2139 | ||
2140 | This flag has no effect on $(LREF spawnProcess) or $(LREF spawnShell). | |
2141 | */ | |
2142 | stderrPassThrough = 128, | |
2143 | } | |
2144 | Flags flags; /// ditto | |
b4c522fa | 2145 | |
5fee5ec3 IB |
2146 | /** |
2147 | For backwards compatibility, and cases when only flags need to | |
2148 | be specified in the `Config`, these allow building `Config` | |
2149 | instances using flag names only. | |
2150 | */ | |
2151 | enum Config none = Config.init; | |
2152 | enum Config newEnv = Config(Flags.newEnv); /// ditto | |
2153 | enum Config retainStdin = Config(Flags.retainStdin); /// ditto | |
2154 | enum Config retainStdout = Config(Flags.retainStdout); /// ditto | |
2155 | enum Config retainStderr = Config(Flags.retainStderr); /// ditto | |
2156 | enum Config suppressConsole = Config(Flags.suppressConsole); /// ditto | |
2157 | enum Config inheritFDs = Config(Flags.inheritFDs); /// ditto | |
2158 | enum Config detached = Config(Flags.detached); /// ditto | |
2159 | enum Config stderrPassThrough = Config(Flags.stderrPassThrough); /// ditto | |
2160 | Config opUnary(string op)() | |
2161 | if (is(typeof(mixin(op ~ q{flags})))) | |
2162 | { | |
2163 | return Config(mixin(op ~ q{flags})); | |
2164 | } /// ditto | |
2165 | Config opBinary(string op)(Config other) | |
2166 | if (is(typeof(mixin(q{flags} ~ op ~ q{other.flags})))) | |
2167 | { | |
2168 | return Config(mixin(q{flags} ~ op ~ q{other.flags})); | |
2169 | } /// ditto | |
2170 | Config opOpAssign(string op)(Config other) | |
2171 | if (is(typeof(mixin(q{flags} ~ op ~ q{=other.flags})))) | |
2172 | { | |
2173 | return Config(mixin(q{flags} ~ op ~ q{=other.flags})); | |
2174 | } /// ditto | |
b4c522fa | 2175 | |
5fee5ec3 IB |
2176 | version (StdDdoc) |
2177 | { | |
2178 | /** | |
2179 | A function that is called before `exec` in $(LREF spawnProcess). | |
2180 | It returns `true` if succeeded and otherwise returns `false`. | |
b4c522fa | 2181 | |
5fee5ec3 IB |
2182 | $(RED Warning: |
2183 | Please note that the code in this function must only use | |
2184 | async-signal-safe functions.) | |
b4c522fa | 2185 | |
5fee5ec3 IB |
2186 | On Windows, this member is not available. |
2187 | */ | |
2188 | bool function() nothrow @nogc @safe preExecFunction; | |
2189 | } | |
2190 | else version (Posix) | |
2191 | { | |
2192 | bool function() nothrow @nogc @safe preExecFunction; | |
2193 | } | |
b4c522fa IB |
2194 | } |
2195 | ||
5fee5ec3 IB |
2196 | // https://issues.dlang.org/show_bug.cgi?id=22125 |
2197 | @safe unittest | |
b4c522fa | 2198 | { |
5fee5ec3 IB |
2199 | Config c = Config.retainStdin; |
2200 | c |= Config.retainStdout; | |
2201 | c |= Config.retainStderr; | |
2202 | c &= ~Config.retainStderr; | |
2203 | assert(c == (Config.retainStdin | Config.retainStdout)); | |
b4c522fa IB |
2204 | } |
2205 | ||
5fee5ec3 IB |
2206 | /// A handle that corresponds to a spawned process. |
2207 | final class Pid | |
b4c522fa | 2208 | { |
5fee5ec3 IB |
2209 | /** |
2210 | The process ID number. | |
e19c6389 | 2211 | |
5fee5ec3 IB |
2212 | This is a number that uniquely identifies the process on the operating |
2213 | system, for at least as long as the process is running. Once $(LREF wait) | |
2214 | has been called on the $(LREF Pid), this method will return an | |
2215 | invalid (negative) process ID. | |
2216 | */ | |
2217 | @property int processID() const @safe pure nothrow | |
2218 | { | |
2219 | return _processID; | |
2220 | } | |
b4c522fa | 2221 | |
5fee5ec3 IB |
2222 | /** |
2223 | An operating system handle to the process. | |
2224 | ||
2225 | This handle is used to specify the process in OS-specific APIs. | |
2226 | On POSIX, this function returns a `core.sys.posix.sys.types.pid_t` | |
2227 | with the same value as $(LREF Pid.processID), while on Windows it returns | |
2228 | a `core.sys.windows.windows.HANDLE`. | |
2229 | ||
2230 | Once $(LREF wait) has been called on the $(LREF Pid), this method | |
2231 | will return an invalid handle. | |
2232 | */ | |
2233 | // Note: Since HANDLE is a reference, this function cannot be const. | |
2234 | version (Windows) | |
2235 | @property HANDLE osHandle() @nogc @safe pure nothrow | |
2236 | { | |
2237 | return _handle; | |
2238 | } | |
2239 | else version (Posix) | |
2240 | @property pid_t osHandle() @nogc @safe pure nothrow | |
2241 | { | |
2242 | return _processID; | |
2243 | } | |
2244 | ||
2245 | private: | |
2246 | /* | |
2247 | Pid.performWait() does the dirty work for wait() and nonBlockingWait(). | |
2248 | ||
2249 | If block == true, this function blocks until the process terminates, | |
2250 | sets _processID to terminated, and returns the exit code or terminating | |
2251 | signal as described in the wait() documentation. | |
2252 | ||
2253 | If block == false, this function returns immediately, regardless | |
2254 | of the status of the process. If the process has terminated, the | |
2255 | function has the exact same effect as the blocking version. If not, | |
2256 | it returns 0 and does not modify _processID. | |
2257 | */ | |
2258 | version (Posix) | |
2259 | int performWait(bool block) @trusted | |
2260 | { | |
2261 | import std.exception : enforce; | |
2262 | enforce!ProcessException(owned, "Can't wait on a detached process"); | |
2263 | if (_processID == terminated) return _exitCode; | |
2264 | int exitCode; | |
2265 | while (true) | |
2266 | { | |
2267 | int status; | |
2268 | auto check = waitpid(_processID, &status, block ? 0 : WNOHANG); | |
2269 | if (check == -1) | |
2270 | { | |
2271 | if (errno == ECHILD) | |
2272 | { | |
2273 | throw new ProcessException( | |
2274 | "Process does not exist or is not a child process."); | |
2275 | } | |
2276 | else | |
2277 | { | |
2278 | // waitpid() was interrupted by a signal. We simply | |
2279 | // restart it. | |
2280 | assert(errno == EINTR); | |
2281 | continue; | |
2282 | } | |
2283 | } | |
2284 | if (!block && check == 0) return 0; | |
2285 | if (WIFEXITED(status)) | |
2286 | { | |
2287 | exitCode = WEXITSTATUS(status); | |
2288 | break; | |
2289 | } | |
2290 | else if (WIFSIGNALED(status)) | |
2291 | { | |
2292 | exitCode = -WTERMSIG(status); | |
2293 | break; | |
2294 | } | |
2295 | // We check again whether the call should be blocking, | |
2296 | // since we don't care about other status changes besides | |
2297 | // "exited" and "terminated by signal". | |
2298 | if (!block) return 0; | |
2299 | ||
2300 | // Process has stopped, but not terminated, so we continue waiting. | |
2301 | } | |
2302 | // Mark Pid as terminated, and cache and return exit code. | |
2303 | _processID = terminated; | |
2304 | _exitCode = exitCode; | |
2305 | return exitCode; | |
2306 | } | |
2307 | else version (Windows) | |
2308 | { | |
2309 | int performWait(const bool block, const DWORD timeout = INFINITE) @trusted | |
2310 | { | |
2311 | import std.exception : enforce; | |
2312 | enforce!ProcessException(owned, "Can't wait on a detached process"); | |
2313 | if (_processID == terminated) return _exitCode; | |
2314 | assert(_handle != INVALID_HANDLE_VALUE); | |
2315 | if (block) | |
2316 | { | |
2317 | auto result = WaitForSingleObject(_handle, timeout); | |
2318 | if (result != WAIT_OBJECT_0) | |
2319 | { | |
2320 | // Wait time exceeded `timeout` milliseconds? | |
2321 | if (result == WAIT_TIMEOUT && timeout != INFINITE) | |
2322 | return 0; | |
2323 | ||
2324 | throw ProcessException.newFromLastError("Wait failed."); | |
2325 | } | |
2326 | } | |
2327 | if (!GetExitCodeProcess(_handle, cast(LPDWORD)&_exitCode)) | |
2328 | throw ProcessException.newFromLastError(); | |
2329 | if (!block && _exitCode == STILL_ACTIVE) return 0; | |
2330 | CloseHandle(_handle); | |
2331 | _handle = INVALID_HANDLE_VALUE; | |
2332 | _processID = terminated; | |
2333 | return _exitCode; | |
2334 | } | |
2335 | ||
2336 | int performWait(Duration timeout) @safe | |
2337 | { | |
2338 | import std.exception : enforce; | |
2339 | const msecs = timeout.total!"msecs"; | |
2340 | ||
2341 | // Limit this implementation the maximum wait time offered by | |
2342 | // WaitForSingleObject. One could theoretically break up larger | |
2343 | // durations into multiple waits but (DWORD.max - 1).msecs | |
2344 | // (> 7 weeks, 17 hours) should be enough for the usual case. | |
2345 | // DWORD.max is reserved for INFINITE | |
2346 | enforce!ProcessException(msecs < DWORD.max, "Timeout exceeds maximum wait time!"); | |
2347 | return performWait(true, cast(DWORD) msecs); | |
2348 | } | |
2349 | ||
2350 | ~this() | |
2351 | { | |
2352 | if (_handle != INVALID_HANDLE_VALUE) | |
2353 | { | |
2354 | CloseHandle(_handle); | |
2355 | _handle = INVALID_HANDLE_VALUE; | |
2356 | } | |
2357 | } | |
2358 | } | |
2359 | ||
2360 | // Special values for _processID. | |
2361 | enum invalid = -1, terminated = -2; | |
2362 | ||
2363 | // OS process ID number. Only nonnegative IDs correspond to | |
2364 | // running processes. | |
2365 | int _processID = invalid; | |
2366 | ||
2367 | // Exit code cached by wait(). This is only expected to hold a | |
2368 | // sensible value if _processID == terminated. | |
2369 | int _exitCode; | |
2370 | ||
2371 | // Whether the process can be waited for by wait() for or killed by kill(). | |
2372 | // False if process was started as detached. True otherwise. | |
2373 | bool owned; | |
2374 | ||
2375 | // Pids are only meant to be constructed inside this module, so | |
2376 | // we make the constructor private. | |
2377 | version (Windows) | |
2378 | { | |
2379 | HANDLE _handle = INVALID_HANDLE_VALUE; | |
2380 | this(int pid, HANDLE handle) @safe pure nothrow | |
2381 | { | |
2382 | _processID = pid; | |
2383 | _handle = handle; | |
2384 | this.owned = true; | |
2385 | } | |
2386 | this(int pid) @safe pure nothrow | |
2387 | { | |
2388 | _processID = pid; | |
2389 | this.owned = false; | |
2390 | } | |
2391 | } | |
2392 | else | |
2393 | { | |
2394 | this(int id, bool owned) @safe pure nothrow | |
2395 | { | |
2396 | _processID = id; | |
2397 | this.owned = owned; | |
2398 | } | |
2399 | } | |
2400 | } | |
2401 | ||
2402 | ||
2403 | /** | |
2404 | Waits for the process associated with `pid` to terminate, and returns | |
2405 | its exit status. | |
2406 | ||
2407 | In general one should always _wait for child processes to terminate | |
2408 | before exiting the parent process unless the process was spawned as detached | |
2409 | (that was spawned with `Config.detached` flag). | |
2410 | Otherwise, they may become "$(HTTP en.wikipedia.org/wiki/Zombie_process,zombies)" | |
2411 | – processes that are defunct, yet still occupy a slot in the OS process table. | |
2412 | You should not and must not wait for detached processes, since you don't own them. | |
2413 | ||
2414 | If the process has already terminated, this function returns directly. | |
2415 | The exit code is cached, so that if wait() is called multiple times on | |
2416 | the same $(LREF Pid) it will always return the same value. | |
2417 | ||
2418 | POSIX_specific: | |
2419 | If the process is terminated by a signal, this function returns a | |
2420 | negative number whose absolute value is the signal number. | |
2421 | Since POSIX restricts normal exit codes to the range 0-255, a | |
2422 | negative return value will always indicate termination by signal. | |
2423 | Signal codes are defined in the `core.sys.posix.signal` module | |
2424 | (which corresponds to the `signal.h` POSIX header). | |
2425 | ||
2426 | Throws: | |
2427 | $(LREF ProcessException) on failure or on attempt to wait for detached process. | |
2428 | ||
2429 | Example: | |
2430 | See the $(LREF spawnProcess) documentation. | |
2431 | ||
2432 | See_also: | |
2433 | $(LREF tryWait), for a non-blocking function. | |
2434 | */ | |
2435 | int wait(Pid pid) @safe | |
2436 | { | |
2437 | assert(pid !is null, "Called wait on a null Pid."); | |
2438 | return pid.performWait(true); | |
2439 | } | |
2440 | ||
2441 | ||
2442 | @system unittest // Pid and wait() | |
2443 | { | |
2444 | version (Windows) TestScript prog = "exit %~1"; | |
2445 | else version (Posix) TestScript prog = "exit $1"; | |
2446 | assert(wait(spawnProcess([prog.path, "0"])) == 0); | |
2447 | assert(wait(spawnProcess([prog.path, "123"])) == 123); | |
2448 | auto pid = spawnProcess([prog.path, "10"]); | |
2449 | assert(pid.processID > 0); | |
2450 | version (Windows) assert(pid.osHandle != INVALID_HANDLE_VALUE); | |
2451 | else version (Posix) assert(pid.osHandle == pid.processID); | |
2452 | assert(wait(pid) == 10); | |
2453 | assert(wait(pid) == 10); // cached exit code | |
2454 | assert(pid.processID < 0); | |
2455 | version (Windows) assert(pid.osHandle == INVALID_HANDLE_VALUE); | |
2456 | else version (Posix) assert(pid.osHandle < 0); | |
2457 | } | |
2458 | ||
2459 | private import std.typecons : Tuple; | |
2460 | ||
2461 | /** | |
2462 | Waits until either the process associated with `pid` terminates or the | |
2463 | elapsed time exceeds the given timeout. | |
2464 | ||
2465 | If the process terminates within the given duration it behaves exactly like | |
2466 | `wait`, except that it returns a tuple `(true, exit code)`. | |
2467 | ||
2468 | If the process does not terminate within the given duration it will stop | |
2469 | waiting and return `(false, 0).` | |
2470 | ||
2471 | The timeout may not exceed `(uint.max - 1).msecs` (~ 7 weeks, 17 hours). | |
2472 | ||
2473 | $(BLUE This function is Windows-Only.) | |
2474 | ||
2475 | Returns: | |
2476 | An $(D std.typecons.Tuple!(bool, "terminated", int, "status")). | |
2477 | ||
2478 | Throws: | |
2479 | $(LREF ProcessException) on failure or on attempt to wait for detached process. | |
2480 | ||
2481 | Example: | |
2482 | See the $(LREF spawnProcess) documentation. | |
2483 | ||
2484 | See_also: | |
2485 | $(LREF wait), for a blocking function without timeout. | |
2486 | $(LREF tryWait), for a non-blocking function without timeout. | |
2487 | */ | |
2488 | version (StdDdoc) | |
2489 | Tuple!(bool, "terminated", int, "status") waitTimeout(Pid pid, Duration timeout) @safe; | |
2490 | ||
2491 | else version (Windows) | |
2492 | Tuple!(bool, "terminated", int, "status") waitTimeout(Pid pid, Duration timeout) @safe | |
2493 | { | |
2494 | assert(pid !is null, "Called wait on a null Pid."); | |
2495 | auto code = pid.performWait(timeout); | |
2496 | return typeof(return)(pid._processID == Pid.terminated, code); | |
2497 | } | |
2498 | ||
2499 | version (Windows) | |
2500 | @system unittest // Pid and waitTimeout() | |
2501 | { | |
2502 | import std.exception : collectException; | |
2503 | import std.typecons : tuple; | |
2504 | ||
2505 | TestScript prog = ":Loop\ngoto Loop;"; | |
2506 | auto pid = spawnProcess(prog.path); | |
2507 | ||
2508 | // Doesn't block longer than one second | |
2509 | assert(waitTimeout(pid, 1.seconds) == tuple(false, 0)); | |
2510 | ||
2511 | kill(pid); | |
2512 | assert(waitTimeout(pid, 1.seconds) == tuple(true, 1)); // exit 1 because the process is killed | |
2513 | ||
2514 | // Rejects timeouts exceeding the Windows API capabilities | |
2515 | const dur = DWORD.max.msecs; | |
2516 | const ex = collectException!ProcessException(waitTimeout(pid, dur)); | |
2517 | assert(ex); | |
2518 | assert(ex.msg == "Timeout exceeds maximum wait time!"); | |
2519 | } | |
2520 | ||
2521 | /** | |
2522 | A non-blocking version of $(LREF wait). | |
2523 | ||
2524 | If the process associated with `pid` has already terminated, | |
2525 | `tryWait` has the exact same effect as `wait`. | |
2526 | In this case, it returns a tuple where the `terminated` field | |
2527 | is set to `true` and the `status` field has the same | |
2528 | interpretation as the return value of `wait`. | |
2529 | ||
2530 | If the process has $(I not) yet terminated, this function differs | |
2531 | from `wait` in that does not wait for this to happen, but instead | |
2532 | returns immediately. The `terminated` field of the returned | |
2533 | tuple will then be set to `false`, while the `status` field | |
2534 | will always be 0 (zero). `wait` or `tryWait` should then be | |
2535 | called again on the same `Pid` at some later time; not only to | |
2536 | get the exit code, but also to avoid the process becoming a "zombie" | |
2537 | when it finally terminates. (See $(LREF wait) for details). | |
2538 | ||
2539 | Returns: | |
2540 | An $(D std.typecons.Tuple!(bool, "terminated", int, "status")). | |
2541 | ||
2542 | Throws: | |
2543 | $(LREF ProcessException) on failure or on attempt to wait for detached process. | |
2544 | ||
2545 | Example: | |
2546 | --- | |
2547 | auto pid = spawnProcess("dmd myapp.d"); | |
2548 | scope(exit) wait(pid); | |
2549 | ... | |
2550 | auto dmd = tryWait(pid); | |
2551 | if (dmd.terminated) | |
2552 | { | |
2553 | if (dmd.status == 0) writeln("Compilation succeeded!"); | |
2554 | else writeln("Compilation failed"); | |
2555 | } | |
2556 | else writeln("Still compiling..."); | |
2557 | ... | |
2558 | --- | |
2559 | Note that in this example, the first `wait` call will have no | |
2560 | effect if the process has already terminated by the time `tryWait` | |
2561 | is called. In the opposite case, however, the `scope` statement | |
2562 | ensures that we always wait for the process if it hasn't terminated | |
2563 | by the time we reach the end of the scope. | |
2564 | */ | |
2565 | auto tryWait(Pid pid) @safe | |
2566 | { | |
2567 | import std.typecons : Tuple; | |
2568 | assert(pid !is null, "Called tryWait on a null Pid."); | |
2569 | auto code = pid.performWait(false); | |
2570 | return Tuple!(bool, "terminated", int, "status")(pid._processID == Pid.terminated, code); | |
2571 | } | |
2572 | // unittest: This function is tested together with kill() below. | |
2573 | ||
2574 | ||
2575 | /** | |
2576 | Attempts to terminate the process associated with `pid`. | |
2577 | ||
2578 | The effect of this function, as well as the meaning of `codeOrSignal`, | |
2579 | is highly platform dependent. Details are given below. Common to all | |
2580 | platforms is that this function only $(I initiates) termination of the process, | |
2581 | and returns immediately. It does not wait for the process to end, | |
2582 | nor does it guarantee that the process does in fact get terminated. | |
2583 | ||
2584 | Always call $(LREF wait) to wait for a process to complete, even if `kill` | |
2585 | has been called on it. | |
2586 | ||
2587 | Windows_specific: | |
2588 | The process will be | |
2589 | $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686714%28v=vs.100%29.aspx, | |
2590 | forcefully and abruptly terminated). If `codeOrSignal` is specified, it | |
2591 | must be a nonnegative number which will be used as the exit code of the process. | |
2592 | If not, the process wil exit with code 1. Do not use $(D codeOrSignal = 259), | |
2593 | as this is a special value (aka. $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683189.aspx,STILL_ACTIVE)) | |
2594 | used by Windows to signal that a process has in fact $(I not) terminated yet. | |
2595 | --- | |
2596 | auto pid = spawnProcess("some_app"); | |
2597 | kill(pid, 10); | |
2598 | assert(wait(pid) == 10); | |
2599 | --- | |
2600 | ||
2601 | POSIX_specific: | |
2602 | A $(LINK2 http://en.wikipedia.org/wiki/Unix_signal,signal) will be sent to | |
2603 | the process, whose value is given by `codeOrSignal`. Depending on the | |
2604 | signal sent, this may or may not terminate the process. Symbolic constants | |
2605 | for various $(LINK2 http://en.wikipedia.org/wiki/Unix_signal#POSIX_signals, | |
2606 | POSIX signals) are defined in `core.sys.posix.signal`, which corresponds to the | |
2607 | $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html, | |
2608 | `signal.h` POSIX header). If `codeOrSignal` is omitted, the | |
2609 | `SIGTERM` signal will be sent. (This matches the behaviour of the | |
2610 | $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/kill.html, | |
2611 | `_kill`) shell command.) | |
2612 | --- | |
2613 | import core.sys.posix.signal : SIGKILL; | |
2614 | auto pid = spawnProcess("some_app"); | |
2615 | kill(pid, SIGKILL); | |
2616 | assert(wait(pid) == -SIGKILL); // Negative return value on POSIX! | |
2617 | --- | |
2618 | ||
2619 | Throws: | |
2620 | $(LREF ProcessException) on error (e.g. if codeOrSignal is invalid). | |
2621 | or on attempt to kill detached process. | |
2622 | Note that failure to terminate the process is considered a "normal" | |
2623 | outcome, not an error.$(BR) | |
2624 | */ | |
2625 | void kill(Pid pid) | |
2626 | { | |
2627 | version (Windows) kill(pid, 1); | |
2628 | else version (Posix) | |
2629 | { | |
2630 | import core.sys.posix.signal : SIGTERM; | |
2631 | kill(pid, SIGTERM); | |
2632 | } | |
2633 | } | |
2634 | ||
2635 | /// ditto | |
2636 | void kill(Pid pid, int codeOrSignal) | |
2637 | { | |
2638 | import std.exception : enforce; | |
2639 | enforce!ProcessException(pid.owned, "Can't kill detached process"); | |
2640 | version (Windows) | |
2641 | { | |
2642 | if (codeOrSignal < 0) throw new ProcessException("Invalid exit code"); | |
2643 | // On Windows, TerminateProcess() appears to terminate the | |
2644 | // *current* process if it is passed an invalid handle... | |
2645 | if (pid.osHandle == INVALID_HANDLE_VALUE) | |
2646 | throw new ProcessException("Invalid process handle"); | |
2647 | if (!TerminateProcess(pid.osHandle, codeOrSignal)) | |
2648 | throw ProcessException.newFromLastError(); | |
2649 | } | |
2650 | else version (Posix) | |
2651 | { | |
2652 | import core.sys.posix.signal : kill; | |
2653 | if (kill(pid.osHandle, codeOrSignal) == -1) | |
2654 | throw ProcessException.newFromErrno(); | |
2655 | } | |
2656 | } | |
2657 | ||
2658 | @system unittest // tryWait() and kill() | |
2659 | { | |
2660 | import core.thread; | |
2661 | import std.exception : assertThrown; | |
2662 | // The test script goes into an infinite loop. | |
2663 | version (Windows) | |
2664 | { | |
2665 | TestScript prog = ":loop | |
2666 | goto loop"; | |
2667 | } | |
2668 | else version (Posix) | |
2669 | { | |
2670 | import core.sys.posix.signal : SIGTERM, SIGKILL; | |
2671 | TestScript prog = "while true; do sleep 1; done"; | |
2672 | } | |
2673 | auto pid = spawnProcess(prog.path); | |
2674 | // Android appears to automatically kill sleeping processes very quickly, | |
2675 | // so shorten the wait before killing here. | |
2676 | version (Android) | |
2677 | Thread.sleep(dur!"msecs"(5)); | |
2678 | else | |
2679 | Thread.sleep(dur!"msecs"(500)); | |
2680 | kill(pid); | |
2681 | version (Windows) assert(wait(pid) == 1); | |
2682 | else version (Posix) assert(wait(pid) == -SIGTERM); | |
2683 | ||
2684 | pid = spawnProcess(prog.path); | |
2685 | Thread.sleep(dur!"msecs"(500)); | |
2686 | auto s = tryWait(pid); | |
2687 | assert(!s.terminated && s.status == 0); | |
2688 | assertThrown!ProcessException(kill(pid, -123)); // Negative code not allowed. | |
2689 | version (Windows) kill(pid, 123); | |
2690 | else version (Posix) kill(pid, SIGKILL); | |
2691 | do { s = tryWait(pid); } while (!s.terminated); | |
2692 | version (Windows) assert(s.status == 123); | |
2693 | else version (Posix) assert(s.status == -SIGKILL); | |
2694 | assertThrown!ProcessException(kill(pid)); | |
2695 | } | |
2696 | ||
2697 | @system unittest // wait() and kill() detached process | |
2698 | { | |
2699 | import core.thread; | |
2700 | import std.exception : assertThrown; | |
2701 | TestScript prog = "exit 0"; | |
2702 | auto pid = spawnProcess([prog.path], null, Config.detached); | |
2703 | /* | |
2704 | This sleep is needed because we can't wait() for detached process to end | |
2705 | and therefore TestScript destructor may run at the same time as /bin/sh tries to start the script. | |
2706 | This leads to the annoying message like "/bin/sh: 0: Can't open /tmp/std.process temporary file" to appear when running tests. | |
2707 | It does not happen in unittests with non-detached processes because we always wait() for them to finish. | |
2708 | */ | |
2709 | Thread.sleep(500.msecs); | |
2710 | assert(!pid.owned); | |
2711 | version (Windows) assert(pid.osHandle == INVALID_HANDLE_VALUE); | |
2712 | assertThrown!ProcessException(wait(pid)); | |
2713 | assertThrown!ProcessException(kill(pid)); | |
2714 | } | |
2715 | ||
2716 | ||
2717 | /** | |
2718 | Creates a unidirectional _pipe. | |
2719 | ||
2720 | Data is written to one end of the _pipe and read from the other. | |
2721 | --- | |
2722 | auto p = pipe(); | |
2723 | p.writeEnd.writeln("Hello World"); | |
2724 | p.writeEnd.flush(); | |
2725 | assert(p.readEnd.readln().chomp() == "Hello World"); | |
2726 | --- | |
2727 | Pipes can, for example, be used for interprocess communication | |
2728 | by spawning a new process and passing one end of the _pipe to | |
2729 | the child, while the parent uses the other end. | |
2730 | (See also $(LREF pipeProcess) and $(LREF pipeShell) for an easier | |
2731 | way of doing this.) | |
2732 | --- | |
2733 | // Use cURL to download the dlang.org front page, pipe its | |
2734 | // output to grep to extract a list of links to ZIP files, | |
2735 | // and write the list to the file "D downloads.txt": | |
2736 | auto p = pipe(); | |
2737 | auto outFile = File("D downloads.txt", "w"); | |
2738 | auto cpid = spawnProcess(["curl", "http://dlang.org/download.html"], | |
2739 | std.stdio.stdin, p.writeEnd); | |
2740 | scope(exit) wait(cpid); | |
2741 | auto gpid = spawnProcess(["grep", "-o", `http://\S*\.zip`], | |
2742 | p.readEnd, outFile); | |
2743 | scope(exit) wait(gpid); | |
2744 | --- | |
2745 | ||
2746 | Returns: | |
2747 | A $(LREF Pipe) object that corresponds to the created _pipe. | |
2748 | ||
2749 | Throws: | |
2750 | $(REF StdioException, std,stdio) on failure. | |
2751 | */ | |
2752 | version (Posix) | |
2753 | Pipe pipe() @trusted //TODO: @safe | |
2754 | { | |
2755 | import core.sys.posix.stdio : fdopen; | |
2756 | int[2] fds; | |
2757 | if (core.sys.posix.unistd.pipe(fds) != 0) | |
2758 | throw new StdioException("Unable to create pipe"); | |
2759 | Pipe p; | |
2760 | auto readFP = fdopen(fds[0], "r"); | |
2761 | if (readFP == null) | |
2762 | throw new StdioException("Cannot open read end of pipe"); | |
2763 | p._read = File(readFP, null); | |
2764 | auto writeFP = fdopen(fds[1], "w"); | |
2765 | if (writeFP == null) | |
2766 | throw new StdioException("Cannot open write end of pipe"); | |
2767 | p._write = File(writeFP, null); | |
2768 | return p; | |
2769 | } | |
2770 | else version (Windows) | |
2771 | Pipe pipe() @trusted //TODO: @safe | |
2772 | { | |
2773 | // use CreatePipe to create an anonymous pipe | |
2774 | HANDLE readHandle; | |
2775 | HANDLE writeHandle; | |
2776 | if (!CreatePipe(&readHandle, &writeHandle, null, 0)) | |
2777 | { | |
2778 | throw new StdioException( | |
2779 | "Error creating pipe (" ~ sysErrorString(GetLastError()) ~ ')', | |
2780 | 0); | |
2781 | } | |
2782 | ||
2783 | scope(failure) | |
2784 | { | |
2785 | CloseHandle(readHandle); | |
2786 | CloseHandle(writeHandle); | |
2787 | } | |
b4c522fa | 2788 | |
5fee5ec3 | 2789 | try |
b4c522fa | 2790 | { |
5fee5ec3 IB |
2791 | Pipe p; |
2792 | p._read .windowsHandleOpen(readHandle , "r"); | |
2793 | p._write.windowsHandleOpen(writeHandle, "a"); | |
2794 | return p; | |
2795 | } | |
2796 | catch (Exception e) | |
2797 | { | |
2798 | throw new StdioException("Error attaching pipe (" ~ e.msg ~ ")", | |
2799 | 0); | |
2800 | } | |
2801 | } | |
b4c522fa | 2802 | |
5fee5ec3 IB |
2803 | |
2804 | /// An interface to a pipe created by the $(LREF pipe) function. | |
2805 | struct Pipe | |
2806 | { | |
2807 | /// The read end of the pipe. | |
2808 | @property File readEnd() @safe nothrow { return _read; } | |
2809 | ||
2810 | ||
2811 | /// The write end of the pipe. | |
2812 | @property File writeEnd() @safe nothrow { return _write; } | |
2813 | ||
2814 | ||
2815 | /** | |
2816 | Closes both ends of the pipe. | |
2817 | ||
2818 | Normally it is not necessary to do this manually, as $(REF File, std,stdio) | |
2819 | objects are automatically closed when there are no more references | |
2820 | to them. | |
2821 | ||
2822 | Note that if either end of the pipe has been passed to a child process, | |
2823 | it will only be closed in the parent process. (What happens in the | |
2824 | child process is platform dependent.) | |
2825 | ||
2826 | Throws: | |
2827 | $(REF ErrnoException, std,exception) if an error occurs. | |
2828 | */ | |
2829 | void close() @safe | |
2830 | { | |
2831 | _read.close(); | |
2832 | _write.close(); | |
b4c522fa | 2833 | } |
b4c522fa | 2834 | |
5fee5ec3 IB |
2835 | private: |
2836 | File _read, _write; | |
b4c522fa IB |
2837 | } |
2838 | ||
2839 | @system unittest | |
2840 | { | |
2841 | import std.string; | |
5fee5ec3 IB |
2842 | auto p = pipe(); |
2843 | p.writeEnd.writeln("Hello World"); | |
2844 | p.writeEnd.flush(); | |
2845 | assert(p.readEnd.readln().chomp() == "Hello World"); | |
2846 | p.close(); | |
2847 | assert(!p.readEnd.isOpen); | |
2848 | assert(!p.writeEnd.isOpen); | |
b4c522fa IB |
2849 | } |
2850 | ||
5fee5ec3 IB |
2851 | |
2852 | /** | |
2853 | Starts a new process, creating pipes to redirect its standard | |
2854 | input, output and/or error streams. | |
2855 | ||
2856 | `pipeProcess` and `pipeShell` are convenient wrappers around | |
2857 | $(LREF spawnProcess) and $(LREF spawnShell), respectively, and | |
2858 | automate the task of redirecting one or more of the child process' | |
2859 | standard streams through pipes. Like the functions they wrap, | |
2860 | these functions return immediately, leaving the child process to | |
2861 | execute in parallel with the invoking process. It is recommended | |
2862 | to always call $(LREF wait) on the returned $(LREF ProcessPipes.pid), | |
2863 | as detailed in the documentation for `wait`. | |
2864 | ||
2865 | The `args`/`program`/`command`, `env` and `config` | |
2866 | parameters are forwarded straight to the underlying spawn functions, | |
2867 | and we refer to their documentation for details. | |
2868 | ||
2869 | Params: | |
2870 | args = An array which contains the program name as the zeroth element | |
2871 | and any command-line arguments in the following elements. | |
2872 | (See $(LREF spawnProcess) for details.) | |
2873 | program = The program name, $(I without) command-line arguments. | |
2874 | (See $(LREF spawnProcess) for details.) | |
2875 | command = A shell command which is passed verbatim to the command | |
2876 | interpreter. (See $(LREF spawnShell) for details.) | |
2877 | redirect = Flags that determine which streams are redirected, and | |
2878 | how. See $(LREF Redirect) for an overview of available | |
2879 | flags. | |
2880 | env = Additional environment variables for the child process. | |
2881 | (See $(LREF spawnProcess) for details.) | |
2882 | config = Flags that control process creation. See $(LREF Config) | |
2883 | for an overview of available flags, and note that the | |
2884 | `retainStd...` flags have no effect in this function. | |
2885 | workDir = The working directory for the new process. | |
2886 | By default the child process inherits the parent's working | |
2887 | directory. | |
2888 | shellPath = The path to the shell to use to run the specified program. | |
2889 | By default this is $(LREF nativeShell). | |
2890 | ||
2891 | Returns: | |
2892 | A $(LREF ProcessPipes) object which contains $(REF File, std,stdio) | |
2893 | handles that communicate with the redirected streams of the child | |
2894 | process, along with a $(LREF Pid) object that corresponds to the | |
2895 | spawned process. | |
2896 | ||
2897 | Throws: | |
2898 | $(LREF ProcessException) on failure to start the process.$(BR) | |
2899 | $(REF StdioException, std,stdio) on failure to redirect any of the streams.$(BR) | |
2900 | ||
2901 | Example: | |
2902 | --- | |
2903 | // my_application writes to stdout and might write to stderr | |
2904 | auto pipes = pipeProcess("my_application", Redirect.stdout | Redirect.stderr); | |
2905 | scope(exit) wait(pipes.pid); | |
2906 | ||
2907 | // Store lines of output. | |
2908 | string[] output; | |
2909 | foreach (line; pipes.stdout.byLine) output ~= line.idup; | |
2910 | ||
2911 | // Store lines of errors. | |
2912 | string[] errors; | |
2913 | foreach (line; pipes.stderr.byLine) errors ~= line.idup; | |
2914 | ||
2915 | ||
2916 | // sendmail expects to read from stdin | |
2917 | pipes = pipeProcess(["/usr/bin/sendmail", "-t"], Redirect.stdin); | |
2918 | pipes.stdin.writeln("To: you"); | |
2919 | pipes.stdin.writeln("From: me"); | |
2920 | pipes.stdin.writeln("Subject: dlang"); | |
2921 | pipes.stdin.writeln(""); | |
2922 | pipes.stdin.writeln(message); | |
2923 | ||
2924 | // a single period tells sendmail we are finished | |
2925 | pipes.stdin.writeln("."); | |
2926 | ||
2927 | // but at this point sendmail might not see it, we need to flush | |
2928 | pipes.stdin.flush(); | |
2929 | ||
2930 | // sendmail happens to exit on ".", but some you have to close the file: | |
2931 | pipes.stdin.close(); | |
2932 | ||
2933 | // otherwise this wait will wait forever | |
2934 | wait(pipes.pid); | |
2935 | ||
2936 | --- | |
2937 | */ | |
2938 | ProcessPipes pipeProcess(scope const(char[])[] args, | |
2939 | Redirect redirect = Redirect.all, | |
2940 | const string[string] env = null, | |
2941 | Config config = Config.none, | |
2942 | scope const(char)[] workDir = null) | |
2943 | @safe | |
b4c522fa | 2944 | { |
5fee5ec3 | 2945 | return pipeProcessImpl!spawnProcess(args, redirect, env, config, workDir); |
b4c522fa IB |
2946 | } |
2947 | ||
5fee5ec3 IB |
2948 | /// ditto |
2949 | ProcessPipes pipeProcess(scope const(char)[] program, | |
2950 | Redirect redirect = Redirect.all, | |
2951 | const string[string] env = null, | |
2952 | Config config = Config.none, | |
2953 | scope const(char)[] workDir = null) | |
2954 | @safe | |
e19c6389 | 2955 | { |
5fee5ec3 IB |
2956 | return pipeProcessImpl!spawnProcess(program, redirect, env, config, workDir); |
2957 | } | |
e19c6389 | 2958 | |
5fee5ec3 IB |
2959 | /// ditto |
2960 | ProcessPipes pipeShell(scope const(char)[] command, | |
2961 | Redirect redirect = Redirect.all, | |
2962 | const string[string] env = null, | |
2963 | Config config = Config.none, | |
2964 | scope const(char)[] workDir = null, | |
2965 | string shellPath = nativeShell) | |
2966 | @safe | |
2967 | { | |
2968 | return pipeProcessImpl!spawnShell(command, | |
2969 | redirect, | |
2970 | env, | |
2971 | config, | |
2972 | workDir, | |
2973 | shellPath); | |
e19c6389 IB |
2974 | } |
2975 | ||
5fee5ec3 IB |
2976 | // Implementation of the pipeProcess() family of functions. |
2977 | private ProcessPipes pipeProcessImpl(alias spawnFunc, Cmd, ExtraSpawnFuncArgs...) | |
2978 | (scope Cmd command, | |
2979 | Redirect redirectFlags, | |
2980 | const string[string] env = null, | |
2981 | Config config = Config.none, | |
2982 | scope const(char)[] workDir = null, | |
2983 | ExtraSpawnFuncArgs extraArgs = ExtraSpawnFuncArgs.init) | |
2984 | @trusted //TODO: @safe | |
b4c522fa | 2985 | { |
5fee5ec3 IB |
2986 | File childStdin, childStdout, childStderr; |
2987 | ProcessPipes pipes; | |
2988 | pipes._redirectFlags = redirectFlags; | |
2989 | ||
2990 | if (redirectFlags & Redirect.stdin) | |
2991 | { | |
2992 | auto p = pipe(); | |
2993 | childStdin = p.readEnd; | |
2994 | pipes._stdin = p.writeEnd; | |
2995 | } | |
2996 | else | |
2997 | { | |
2998 | childStdin = std.stdio.stdin; | |
2999 | } | |
3000 | ||
3001 | if (redirectFlags & Redirect.stdout) | |
3002 | { | |
3003 | if ((redirectFlags & Redirect.stdoutToStderr) != 0) | |
3004 | throw new StdioException("Cannot create pipe for stdout AND " | |
3005 | ~"redirect it to stderr", 0); | |
3006 | auto p = pipe(); | |
3007 | childStdout = p.writeEnd; | |
3008 | pipes._stdout = p.readEnd; | |
3009 | } | |
3010 | else | |
b4c522fa | 3011 | { |
5fee5ec3 | 3012 | childStdout = std.stdio.stdout; |
b4c522fa | 3013 | } |
b4c522fa | 3014 | |
5fee5ec3 | 3015 | if (redirectFlags & Redirect.stderr) |
b4c522fa | 3016 | { |
5fee5ec3 IB |
3017 | if ((redirectFlags & Redirect.stderrToStdout) != 0) |
3018 | throw new StdioException("Cannot create pipe for stderr AND " | |
3019 | ~"redirect it to stdout", 0); | |
3020 | auto p = pipe(); | |
3021 | childStderr = p.writeEnd; | |
3022 | pipes._stderr = p.readEnd; | |
b4c522fa | 3023 | } |
5fee5ec3 | 3024 | else |
b4c522fa | 3025 | { |
5fee5ec3 | 3026 | childStderr = std.stdio.stderr; |
b4c522fa IB |
3027 | } |
3028 | ||
5fee5ec3 | 3029 | if (redirectFlags & Redirect.stdoutToStderr) |
b4c522fa | 3030 | { |
5fee5ec3 IB |
3031 | if (redirectFlags & Redirect.stderrToStdout) |
3032 | { | |
3033 | // We know that neither of the other options have been | |
3034 | // set, so we assign the std.stdio.std* streams directly. | |
3035 | childStdout = std.stdio.stderr; | |
3036 | childStderr = std.stdio.stdout; | |
3037 | } | |
3038 | else | |
3039 | { | |
3040 | childStdout = childStderr; | |
3041 | } | |
3042 | } | |
3043 | else if (redirectFlags & Redirect.stderrToStdout) | |
3044 | { | |
3045 | childStderr = childStdout; | |
b4c522fa | 3046 | } |
b4c522fa | 3047 | |
5fee5ec3 IB |
3048 | config.flags &= ~(Config.Flags.retainStdin | Config.Flags.retainStdout | Config.Flags.retainStderr); |
3049 | pipes._pid = spawnFunc(command, childStdin, childStdout, childStderr, | |
3050 | env, config, workDir, extraArgs); | |
3051 | return pipes; | |
b4c522fa IB |
3052 | } |
3053 | ||
b4c522fa | 3054 | |
5fee5ec3 IB |
3055 | /** |
3056 | Flags that can be passed to $(LREF pipeProcess) and $(LREF pipeShell) | |
3057 | to specify which of the child process' standard streams are redirected. | |
3058 | Use bitwise OR to combine flags. | |
b4c522fa | 3059 | */ |
5fee5ec3 | 3060 | enum Redirect |
b4c522fa | 3061 | { |
5fee5ec3 IB |
3062 | /// Redirect the standard input, output or error streams, respectively. |
3063 | stdin = 1, | |
3064 | stdout = 2, /// ditto | |
3065 | stderr = 4, /// ditto | |
b4c522fa | 3066 | |
5fee5ec3 IB |
3067 | /** |
3068 | Redirect _all three streams. This is equivalent to | |
3069 | $(D Redirect.stdin | Redirect.stdout | Redirect.stderr). | |
3070 | */ | |
3071 | all = stdin | stdout | stderr, | |
b4c522fa | 3072 | |
5fee5ec3 IB |
3073 | /** |
3074 | Redirect the standard error stream into the standard output stream. | |
3075 | This can not be combined with `Redirect.stderr`. | |
3076 | */ | |
3077 | stderrToStdout = 8, | |
b4c522fa | 3078 | |
5fee5ec3 IB |
3079 | /** |
3080 | Redirect the standard output stream into the standard error stream. | |
3081 | This can not be combined with `Redirect.stdout`. | |
3082 | */ | |
3083 | stdoutToStderr = 16, | |
b4c522fa IB |
3084 | } |
3085 | ||
b4c522fa IB |
3086 | @system unittest |
3087 | { | |
5fee5ec3 IB |
3088 | import std.string; |
3089 | version (Windows) TestScript prog = | |
3090 | "call :sub %~1 %~2 0 | |
3091 | call :sub %~1 %~2 1 | |
3092 | call :sub %~1 %~2 2 | |
3093 | call :sub %~1 %~2 3 | |
3094 | exit 3 | |
b4c522fa | 3095 | |
5fee5ec3 IB |
3096 | :sub |
3097 | set /p INPUT= | |
3098 | if -%INPUT%-==-stop- ( exit %~3 ) | |
3099 | echo %INPUT% %~1 | |
3100 | echo %INPUT% %~2 1>&2"; | |
3101 | else version (Posix) TestScript prog = | |
3102 | `for EXITCODE in 0 1 2 3; do | |
3103 | read INPUT | |
3104 | if test "$INPUT" = stop; then break; fi | |
3105 | echo "$INPUT $1" | |
3106 | echo "$INPUT $2" >&2 | |
3107 | done | |
3108 | exit $EXITCODE`; | |
3109 | auto pp = pipeProcess([prog.path, "bar", "baz"]); | |
3110 | pp.stdin.writeln("foo"); | |
3111 | pp.stdin.flush(); | |
3112 | assert(pp.stdout.readln().chomp() == "foo bar"); | |
3113 | assert(pp.stderr.readln().chomp().stripRight() == "foo baz"); | |
3114 | pp.stdin.writeln("1234567890"); | |
3115 | pp.stdin.flush(); | |
3116 | assert(pp.stdout.readln().chomp() == "1234567890 bar"); | |
3117 | assert(pp.stderr.readln().chomp().stripRight() == "1234567890 baz"); | |
3118 | pp.stdin.writeln("stop"); | |
3119 | pp.stdin.flush(); | |
3120 | assert(wait(pp.pid) == 2); | |
b4c522fa | 3121 | |
5fee5ec3 IB |
3122 | pp = pipeProcess([prog.path, "12345", "67890"], |
3123 | Redirect.stdin | Redirect.stdout | Redirect.stderrToStdout); | |
3124 | pp.stdin.writeln("xyz"); | |
3125 | pp.stdin.flush(); | |
3126 | assert(pp.stdout.readln().chomp() == "xyz 12345"); | |
3127 | assert(pp.stdout.readln().chomp().stripRight() == "xyz 67890"); | |
3128 | pp.stdin.writeln("stop"); | |
3129 | pp.stdin.flush(); | |
3130 | assert(wait(pp.pid) == 1); | |
b4c522fa | 3131 | |
5fee5ec3 IB |
3132 | pp = pipeShell(escapeShellCommand(prog.path, "AAAAA", "BBB"), |
3133 | Redirect.stdin | Redirect.stdoutToStderr | Redirect.stderr); | |
3134 | pp.stdin.writeln("ab"); | |
3135 | pp.stdin.flush(); | |
3136 | assert(pp.stderr.readln().chomp() == "ab AAAAA"); | |
3137 | assert(pp.stderr.readln().chomp().stripRight() == "ab BBB"); | |
3138 | pp.stdin.writeln("stop"); | |
3139 | pp.stdin.flush(); | |
3140 | assert(wait(pp.pid) == 1); | |
b4c522fa IB |
3141 | } |
3142 | ||
5fee5ec3 | 3143 | @system unittest |
b4c522fa | 3144 | { |
5fee5ec3 IB |
3145 | import std.exception : assertThrown; |
3146 | TestScript prog = "exit 0"; | |
3147 | assertThrown!StdioException(pipeProcess( | |
3148 | prog.path, | |
3149 | Redirect.stdout | Redirect.stdoutToStderr)); | |
3150 | assertThrown!StdioException(pipeProcess( | |
3151 | prog.path, | |
3152 | Redirect.stderr | Redirect.stderrToStdout)); | |
3153 | auto p = pipeProcess(prog.path, Redirect.stdin); | |
3154 | assertThrown!Error(p.stdout); | |
3155 | assertThrown!Error(p.stderr); | |
3156 | wait(p.pid); | |
3157 | p = pipeProcess(prog.path, Redirect.stderr); | |
3158 | assertThrown!Error(p.stdin); | |
3159 | assertThrown!Error(p.stdout); | |
3160 | wait(p.pid); | |
b4c522fa IB |
3161 | } |
3162 | ||
5fee5ec3 IB |
3163 | /** |
3164 | Object which contains $(REF File, std,stdio) handles that allow communication | |
3165 | with a child process through its standard streams. | |
b4c522fa | 3166 | */ |
5fee5ec3 IB |
3167 | struct ProcessPipes |
3168 | { | |
3169 | /// The $(LREF Pid) of the child process. | |
3170 | @property Pid pid() @safe nothrow | |
3171 | { | |
3172 | return _pid; | |
3173 | } | |
b4c522fa | 3174 | |
5fee5ec3 IB |
3175 | /** |
3176 | An $(REF File, std,stdio) that allows writing to the child process' | |
3177 | standard input stream. | |
b4c522fa | 3178 | |
5fee5ec3 IB |
3179 | Throws: |
3180 | $(OBJECTREF Error) if the child process' standard input stream hasn't | |
3181 | been redirected. | |
3182 | */ | |
3183 | @property File stdin() @safe nothrow | |
3184 | { | |
3185 | if ((_redirectFlags & Redirect.stdin) == 0) | |
3186 | throw new Error("Child process' standard input stream hasn't " | |
3187 | ~"been redirected."); | |
3188 | return _stdin; | |
3189 | } | |
b4c522fa | 3190 | |
5fee5ec3 IB |
3191 | /** |
3192 | An $(REF File, std,stdio) that allows reading from the child process' | |
3193 | standard output stream. | |
3194 | ||
3195 | Throws: | |
3196 | $(OBJECTREF Error) if the child process' standard output stream hasn't | |
3197 | been redirected. | |
3198 | */ | |
3199 | @property File stdout() @safe nothrow | |
b4c522fa | 3200 | { |
5fee5ec3 IB |
3201 | if ((_redirectFlags & Redirect.stdout) == 0) |
3202 | throw new Error("Child process' standard output stream hasn't " | |
3203 | ~"been redirected."); | |
3204 | return _stdout; | |
b4c522fa | 3205 | } |
5fee5ec3 IB |
3206 | |
3207 | /** | |
3208 | An $(REF File, std,stdio) that allows reading from the child process' | |
3209 | standard error stream. | |
3210 | ||
3211 | Throws: | |
3212 | $(OBJECTREF Error) if the child process' standard error stream hasn't | |
3213 | been redirected. | |
3214 | */ | |
3215 | @property File stderr() @safe nothrow | |
b4c522fa | 3216 | { |
5fee5ec3 IB |
3217 | if ((_redirectFlags & Redirect.stderr) == 0) |
3218 | throw new Error("Child process' standard error stream hasn't " | |
3219 | ~"been redirected."); | |
3220 | return _stderr; | |
b4c522fa | 3221 | } |
5fee5ec3 IB |
3222 | |
3223 | private: | |
3224 | Redirect _redirectFlags; | |
3225 | Pid _pid; | |
3226 | File _stdin, _stdout, _stderr; | |
b4c522fa IB |
3227 | } |
3228 | ||
b4c522fa | 3229 | |
b4c522fa | 3230 | |
5fee5ec3 IB |
3231 | /** |
3232 | Executes the given program or shell command and returns its exit | |
3233 | code and output. | |
b4c522fa | 3234 | |
5fee5ec3 IB |
3235 | `execute` and `executeShell` start a new process using |
3236 | $(LREF spawnProcess) and $(LREF spawnShell), respectively, and wait | |
3237 | for the process to complete before returning. The functions capture | |
3238 | what the child process prints to both its standard output and | |
3239 | standard error streams, and return this together with its exit code. | |
3240 | --- | |
3241 | auto dmd = execute(["dmd", "myapp.d"]); | |
3242 | if (dmd.status != 0) writeln("Compilation failed:\n", dmd.output); | |
b4c522fa | 3243 | |
5fee5ec3 IB |
3244 | auto ls = executeShell("ls -l"); |
3245 | if (ls.status != 0) writeln("Failed to retrieve file listing"); | |
3246 | else writeln(ls.output); | |
3247 | --- | |
b4c522fa | 3248 | |
5fee5ec3 IB |
3249 | The `args`/`program`/`command`, `env` and `config` |
3250 | parameters are forwarded straight to the underlying spawn functions, | |
3251 | and we refer to their documentation for details. | |
3252 | ||
3253 | Params: | |
3254 | args = An array which contains the program name as the zeroth element | |
3255 | and any command-line arguments in the following elements. | |
3256 | (See $(LREF spawnProcess) for details.) | |
3257 | program = The program name, $(I without) command-line arguments. | |
3258 | (See $(LREF spawnProcess) for details.) | |
3259 | command = A shell command which is passed verbatim to the command | |
3260 | interpreter. (See $(LREF spawnShell) for details.) | |
3261 | env = Additional environment variables for the child process. | |
3262 | (See $(LREF spawnProcess) for details.) | |
3263 | config = Flags that control process creation. See $(LREF Config) | |
3264 | for an overview of available flags, and note that the | |
3265 | `retainStd...` flags have no effect in this function. | |
3266 | maxOutput = The maximum number of bytes of output that should be | |
3267 | captured. | |
3268 | workDir = The working directory for the new process. | |
3269 | By default the child process inherits the parent's working | |
3270 | directory. | |
3271 | shellPath = The path to the shell to use to run the specified program. | |
3272 | By default this is $(LREF nativeShell). | |
b4c522fa | 3273 | |
b4c522fa | 3274 | |
5fee5ec3 IB |
3275 | Returns: |
3276 | An $(D std.typecons.Tuple!(int, "status", string, "output")). | |
b4c522fa | 3277 | |
5fee5ec3 IB |
3278 | POSIX_specific: |
3279 | If the process is terminated by a signal, the `status` field of | |
3280 | the return value will contain a negative number whose absolute | |
3281 | value is the signal number. (See $(LREF wait) for details.) | |
b4c522fa | 3282 | |
5fee5ec3 IB |
3283 | Throws: |
3284 | $(LREF ProcessException) on failure to start the process.$(BR) | |
3285 | $(REF StdioException, std,stdio) on failure to capture output. | |
3286 | */ | |
3287 | auto execute(scope const(char[])[] args, | |
3288 | const string[string] env = null, | |
3289 | Config config = Config.none, | |
3290 | size_t maxOutput = size_t.max, | |
3291 | scope const(char)[] workDir = null) | |
3292 | @safe | |
b4c522fa | 3293 | { |
5fee5ec3 | 3294 | return executeImpl!pipeProcess(args, env, config, maxOutput, workDir); |
b4c522fa IB |
3295 | } |
3296 | ||
5fee5ec3 IB |
3297 | /// ditto |
3298 | auto execute(scope const(char)[] program, | |
3299 | const string[string] env = null, | |
3300 | Config config = Config.none, | |
3301 | size_t maxOutput = size_t.max, | |
3302 | scope const(char)[] workDir = null) | |
3303 | @safe | |
b4c522fa | 3304 | { |
5fee5ec3 | 3305 | return executeImpl!pipeProcess(program, env, config, maxOutput, workDir); |
b4c522fa IB |
3306 | } |
3307 | ||
5fee5ec3 IB |
3308 | /// ditto |
3309 | auto executeShell(scope const(char)[] command, | |
3310 | const string[string] env = null, | |
3311 | Config config = Config.none, | |
3312 | size_t maxOutput = size_t.max, | |
3313 | scope const(char)[] workDir = null, | |
3314 | string shellPath = nativeShell) | |
3315 | @safe | |
b4c522fa | 3316 | { |
5fee5ec3 IB |
3317 | return executeImpl!pipeShell(command, |
3318 | env, | |
3319 | config, | |
3320 | maxOutput, | |
3321 | workDir, | |
3322 | shellPath); | |
b4c522fa IB |
3323 | } |
3324 | ||
5fee5ec3 IB |
3325 | // Does the actual work for execute() and executeShell(). |
3326 | private auto executeImpl(alias pipeFunc, Cmd, ExtraPipeFuncArgs...)( | |
3327 | Cmd commandLine, | |
3328 | const string[string] env = null, | |
3329 | Config config = Config.none, | |
3330 | size_t maxOutput = size_t.max, | |
3331 | scope const(char)[] workDir = null, | |
3332 | ExtraPipeFuncArgs extraArgs = ExtraPipeFuncArgs.init) | |
3333 | @trusted //TODO: @safe | |
b4c522fa | 3334 | { |
5fee5ec3 IB |
3335 | import std.algorithm.comparison : min; |
3336 | import std.array : appender; | |
3337 | import std.typecons : Tuple; | |
b4c522fa | 3338 | |
5fee5ec3 IB |
3339 | auto redirect = (config.flags & Config.Flags.stderrPassThrough) |
3340 | ? Redirect.stdout | |
3341 | : Redirect.stdout | Redirect.stderrToStdout; | |
b4c522fa | 3342 | |
5fee5ec3 IB |
3343 | auto p = pipeFunc(commandLine, redirect, |
3344 | env, config, workDir, extraArgs); | |
b4c522fa | 3345 | |
5fee5ec3 IB |
3346 | auto a = appender!string; |
3347 | enum size_t defaultChunkSize = 4096; | |
3348 | immutable chunkSize = min(maxOutput, defaultChunkSize); | |
3349 | ||
3350 | // Store up to maxOutput bytes in a. | |
3351 | foreach (ubyte[] chunk; p.stdout.byChunk(chunkSize)) | |
b4c522fa | 3352 | { |
5fee5ec3 IB |
3353 | immutable size_t remain = maxOutput - a.data.length; |
3354 | ||
3355 | if (chunk.length < remain) a.put(chunk); | |
b4c522fa IB |
3356 | else |
3357 | { | |
5fee5ec3 IB |
3358 | a.put(chunk[0 .. remain]); |
3359 | break; | |
b4c522fa IB |
3360 | } |
3361 | } | |
5fee5ec3 IB |
3362 | // Exhaust the stream, if necessary. |
3363 | foreach (ubyte[] chunk; p.stdout.byChunk(defaultChunkSize)) { } | |
b4c522fa | 3364 | |
5fee5ec3 IB |
3365 | return Tuple!(int, "status", string, "output")(wait(p.pid), a.data); |
3366 | } | |
b4c522fa | 3367 | |
5fee5ec3 IB |
3368 | @system unittest |
3369 | { | |
3370 | import std.string; | |
3371 | // To avoid printing the newline characters, we use the echo|set trick on | |
3372 | // Windows, and printf on POSIX (neither echo -n nor echo \c are portable). | |
3373 | version (Windows) TestScript prog = | |
3374 | "echo|set /p=%~1 | |
3375 | echo|set /p=%~2 1>&2 | |
3376 | exit 123"; | |
3377 | else version (Android) TestScript prog = | |
3378 | `echo -n $1 | |
3379 | echo -n $2 >&2 | |
3380 | exit 123`; | |
3381 | else version (Posix) TestScript prog = | |
3382 | `printf '%s' $1 | |
3383 | printf '%s' $2 >&2 | |
3384 | exit 123`; | |
3385 | auto r = execute([prog.path, "foo", "bar"]); | |
3386 | assert(r.status == 123); | |
3387 | assert(r.output.stripRight() == "foobar"); | |
3388 | auto s = execute([prog.path, "Hello", "World"]); | |
3389 | assert(s.status == 123); | |
3390 | assert(s.output.stripRight() == "HelloWorld"); | |
b4c522fa IB |
3391 | } |
3392 | ||
5fee5ec3 | 3393 | @safe unittest |
b4c522fa | 3394 | { |
5fee5ec3 IB |
3395 | import std.string; |
3396 | auto r1 = executeShell("echo foo"); | |
3397 | assert(r1.status == 0); | |
3398 | assert(r1.output.chomp() == "foo"); | |
3399 | auto r2 = executeShell("echo bar 1>&2"); | |
3400 | assert(r2.status == 0); | |
3401 | assert(r2.output.chomp().stripRight() == "bar"); | |
3402 | auto r3 = executeShell("exit 123"); | |
3403 | assert(r3.status == 123); | |
3404 | assert(r3.output.empty); | |
3405 | } | |
b4c522fa | 3406 | |
5fee5ec3 IB |
3407 | @system unittest |
3408 | { | |
3409 | // Temporarily disable output to stderr so as to not spam the build log. | |
3410 | import std.stdio : stderr; | |
3411 | import std.typecons : Tuple; | |
3412 | import std.file : readText, exists, remove; | |
3413 | import std.traits : ReturnType; | |
b4c522fa | 3414 | |
5fee5ec3 IB |
3415 | ReturnType!executeShell r; |
3416 | auto tmpname = uniqueTempPath; | |
3417 | scope(exit) if (exists(tmpname)) remove(tmpname); | |
3418 | auto t = stderr; | |
3419 | // Open a new scope to minimize code ran with stderr redirected. | |
b4c522fa | 3420 | { |
5fee5ec3 IB |
3421 | stderr.open(tmpname, "w"); |
3422 | scope(exit) stderr = t; | |
3423 | r = executeShell("echo D rox>&2", null, Config.stderrPassThrough); | |
b4c522fa | 3424 | } |
5fee5ec3 IB |
3425 | assert(r.status == 0); |
3426 | assert(r.output.empty); | |
3427 | auto witness = readText(tmpname); | |
3428 | import std.ascii : newline; | |
3429 | assert(witness == "D rox" ~ newline, "'" ~ witness ~ "'"); | |
b4c522fa IB |
3430 | } |
3431 | ||
5fee5ec3 | 3432 | @safe unittest |
b4c522fa | 3433 | { |
5fee5ec3 IB |
3434 | import std.typecons : Tuple; |
3435 | void foo() //Just test the compilation | |
3436 | { | |
3437 | auto ret1 = execute(["dummy", "arg"]); | |
3438 | auto ret2 = executeShell("dummy arg"); | |
3439 | static assert(is(typeof(ret1) == typeof(ret2))); | |
3440 | ||
3441 | Tuple!(int, string) ret3 = execute(["dummy", "arg"]); | |
3442 | } | |
b4c522fa IB |
3443 | } |
3444 | ||
5fee5ec3 IB |
3445 | /// An exception that signals a problem with starting or waiting for a process. |
3446 | class ProcessException : Exception | |
b4c522fa | 3447 | { |
5fee5ec3 IB |
3448 | import std.exception : basicExceptionCtors; |
3449 | mixin basicExceptionCtors; | |
b4c522fa | 3450 | |
5fee5ec3 IB |
3451 | // Creates a new ProcessException based on errno. |
3452 | static ProcessException newFromErrno(string customMsg = null, | |
3453 | string file = __FILE__, | |
3454 | size_t line = __LINE__) | |
3455 | { | |
3456 | import core.stdc.errno : errno; | |
3457 | return newFromErrno(errno, customMsg, file, line); | |
3458 | } | |
b4c522fa | 3459 | |
5fee5ec3 IB |
3460 | // ditto, but error number is provided by caller |
3461 | static ProcessException newFromErrno(int error, | |
3462 | string customMsg = null, | |
3463 | string file = __FILE__, | |
3464 | size_t line = __LINE__) | |
3465 | { | |
3466 | import std.exception : errnoString; | |
3467 | auto errnoMsg = errnoString(error); | |
3468 | auto msg = customMsg.empty ? errnoMsg | |
3469 | : customMsg ~ " (" ~ errnoMsg ~ ')'; | |
3470 | return new ProcessException(msg, file, line); | |
3471 | } | |
b4c522fa | 3472 | |
5fee5ec3 IB |
3473 | // Creates a new ProcessException based on GetLastError() (Windows only). |
3474 | version (Windows) | |
3475 | static ProcessException newFromLastError(string customMsg = null, | |
3476 | string file = __FILE__, | |
3477 | size_t line = __LINE__) | |
3478 | { | |
3479 | auto lastMsg = sysErrorString(GetLastError()); | |
3480 | auto msg = customMsg.empty ? lastMsg | |
3481 | : customMsg ~ " (" ~ lastMsg ~ ')'; | |
3482 | return new ProcessException(msg, file, line); | |
3483 | } | |
b4c522fa IB |
3484 | } |
3485 | ||
b4c522fa | 3486 | |
5fee5ec3 IB |
3487 | /** |
3488 | Determines the path to the current user's preferred command interpreter. | |
b4c522fa | 3489 | |
5fee5ec3 IB |
3490 | On Windows, this function returns the contents of the COMSPEC environment |
3491 | variable, if it exists. Otherwise, it returns the result of $(LREF nativeShell). | |
b4c522fa | 3492 | |
5fee5ec3 IB |
3493 | On POSIX, `userShell` returns the contents of the SHELL environment |
3494 | variable, if it exists and is non-empty. Otherwise, it returns the result of | |
3495 | $(LREF nativeShell). | |
3496 | */ | |
3497 | @property string userShell() @safe | |
3498 | { | |
3499 | version (Windows) return environment.get("COMSPEC", nativeShell); | |
3500 | else version (Posix) return environment.get("SHELL", nativeShell); | |
b4c522fa IB |
3501 | } |
3502 | ||
5fee5ec3 IB |
3503 | /** |
3504 | The platform-specific native shell path. | |
b4c522fa | 3505 | |
5fee5ec3 IB |
3506 | This function returns `"cmd.exe"` on Windows, `"/bin/sh"` on POSIX, and |
3507 | `"/system/bin/sh"` on Android. | |
3508 | */ | |
3509 | @property string nativeShell() @safe @nogc pure nothrow | |
b4c522fa | 3510 | { |
5fee5ec3 IB |
3511 | version (Windows) return "cmd.exe"; |
3512 | else version (Android) return "/system/bin/sh"; | |
3513 | else version (Posix) return "/bin/sh"; | |
3514 | } | |
b4c522fa | 3515 | |
5fee5ec3 IB |
3516 | // A command-line switch that indicates to the shell that it should |
3517 | // interpret the following argument as a command to be executed. | |
3518 | version (Posix) private immutable string shellSwitch = "-c"; | |
3519 | version (Windows) private immutable string shellSwitch = "/C"; | |
b4c522fa | 3520 | |
5fee5ec3 IB |
3521 | // Unittest support code: TestScript takes a string that contains a |
3522 | // shell script for the current platform, and writes it to a temporary | |
3523 | // file. On Windows the file name gets a .cmd extension, while on | |
3524 | // POSIX its executable permission bit is set. The file is | |
3525 | // automatically deleted when the object goes out of scope. | |
3526 | version (StdUnittest) | |
3527 | private struct TestScript | |
3528 | { | |
3529 | this(string code) @system | |
b4c522fa | 3530 | { |
5fee5ec3 IB |
3531 | // @system due to chmod |
3532 | import std.ascii : newline; | |
3533 | import std.file : write; | |
3534 | version (Windows) | |
b4c522fa | 3535 | { |
5fee5ec3 IB |
3536 | auto ext = ".cmd"; |
3537 | auto firstLine = "@echo off"; | |
b4c522fa | 3538 | } |
5fee5ec3 | 3539 | else version (Posix) |
b4c522fa | 3540 | { |
5fee5ec3 IB |
3541 | auto ext = ""; |
3542 | auto firstLine = "#!" ~ nativeShell; | |
b4c522fa | 3543 | } |
5fee5ec3 IB |
3544 | path = uniqueTempPath()~ext; |
3545 | write(path, firstLine ~ newline ~ code ~ newline); | |
3546 | version (Posix) | |
b4c522fa | 3547 | { |
5fee5ec3 IB |
3548 | import core.sys.posix.sys.stat : chmod; |
3549 | import std.conv : octal; | |
3550 | chmod(path.tempCString(), octal!777); | |
b4c522fa | 3551 | } |
5fee5ec3 | 3552 | } |
b4c522fa | 3553 | |
5fee5ec3 IB |
3554 | ~this() |
3555 | { | |
3556 | import std.file : remove, exists; | |
3557 | if (!path.empty && exists(path)) | |
b4c522fa | 3558 | { |
5fee5ec3 IB |
3559 | try { remove(path); } |
3560 | catch (Exception e) | |
b4c522fa | 3561 | { |
5fee5ec3 | 3562 | debug std.stdio.stderr.writeln(e.msg); |
b4c522fa | 3563 | } |
b4c522fa | 3564 | } |
b4c522fa | 3565 | } |
5fee5ec3 IB |
3566 | |
3567 | string path; | |
b4c522fa IB |
3568 | } |
3569 | ||
3570 | ||
3571 | // ============================================================================= | |
5fee5ec3 | 3572 | // Functions for shell command quoting/escaping. |
b4c522fa IB |
3573 | // ============================================================================= |
3574 | ||
3575 | ||
5fee5ec3 IB |
3576 | /* |
3577 | Command line arguments exist in three forms: | |
3578 | 1) string or char* array, as received by main. | |
3579 | Also used internally on POSIX systems. | |
3580 | 2) Command line string, as used in Windows' | |
3581 | CreateProcess and CommandLineToArgvW functions. | |
3582 | A specific quoting and escaping algorithm is used | |
3583 | to distinguish individual arguments. | |
3584 | 3) Shell command string, as written at a shell prompt | |
3585 | or passed to cmd /C - this one may contain shell | |
3586 | control characters, e.g. > or | for redirection / | |
3587 | piping - thus, yet another layer of escaping is | |
3588 | used to distinguish them from program arguments. | |
3589 | ||
3590 | Except for escapeWindowsArgument, the intermediary | |
3591 | format (2) is hidden away from the user in this module. | |
3592 | */ | |
3593 | ||
b4c522fa | 3594 | /** |
5fee5ec3 IB |
3595 | Escapes an argv-style argument array to be used with $(LREF spawnShell), |
3596 | $(LREF pipeShell) or $(LREF executeShell). | |
3597 | --- | |
3598 | string url = "http://dlang.org/"; | |
3599 | executeShell(escapeShellCommand("wget", url, "-O", "dlang-index.html")); | |
3600 | --- | |
b4c522fa | 3601 | |
5fee5ec3 IB |
3602 | Concatenate multiple `escapeShellCommand` and |
3603 | $(LREF escapeShellFileName) results to use shell redirection or | |
3604 | piping operators. | |
3605 | --- | |
3606 | executeShell( | |
3607 | escapeShellCommand("curl", "http://dlang.org/download.html") ~ | |
3608 | "|" ~ | |
3609 | escapeShellCommand("grep", "-o", `http://\S*\.zip`) ~ | |
3610 | ">" ~ | |
3611 | escapeShellFileName("D download links.txt")); | |
3612 | --- | |
3613 | ||
3614 | Throws: | |
3615 | $(OBJECTREF Exception) if any part of the command line contains unescapable | |
3616 | characters (NUL on all platforms, as well as CR and LF on Windows). | |
b4c522fa | 3617 | */ |
5fee5ec3 | 3618 | string escapeShellCommand(scope const(char[])[] args...) @safe pure |
b4c522fa | 3619 | { |
5fee5ec3 IB |
3620 | if (args.empty) |
3621 | return null; | |
3622 | version (Windows) | |
b4c522fa | 3623 | { |
5fee5ec3 IB |
3624 | // Do not ^-escape the first argument (the program path), |
3625 | // as the shell parses it differently from parameters. | |
3626 | // ^-escaping a program path that contains spaces will fail. | |
3627 | string result = escapeShellFileName(args[0]); | |
3628 | if (args.length > 1) | |
3629 | { | |
3630 | result ~= " " ~ escapeShellCommandString( | |
3631 | escapeShellArguments(args[1..$])); | |
3632 | } | |
3633 | return result; | |
b4c522fa | 3634 | } |
5fee5ec3 | 3635 | version (Posix) |
b4c522fa | 3636 | { |
5fee5ec3 | 3637 | return escapeShellCommandString(escapeShellArguments(args)); |
b4c522fa | 3638 | } |
5fee5ec3 | 3639 | } |
b4c522fa | 3640 | |
5fee5ec3 IB |
3641 | @safe unittest |
3642 | { | |
3643 | // This is a simple unit test without any special requirements, | |
3644 | // in addition to the unittest_burnin one below which requires | |
3645 | // special preparation. | |
b4c522fa | 3646 | |
5fee5ec3 IB |
3647 | struct TestVector { string[] args; string windows, posix; } |
3648 | TestVector[] tests = | |
3649 | [ | |
3650 | { | |
3651 | args : ["foo bar"], | |
3652 | windows : `"foo bar"`, | |
3653 | posix : `'foo bar'` | |
3654 | }, | |
3655 | { | |
3656 | args : ["foo bar", "hello"], | |
3657 | windows : `"foo bar" hello`, | |
3658 | posix : `'foo bar' 'hello'` | |
3659 | }, | |
3660 | { | |
3661 | args : ["foo bar", "hello world"], | |
3662 | windows : `"foo bar" ^"hello world^"`, | |
3663 | posix : `'foo bar' 'hello world'` | |
3664 | }, | |
3665 | { | |
3666 | args : ["foo bar", "hello", "world"], | |
3667 | windows : `"foo bar" hello world`, | |
3668 | posix : `'foo bar' 'hello' 'world'` | |
3669 | }, | |
3670 | { | |
3671 | args : ["foo bar", `'"^\`], | |
3672 | windows : `"foo bar" ^"'\^"^^\\^"`, | |
3673 | posix : `'foo bar' ''\''"^\'` | |
3674 | }, | |
3675 | ]; | |
b4c522fa | 3676 | |
5fee5ec3 IB |
3677 | foreach (test; tests) |
3678 | version (Windows) | |
3679 | assert(escapeShellCommand(test.args) == test.windows); | |
3680 | else | |
3681 | assert(escapeShellCommand(test.args) == test.posix ); | |
3682 | } | |
b4c522fa | 3683 | |
5fee5ec3 IB |
3684 | private string escapeShellCommandString(return scope string command) @safe pure |
3685 | { | |
3686 | version (Windows) | |
3687 | return escapeWindowsShellCommand(command); | |
3688 | else | |
3689 | return command; | |
3690 | } | |
b4c522fa | 3691 | |
5fee5ec3 IB |
3692 | private string escapeWindowsShellCommand(scope const(char)[] command) @safe pure |
3693 | { | |
3694 | import std.array : appender; | |
3695 | auto result = appender!string(); | |
3696 | result.reserve(command.length); | |
3697 | ||
3698 | foreach (c; command) | |
3699 | switch (c) | |
b4c522fa | 3700 | { |
5fee5ec3 IB |
3701 | case '\0': |
3702 | throw new Exception("Cannot put NUL in command line"); | |
3703 | case '\r': | |
3704 | case '\n': | |
3705 | throw new Exception("CR/LF are not escapable"); | |
3706 | case '\x01': .. case '\x09': | |
3707 | case '\x0B': .. case '\x0C': | |
3708 | case '\x0E': .. case '\x1F': | |
3709 | case '"': | |
3710 | case '^': | |
3711 | case '&': | |
3712 | case '<': | |
3713 | case '>': | |
3714 | case '|': | |
3715 | result.put('^'); | |
3716 | goto default; | |
3717 | default: | |
3718 | result.put(c); | |
b4c522fa | 3719 | } |
5fee5ec3 IB |
3720 | return result.data; |
3721 | } | |
3722 | ||
3723 | private string escapeShellArguments(scope const(char[])[] args...) | |
3724 | @trusted pure nothrow | |
3725 | { | |
3726 | import std.exception : assumeUnique; | |
3727 | char[] buf; | |
3728 | ||
3729 | @safe nothrow | |
3730 | char[] allocator(size_t size) | |
3731 | { | |
3732 | if (buf.length == 0) | |
3733 | return buf = new char[size]; | |
3734 | else | |
b4c522fa | 3735 | { |
5fee5ec3 IB |
3736 | auto p = buf.length; |
3737 | buf.length = buf.length + 1 + size; | |
3738 | buf[p++] = ' '; | |
3739 | return buf[p .. p+size]; | |
b4c522fa | 3740 | } |
b4c522fa IB |
3741 | } |
3742 | ||
5fee5ec3 IB |
3743 | foreach (arg; args) |
3744 | escapeShellArgument!allocator(arg); | |
3745 | return assumeUnique(buf); | |
3746 | } | |
3747 | ||
3748 | private auto escapeShellArgument(alias allocator)(scope const(char)[] arg) @safe nothrow | |
3749 | { | |
3750 | // The unittest for this function requires special | |
3751 | // preparation - see below. | |
3752 | ||
3753 | version (Windows) | |
3754 | return escapeWindowsArgumentImpl!allocator(arg); | |
3755 | else | |
3756 | return escapePosixArgumentImpl!allocator(arg); | |
3757 | } | |
3758 | ||
3759 | /** | |
3760 | Quotes a command-line argument in a manner conforming to the behavior of | |
3761 | $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx, | |
3762 | CommandLineToArgvW). | |
3763 | */ | |
3764 | string escapeWindowsArgument(scope const(char)[] arg) @trusted pure nothrow | |
3765 | { | |
3766 | // Rationale for leaving this function as public: | |
3767 | // this algorithm of escaping paths is also used in other software, | |
3768 | // e.g. DMD's response files. | |
3769 | import std.exception : assumeUnique; | |
3770 | auto buf = escapeWindowsArgumentImpl!charAllocator(arg); | |
3771 | return assumeUnique(buf); | |
3772 | } | |
b4c522fa | 3773 | |
b4c522fa | 3774 | |
5fee5ec3 IB |
3775 | private char[] charAllocator(size_t size) @safe pure nothrow |
3776 | { | |
3777 | return new char[size]; | |
3778 | } | |
b4c522fa | 3779 | |
b4c522fa | 3780 | |
5fee5ec3 IB |
3781 | private char[] escapeWindowsArgumentImpl(alias allocator)(scope const(char)[] arg) |
3782 | @safe nothrow | |
3783 | if (is(typeof(allocator(size_t.init)[0] = char.init))) | |
3784 | { | |
3785 | // References: | |
3786 | // * http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx | |
3787 | // * http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx | |
b4c522fa | 3788 | |
5fee5ec3 IB |
3789 | // Check if the string needs to be escaped, |
3790 | // and calculate the total string size. | |
b4c522fa | 3791 | |
5fee5ec3 IB |
3792 | // Trailing backslashes must be escaped |
3793 | bool escaping = true; | |
3794 | bool needEscape = false; | |
3795 | // Result size = input size + 2 for surrounding quotes + 1 for the | |
3796 | // backslash for each escaped character. | |
3797 | size_t size = 1 + arg.length + 1; | |
b4c522fa | 3798 | |
5fee5ec3 | 3799 | foreach_reverse (char c; arg) |
b4c522fa | 3800 | { |
5fee5ec3 | 3801 | if (c == '"') |
b4c522fa | 3802 | { |
5fee5ec3 IB |
3803 | needEscape = true; |
3804 | escaping = true; | |
3805 | size++; | |
b4c522fa | 3806 | } |
5fee5ec3 IB |
3807 | else |
3808 | if (c == '\\') | |
b4c522fa | 3809 | { |
5fee5ec3 IB |
3810 | if (escaping) |
3811 | size++; | |
b4c522fa | 3812 | } |
5fee5ec3 | 3813 | else |
b4c522fa | 3814 | { |
5fee5ec3 IB |
3815 | if (c == ' ' || c == '\t') |
3816 | needEscape = true; | |
3817 | escaping = false; | |
3818 | } | |
3819 | } | |
b4c522fa | 3820 | |
5fee5ec3 IB |
3821 | import std.ascii : isDigit; |
3822 | // Empty arguments need to be specified as "" | |
3823 | if (!arg.length) | |
3824 | needEscape = true; | |
3825 | else | |
3826 | // Arguments ending with digits need to be escaped, | |
3827 | // to disambiguate with 1>file redirection syntax | |
3828 | if (isDigit(arg[$-1])) | |
3829 | needEscape = true; | |
b4c522fa | 3830 | |
5fee5ec3 IB |
3831 | if (!needEscape) |
3832 | return allocator(arg.length)[] = arg; | |
b4c522fa | 3833 | |
5fee5ec3 | 3834 | // Construct result string. |
b4c522fa | 3835 | |
5fee5ec3 IB |
3836 | auto buf = allocator(size); |
3837 | size_t p = size; | |
3838 | buf[--p] = '"'; | |
3839 | escaping = true; | |
3840 | foreach_reverse (char c; arg) | |
3841 | { | |
3842 | if (c == '"') | |
3843 | escaping = true; | |
3844 | else | |
3845 | if (c != '\\') | |
3846 | escaping = false; | |
3847 | ||
3848 | buf[--p] = c; | |
3849 | if (escaping) | |
3850 | buf[--p] = '\\'; | |
b4c522fa | 3851 | } |
5fee5ec3 IB |
3852 | buf[--p] = '"'; |
3853 | assert(p == 0); | |
3854 | ||
3855 | return buf; | |
3856 | } | |
b4c522fa | 3857 | |
5fee5ec3 IB |
3858 | version (Windows) version (StdUnittest) |
3859 | { | |
b4c522fa | 3860 | private: |
5fee5ec3 IB |
3861 | import core.stdc.stddef; |
3862 | import core.stdc.wchar_ : wcslen; | |
3863 | import core.sys.windows.shellapi : CommandLineToArgvW; | |
3864 | import core.sys.windows.winbase; | |
3865 | import core.sys.windows.winnt; | |
3866 | import std.array; | |
3867 | ||
3868 | string[] parseCommandLine(string line) | |
b4c522fa | 3869 | { |
5fee5ec3 IB |
3870 | import std.algorithm.iteration : map; |
3871 | import std.array : array; | |
3872 | import std.conv : to; | |
3873 | auto lpCommandLine = (to!(WCHAR[])(line) ~ '\0').ptr; | |
3874 | int numArgs; | |
3875 | auto args = CommandLineToArgvW(lpCommandLine, &numArgs); | |
3876 | scope(exit) LocalFree(args); | |
3877 | return args[0 .. numArgs] | |
3878 | .map!(arg => to!string(arg[0 .. wcslen(arg)])) | |
3879 | .array(); | |
3880 | } | |
b4c522fa | 3881 | |
5fee5ec3 IB |
3882 | @system unittest |
3883 | { | |
3884 | import std.conv : text; | |
3885 | string[] testStrings = [ | |
3886 | `Hello`, | |
3887 | `Hello, world`, | |
3888 | `Hello, "world"`, | |
3889 | `C:\`, | |
3890 | `C:\dmd`, | |
3891 | `C:\Program Files\`, | |
3892 | ]; | |
b4c522fa | 3893 | |
5fee5ec3 IB |
3894 | enum CHARS = `_x\" *&^` ~ "\t"; // _ is placeholder for nothing |
3895 | foreach (c1; CHARS) | |
3896 | foreach (c2; CHARS) | |
3897 | foreach (c3; CHARS) | |
3898 | foreach (c4; CHARS) | |
3899 | testStrings ~= [c1, c2, c3, c4].replace("_", ""); | |
b4c522fa | 3900 | |
5fee5ec3 | 3901 | foreach (s; testStrings) |
b4c522fa | 3902 | { |
5fee5ec3 IB |
3903 | auto q = escapeWindowsArgument(s); |
3904 | auto args = parseCommandLine("Dummy.exe " ~ q); | |
3905 | assert(args.length == 2, s ~ " => " ~ q ~ " #" ~ text(args.length-1)); | |
3906 | assert(args[1] == s, s ~ " => " ~ q ~ " => " ~ args[1]); | |
b4c522fa | 3907 | } |
b4c522fa IB |
3908 | } |
3909 | } | |
3910 | ||
5fee5ec3 | 3911 | private string escapePosixArgument(scope const(char)[] arg) @trusted pure nothrow |
b4c522fa | 3912 | { |
5fee5ec3 IB |
3913 | import std.exception : assumeUnique; |
3914 | auto buf = escapePosixArgumentImpl!charAllocator(arg); | |
3915 | return assumeUnique(buf); | |
3916 | } | |
b4c522fa | 3917 | |
5fee5ec3 IB |
3918 | private char[] escapePosixArgumentImpl(alias allocator)(scope const(char)[] arg) |
3919 | @safe nothrow | |
3920 | if (is(typeof(allocator(size_t.init)[0] = char.init))) | |
3921 | { | |
3922 | // '\'' means: close quoted part of argument, append an escaped | |
3923 | // single quote, and reopen quotes | |
b4c522fa | 3924 | |
5fee5ec3 IB |
3925 | // Below code is equivalent to: |
3926 | // return `'` ~ std.array.replace(arg, `'`, `'\''`) ~ `'`; | |
b4c522fa | 3927 | |
5fee5ec3 IB |
3928 | size_t size = 1 + arg.length + 1; |
3929 | foreach (char c; arg) | |
3930 | if (c == '\'') | |
3931 | size += 3; | |
b4c522fa | 3932 | |
5fee5ec3 IB |
3933 | auto buf = allocator(size); |
3934 | size_t p = 0; | |
3935 | buf[p++] = '\''; | |
3936 | foreach (char c; arg) | |
3937 | if (c == '\'') | |
3938 | { | |
3939 | buf[p .. p+4] = `'\''`; | |
3940 | p += 4; | |
3941 | } | |
3942 | else | |
3943 | buf[p++] = c; | |
3944 | buf[p++] = '\''; | |
3945 | assert(p == size); | |
b4c522fa | 3946 | |
5fee5ec3 IB |
3947 | return buf; |
3948 | } | |
b4c522fa | 3949 | |
5fee5ec3 IB |
3950 | /** |
3951 | Escapes a filename to be used for shell redirection with $(LREF spawnShell), | |
3952 | $(LREF pipeShell) or $(LREF executeShell). | |
3953 | */ | |
3954 | string escapeShellFileName(scope const(char)[] fileName) @trusted pure nothrow | |
3955 | { | |
3956 | // The unittest for this function requires special | |
3957 | // preparation - see below. | |
b4c522fa | 3958 | |
5fee5ec3 IB |
3959 | version (Windows) |
3960 | { | |
3961 | // If a file starts with &, it can cause cmd.exe to misinterpret | |
3962 | // the file name as the stream redirection syntax: | |
3963 | // command > "&foo.txt" | |
3964 | // gets interpreted as | |
3965 | // command >&foo.txt | |
3966 | // Prepend .\ to disambiguate. | |
b4c522fa | 3967 | |
5fee5ec3 IB |
3968 | if (fileName.length && fileName[0] == '&') |
3969 | return cast(string)(`".\` ~ fileName ~ '"'); | |
b4c522fa | 3970 | |
5fee5ec3 IB |
3971 | return cast(string)('"' ~ fileName ~ '"'); |
3972 | } | |
3973 | else | |
3974 | return escapePosixArgument(fileName); | |
3975 | } | |
3976 | ||
3977 | // Loop generating strings with random characters | |
3978 | //version = unittest_burnin; | |
3979 | ||
3980 | version (unittest_burnin) | |
3981 | @system unittest | |
3982 | { | |
3983 | // There are no readily-available commands on all platforms suitable | |
3984 | // for properly testing command escaping. The behavior of CMD's "echo" | |
3985 | // built-in differs from the POSIX program, and Windows ports of POSIX | |
3986 | // environments (Cygwin, msys, gnuwin32) may interfere with their own | |
3987 | // "echo" ports. | |
3988 | ||
3989 | // To run this unit test, create std_process_unittest_helper.d with the | |
3990 | // following content and compile it: | |
3991 | // import std.stdio, std.array; void main(string[] args) { write(args.join("\0")); } | |
3992 | // Then, test this module with: | |
3993 | // rdmd --main -unittest -version=unittest_burnin process.d | |
3994 | ||
3995 | auto helper = absolutePath("std_process_unittest_helper"); | |
3996 | assert(executeShell(helper ~ " hello").output.split("\0")[1..$] == ["hello"], "Helper malfunction"); | |
3997 | ||
3998 | void test(string[] s, string fn) | |
b4c522fa | 3999 | { |
5fee5ec3 IB |
4000 | string e; |
4001 | string[] g; | |
b4c522fa | 4002 | |
5fee5ec3 IB |
4003 | e = escapeShellCommand(helper ~ s); |
4004 | { | |
4005 | scope(failure) writefln("executeShell() failed.\nExpected:\t%s\nEncoded:\t%s", s, [e]); | |
4006 | auto result = executeShell(e); | |
4007 | assert(result.status == 0, "std_process_unittest_helper failed"); | |
4008 | g = result.output.split("\0")[1..$]; | |
4009 | } | |
4010 | assert(s == g, format("executeShell() test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); | |
4011 | ||
4012 | e = escapeShellCommand(helper ~ s) ~ ">" ~ escapeShellFileName(fn); | |
4013 | { | |
4014 | scope(failure) writefln( | |
4015 | "executeShell() with redirect failed.\nExpected:\t%s\nFilename:\t%s\nEncoded:\t%s", s, [fn], [e]); | |
4016 | auto result = executeShell(e); | |
4017 | assert(result.status == 0, "std_process_unittest_helper failed"); | |
4018 | assert(!result.output.length, "No output expected, got:\n" ~ result.output); | |
4019 | g = readText(fn).split("\0")[1..$]; | |
4020 | } | |
4021 | remove(fn); | |
4022 | assert(s == g, | |
4023 | format("executeShell() with redirect test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); | |
b4c522fa IB |
4024 | } |
4025 | ||
5fee5ec3 IB |
4026 | while (true) |
4027 | { | |
4028 | string[] args; | |
4029 | foreach (n; 0 .. uniform(1, 4)) | |
4030 | { | |
4031 | string arg; | |
4032 | foreach (l; 0 .. uniform(0, 10)) | |
4033 | { | |
4034 | dchar c; | |
4035 | while (true) | |
4036 | { | |
4037 | version (Windows) | |
4038 | { | |
4039 | // As long as DMD's system() uses CreateProcessA, | |
4040 | // we can't reliably pass Unicode | |
4041 | c = uniform(0, 128); | |
4042 | } | |
4043 | else | |
4044 | c = uniform!ubyte(); | |
b4c522fa | 4045 | |
5fee5ec3 IB |
4046 | if (c == 0) |
4047 | continue; // argv-strings are zero-terminated | |
4048 | version (Windows) | |
4049 | if (c == '\r' || c == '\n') | |
4050 | continue; // newlines are unescapable on Windows | |
4051 | break; | |
4052 | } | |
4053 | arg ~= c; | |
4054 | } | |
4055 | args ~= arg; | |
4056 | } | |
b4c522fa | 4057 | |
5fee5ec3 IB |
4058 | // generate filename |
4059 | string fn; | |
4060 | foreach (l; 0 .. uniform(1, 10)) | |
4061 | { | |
4062 | dchar c; | |
4063 | while (true) | |
4064 | { | |
4065 | version (Windows) | |
4066 | c = uniform(0, 128); // as above | |
4067 | else | |
4068 | c = uniform!ubyte(); | |
b4c522fa | 4069 | |
5fee5ec3 IB |
4070 | if (c == 0 || c == '/') |
4071 | continue; // NUL and / are the only characters | |
4072 | // forbidden in POSIX filenames | |
4073 | version (Windows) | |
4074 | if (c < '\x20' || c == '<' || c == '>' || c == ':' || | |
4075 | c == '"' || c == '\\' || c == '|' || c == '?' || c == '*') | |
4076 | continue; // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx | |
4077 | break; | |
4078 | } | |
b4c522fa | 4079 | |
5fee5ec3 IB |
4080 | fn ~= c; |
4081 | } | |
4082 | fn = fn[0..$/2] ~ "_testfile_" ~ fn[$/2..$]; | |
b4c522fa | 4083 | |
5fee5ec3 IB |
4084 | test(args, fn); |
4085 | } | |
4086 | } | |
b4c522fa IB |
4087 | |
4088 | // ============================================================================= | |
4089 | // Everything below this line was part of the old std.process, and most of | |
4090 | // it will be deprecated and removed. | |
4091 | // ============================================================================= | |
4092 | ||
4093 | ||
4094 | /* | |
5fee5ec3 | 4095 | Copyright: Copyright The D Language Foundation 2007 - 2009. |
b4c522fa IB |
4096 | License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). |
4097 | Authors: $(HTTP digitalmars.com, Walter Bright), | |
4098 | $(HTTP erdani.org, Andrei Alexandrescu), | |
4099 | $(HTTP thecybershadow.net, Vladimir Panteleev) | |
4100 | Source: $(PHOBOSSRC std/_process.d) | |
4101 | */ | |
4102 | /* | |
5fee5ec3 | 4103 | Copyright The D Language Foundation 2007 - 2009. |
b4c522fa IB |
4104 | Distributed under the Boost Software License, Version 1.0. |
4105 | (See accompanying file LICENSE_1_0.txt or copy at | |
4106 | http://www.boost.org/LICENSE_1_0.txt) | |
4107 | */ | |
4108 | ||
4109 | ||
4110 | import core.stdc.errno; | |
4111 | import core.stdc.stdlib; | |
4112 | import core.stdc.string; | |
4113 | import core.thread; | |
4114 | ||
4115 | version (Windows) | |
4116 | { | |
4117 | import std.file, std.format, std.random; | |
4118 | } | |
4119 | version (Posix) | |
4120 | { | |
4121 | import core.sys.posix.stdlib; | |
4122 | } | |
b4c522fa IB |
4123 | |
4124 | private void toAStringz(in string[] a, const(char)**az) | |
4125 | { | |
4126 | import std.string : toStringz; | |
4127 | foreach (string s; a) | |
4128 | { | |
4129 | *az++ = toStringz(s); | |
4130 | } | |
4131 | *az = null; | |
4132 | } | |
4133 | ||
4134 | ||
4135 | /* ========================================================== */ | |
4136 | ||
4137 | //version (Windows) | |
4138 | //{ | |
4139 | // int spawnvp(int mode, string pathname, string[] argv) | |
4140 | // { | |
4141 | // char** argv_ = cast(char**) core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); | |
4142 | // scope(exit) core.stdc.stdlib.free(argv_); | |
4143 | // | |
4144 | // toAStringz(argv, argv_); | |
4145 | // | |
4146 | // return spawnvp(mode, pathname.tempCString(), argv_); | |
4147 | // } | |
4148 | //} | |
4149 | ||
4150 | // Incorporating idea (for spawnvp() on Posix) from Dave Fladebo | |
4151 | ||
4152 | enum { _P_WAIT, _P_NOWAIT, _P_OVERLAY } | |
5fee5ec3 | 4153 | version (Windows) extern(C) int spawnvp(int, scope const(char) *, scope const(char*)*); |
b4c522fa IB |
4154 | alias P_WAIT = _P_WAIT; |
4155 | alias P_NOWAIT = _P_NOWAIT; | |
4156 | ||
4157 | /* ========================================================== */ | |
4158 | ||
4159 | version (StdDdoc) | |
4160 | { | |
4161 | /** | |
5fee5ec3 IB |
4162 | Replaces the current process by executing a command, `pathname`, with |
4163 | the arguments in `argv`. | |
b4c522fa | 4164 | |
5fee5ec3 | 4165 | $(BLUE This function is Posix-Only.) |
b4c522fa | 4166 | |
5fee5ec3 | 4167 | Typically, the first element of `argv` is |
b4c522fa | 4168 | the command being executed, i.e. $(D argv[0] == pathname). The 'p' |
5fee5ec3 | 4169 | versions of `exec` search the PATH environment variable for $(D |
b4c522fa IB |
4170 | pathname). The 'e' versions additionally take the new process' |
4171 | environment variables as an array of strings of the form key=value. | |
4172 | ||
4173 | Does not return on success (the current process will have been | |
4174 | replaced). Returns -1 on failure with no indication of the | |
4175 | underlying error. | |
4176 | ||
4177 | Windows_specific: | |
4178 | These functions are only supported on POSIX platforms, as the Windows | |
4179 | operating systems do not provide the ability to overwrite the current | |
4180 | process image with another. In single-threaded programs it is possible | |
5fee5ec3 | 4181 | to approximate the effect of `execv*` by using $(LREF spawnProcess) |
b4c522fa IB |
4182 | and terminating the current process once the child process has returned. |
4183 | For example: | |
4184 | --- | |
4185 | auto commandLine = [ "program", "arg1", "arg2" ]; | |
4186 | version (Posix) | |
4187 | { | |
4188 | execv(commandLine[0], commandLine); | |
4189 | throw new Exception("Failed to execute program"); | |
4190 | } | |
4191 | else version (Windows) | |
4192 | { | |
5fee5ec3 IB |
4193 | import core.stdc.stdlib : _Exit; |
4194 | _Exit(wait(spawnProcess(commandLine))); | |
b4c522fa IB |
4195 | } |
4196 | --- | |
5fee5ec3 | 4197 | This is, however, NOT equivalent to POSIX' `execv*`. For one thing, the |
b4c522fa IB |
4198 | executed program is started as a separate process, with all this entails. |
4199 | Secondly, in a multithreaded program, other threads will continue to do | |
4200 | work while the current thread is waiting for the child process to complete. | |
4201 | ||
4202 | A better option may sometimes be to terminate the current program immediately | |
4203 | after spawning the child process. This is the behaviour exhibited by the | |
5fee5ec3 | 4204 | $(LINK2 http://msdn.microsoft.com/en-us/library/431x4c1w.aspx,`__exec`) |
b4c522fa | 4205 | functions in Microsoft's C runtime library, and it is how D's now-deprecated |
5fee5ec3 | 4206 | Windows `execv*` functions work. Example: |
b4c522fa IB |
4207 | --- |
4208 | auto commandLine = [ "program", "arg1", "arg2" ]; | |
4209 | version (Posix) | |
4210 | { | |
4211 | execv(commandLine[0], commandLine); | |
4212 | throw new Exception("Failed to execute program"); | |
4213 | } | |
4214 | else version (Windows) | |
4215 | { | |
4216 | spawnProcess(commandLine); | |
4217 | import core.stdc.stdlib : _exit; | |
4218 | _exit(0); | |
4219 | } | |
4220 | --- | |
4221 | */ | |
4222 | int execv(in string pathname, in string[] argv); | |
4223 | ///ditto | |
4224 | int execve(in string pathname, in string[] argv, in string[] envp); | |
4225 | /// ditto | |
4226 | int execvp(in string pathname, in string[] argv); | |
4227 | /// ditto | |
4228 | int execvpe(in string pathname, in string[] argv, in string[] envp); | |
4229 | } | |
4230 | else version (Posix) | |
4231 | { | |
4232 | int execv(in string pathname, in string[] argv) | |
4233 | { | |
4234 | return execv_(pathname, argv); | |
4235 | } | |
4236 | int execve(in string pathname, in string[] argv, in string[] envp) | |
4237 | { | |
4238 | return execve_(pathname, argv, envp); | |
4239 | } | |
4240 | int execvp(in string pathname, in string[] argv) | |
4241 | { | |
4242 | return execvp_(pathname, argv); | |
4243 | } | |
4244 | int execvpe(in string pathname, in string[] argv, in string[] envp) | |
4245 | { | |
4246 | return execvpe_(pathname, argv, envp); | |
4247 | } | |
4248 | } | |
4249 | ||
4250 | // Move these C declarations to druntime if we decide to keep the D wrappers | |
4251 | extern(C) | |
4252 | { | |
5fee5ec3 IB |
4253 | int execv(scope const(char) *, scope const(char *)*); |
4254 | int execve(scope const(char)*, scope const(char*)*, scope const(char*)*); | |
4255 | int execvp(scope const(char)*, scope const(char*)*); | |
4256 | version (Windows) int execvpe(scope const(char)*, scope const(char*)*, scope const(char*)*); | |
b4c522fa IB |
4257 | } |
4258 | ||
4259 | private int execv_(in string pathname, in string[] argv) | |
4260 | { | |
5fee5ec3 IB |
4261 | import core.exception : OutOfMemoryError; |
4262 | import std.exception : enforce; | |
b4c522fa | 4263 | auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); |
5fee5ec3 | 4264 | enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); |
b4c522fa IB |
4265 | scope(exit) core.stdc.stdlib.free(argv_); |
4266 | ||
4267 | toAStringz(argv, argv_); | |
4268 | ||
4269 | return execv(pathname.tempCString(), argv_); | |
4270 | } | |
4271 | ||
4272 | private int execve_(in string pathname, in string[] argv, in string[] envp) | |
4273 | { | |
5fee5ec3 IB |
4274 | import core.exception : OutOfMemoryError; |
4275 | import std.exception : enforce; | |
b4c522fa | 4276 | auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); |
5fee5ec3 | 4277 | enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); |
b4c522fa IB |
4278 | scope(exit) core.stdc.stdlib.free(argv_); |
4279 | auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length)); | |
5fee5ec3 | 4280 | enforce!OutOfMemoryError(envp_ !is null, "Out of memory in std.process."); |
b4c522fa IB |
4281 | scope(exit) core.stdc.stdlib.free(envp_); |
4282 | ||
4283 | toAStringz(argv, argv_); | |
4284 | toAStringz(envp, envp_); | |
4285 | ||
4286 | return execve(pathname.tempCString(), argv_, envp_); | |
4287 | } | |
4288 | ||
4289 | private int execvp_(in string pathname, in string[] argv) | |
4290 | { | |
5fee5ec3 IB |
4291 | import core.exception : OutOfMemoryError; |
4292 | import std.exception : enforce; | |
b4c522fa | 4293 | auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); |
5fee5ec3 | 4294 | enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); |
b4c522fa IB |
4295 | scope(exit) core.stdc.stdlib.free(argv_); |
4296 | ||
4297 | toAStringz(argv, argv_); | |
4298 | ||
4299 | return execvp(pathname.tempCString(), argv_); | |
4300 | } | |
4301 | ||
4302 | private int execvpe_(in string pathname, in string[] argv, in string[] envp) | |
4303 | { | |
4304 | version (Posix) | |
4305 | { | |
4306 | import std.array : split; | |
4307 | import std.conv : to; | |
4308 | // Is pathname rooted? | |
4309 | if (pathname[0] == '/') | |
4310 | { | |
4311 | // Yes, so just call execve() | |
4312 | return execve(pathname, argv, envp); | |
4313 | } | |
4314 | else | |
4315 | { | |
4316 | // No, so must traverse PATHs, looking for first match | |
4317 | string[] envPaths = split( | |
4318 | to!string(core.stdc.stdlib.getenv("PATH")), ":"); | |
4319 | int iRet = 0; | |
4320 | ||
4321 | // Note: if any call to execve() succeeds, this process will cease | |
4322 | // execution, so there's no need to check the execve() result through | |
4323 | // the loop. | |
4324 | ||
4325 | foreach (string pathDir; envPaths) | |
4326 | { | |
4327 | string composite = cast(string) (pathDir ~ "/" ~ pathname); | |
4328 | ||
4329 | iRet = execve(composite, argv, envp); | |
4330 | } | |
4331 | if (0 != iRet) | |
4332 | { | |
4333 | iRet = execve(pathname, argv, envp); | |
4334 | } | |
4335 | ||
4336 | return iRet; | |
4337 | } | |
4338 | } | |
4339 | else version (Windows) | |
4340 | { | |
5fee5ec3 IB |
4341 | import core.exception : OutOfMemoryError; |
4342 | import std.exception : enforce; | |
b4c522fa | 4343 | auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); |
5fee5ec3 | 4344 | enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); |
b4c522fa IB |
4345 | scope(exit) core.stdc.stdlib.free(argv_); |
4346 | auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length)); | |
5fee5ec3 | 4347 | enforce!OutOfMemoryError(envp_ !is null, "Out of memory in std.process."); |
b4c522fa IB |
4348 | scope(exit) core.stdc.stdlib.free(envp_); |
4349 | ||
4350 | toAStringz(argv, argv_); | |
4351 | toAStringz(envp, envp_); | |
4352 | ||
4353 | return execvpe(pathname.tempCString(), argv_, envp_); | |
4354 | } | |
4355 | else | |
4356 | { | |
4357 | static assert(0); | |
4358 | } // version | |
4359 | } | |
4360 | ||
4361 | version (StdDdoc) | |
4362 | { | |
4363 | /**************************************** | |
4364 | * Start up the browser and set it to viewing the page at url. | |
4365 | */ | |
5fee5ec3 | 4366 | void browse(scope const(char)[] url); |
b4c522fa IB |
4367 | } |
4368 | else | |
4369 | version (Windows) | |
4370 | { | |
5fee5ec3 | 4371 | import core.sys.windows.shellapi, core.sys.windows.winuser; |
b4c522fa | 4372 | |
9fa27ed0 | 4373 | pragma(lib,"shell32.lib"); |
b4c522fa | 4374 | |
5fee5ec3 | 4375 | void browse(scope const(char)[] url) nothrow @nogc @trusted |
b4c522fa IB |
4376 | { |
4377 | ShellExecuteW(null, "open", url.tempCStringW(), null, null, SW_SHOWNORMAL); | |
4378 | } | |
4379 | } | |
5fee5ec3 | 4380 | else version (Posix) |
b4c522fa IB |
4381 | { |
4382 | import core.stdc.stdio; | |
4383 | import core.stdc.string; | |
4384 | import core.sys.posix.unistd; | |
4385 | ||
5fee5ec3 | 4386 | void browse(scope const(char)[] url) nothrow @nogc @safe |
b4c522fa | 4387 | { |
9c7d5e88 | 4388 | const buffer = url.tempCString(); // Retain buffer until end of scope |
5fee5ec3 | 4389 | const(char)*[3] args; |
b4c522fa | 4390 | |
5fee5ec3 IB |
4391 | // Trusted because it's called with a zero-terminated literal |
4392 | const(char)* browser = (() @trusted => core.stdc.stdlib.getenv("BROWSER"))(); | |
b4c522fa | 4393 | if (browser) |
5fee5ec3 IB |
4394 | { |
4395 | // String already zero-terminated | |
4396 | browser = (() @trusted => strdup(browser))(); | |
b4c522fa | 4397 | args[0] = browser; |
b4c522fa IB |
4398 | } |
4399 | else | |
4400 | { | |
5fee5ec3 IB |
4401 | version (OSX) |
4402 | { | |
4403 | args[0] = "open"; | |
4404 | } | |
4405 | else | |
4406 | { | |
4407 | //args[0] = "x-www-browser"; // doesn't work on some systems | |
4408 | args[0] = "xdg-open"; | |
4409 | } | |
b4c522fa IB |
4410 | } |
4411 | ||
5fee5ec3 IB |
4412 | args[1] = buffer; |
4413 | args[2] = null; | |
4414 | ||
b4c522fa IB |
4415 | auto childpid = core.sys.posix.unistd.fork(); |
4416 | if (childpid == 0) | |
4417 | { | |
5fee5ec3 IB |
4418 | // Trusted because args and all entries are always zero-terminated |
4419 | (() @trusted => | |
4420 | core.sys.posix.unistd.execvp(args[0], &args[0]) || | |
4421 | perror(args[0]) // failed to execute | |
4422 | )(); | |
b4c522fa IB |
4423 | return; |
4424 | } | |
4425 | if (browser) | |
5fee5ec3 IB |
4426 | // Trusted because it's allocated via strdup above |
4427 | (() @trusted => free(cast(void*) browser))(); | |
b4c522fa | 4428 | |
5fee5ec3 | 4429 | version (StdUnittest) |
b4c522fa | 4430 | { |
5fee5ec3 IB |
4431 | // Verify that the test script actually suceeds |
4432 | int status; | |
4433 | const check = (() @trusted => waitpid(childpid, &status, 0))(); | |
4434 | assert(check != -1); | |
4435 | assert(status == 0); | |
b4c522fa | 4436 | } |
b4c522fa IB |
4437 | } |
4438 | } | |
4439 | else | |
4440 | static assert(0, "os not supported"); | |
5fee5ec3 IB |
4441 | |
4442 | // Verify attributes are consistent between all implementations | |
4443 | @safe @nogc nothrow unittest | |
4444 | { | |
4445 | if (false) | |
4446 | browse(""); | |
4447 | } | |
4448 | ||
4449 | version (Windows) { /* Doesn't use BROWSER */ } | |
4450 | else | |
4451 | @system unittest | |
4452 | { | |
4453 | import std.conv : text; | |
4454 | import std.range : repeat; | |
4455 | immutable string url = text("http://", repeat('x', 249)); | |
4456 | ||
4457 | TestScript prog = `if [ "$1" != "` ~ url ~ `" ]; then exit 1; fi`; | |
4458 | environment["BROWSER"] = prog.path; | |
4459 | browse(url); | |
4460 | } |