-
-
Notifications
You must be signed in to change notification settings - Fork 706
/
Copy pathprocess.d
4160 lines (3692 loc) · 137 KB
/
process.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Written in the D programming language.
/**
Functions for starting and interacting with other processes, and for
working with the current process' execution environment.
Process_handling:
$(UL $(LI
$(LREF spawnProcess) spawns a new process, optionally assigning it an
arbitrary set of standard input, output, and error streams.
The function returns immediately, leaving the child process to execute
in parallel with its parent. All other functions in this module that
spawn processes are built around `spawnProcess`.)
$(LI
$(LREF wait) makes the parent process wait for a child process to
terminate. In general one should always do this, to avoid
child processes becoming "zombies" when the parent process exits.
Scope guards are perfect for this – see the $(LREF spawnProcess)
documentation for examples. $(LREF tryWait) is similar to `wait`,
but does not block if the process has not yet terminated.)
$(LI
$(LREF pipeProcess) also spawns a child process which runs
in parallel with its parent. However, instead of taking
arbitrary streams, it automatically creates a set of
pipes that allow the parent to communicate with the child
through the child's standard input, output, and/or error streams.
This function corresponds roughly to C's `popen` function.)
$(LI
$(LREF execute) starts a new process and waits for it
to complete before returning. Additionally, it captures
the process' standard output and error streams and returns
the output of these as a string.)
$(LI
$(LREF spawnShell), $(LREF pipeShell) and $(LREF executeShell) work like
`spawnProcess`, `pipeProcess` and `execute`, respectively,
except that they take a single command string and run it through
the current user's default command interpreter.
`executeShell` corresponds roughly to C's `system` function.)
$(LI
$(LREF kill) attempts to terminate a running process.)
)
The following table compactly summarises the different process creation
functions and how they relate to each other:
$(BOOKTABLE,
$(TR $(TH )
$(TH Runs program directly)
$(TH Runs shell command))
$(TR $(TD Low-level process creation)
$(TD $(LREF spawnProcess))
$(TD $(LREF spawnShell)))
$(TR $(TD Automatic input/output redirection using pipes)
$(TD $(LREF pipeProcess))
$(TD $(LREF pipeShell)))
$(TR $(TD Execute and wait for completion, collect output)
$(TD $(LREF execute))
$(TD $(LREF executeShell)))
)
Other_functionality:
$(UL
$(LI
$(LREF pipe) is used to create unidirectional pipes.)
$(LI
$(LREF environment) is an interface through which the current process'
environment variables can be read and manipulated.)
$(LI
$(LREF escapeShellCommand) and $(LREF escapeShellFileName) are useful
for constructing shell command lines in a portable way.)
)
Authors:
$(LINK2 https://github.com/kyllingstad, Lars Tandle Kyllingstad),
$(LINK2 https://github.com/schveiguy, Steven Schveighoffer),
$(HTTP thecybershadow.net, Vladimir Panteleev)
Copyright:
Copyright (c) 2013, the authors. All rights reserved.
License:
$(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
Source:
$(PHOBOSSRC std/process.d)
Macros:
OBJECTREF=$(REF1 $0, object)
Note:
Most of the functionality in this module is not available on iOS, tvOS
and watchOS. The only functions available on those platforms are:
$(LREF environment), $(LREF thisProcessID) and $(LREF thisThreadID).
*/
module std.process;
import core.thread : ThreadID;
version (Posix)
{
import core.sys.posix.sys.wait;
import core.sys.posix.unistd;
}
version (Windows)
{
import core.stdc.stdio;
import core.sys.windows.winbase;
import core.sys.windows.winnt;
import std.utf;
import std.windows.syserror;
}
import std.internal.cstring;
import std.range.primitives;
import std.stdio;
version (OSX)
version = Darwin;
else version (iOS)
{
version = Darwin;
version = iOSDerived;
}
else version (TVOS)
{
version = Darwin;
version = iOSDerived;
}
else version (WatchOS)
{
version = Darwin;
version = iOSDerived;
}
// When the DMC runtime is used, we have to use some custom functions
// to convert between Windows file handles and FILE*s.
version (Win32) version (CRuntime_DigitalMars) version = DMC_RUNTIME;
// Some of the following should be moved to druntime.
private
{
// Microsoft Visual C Runtime (MSVCRT) declarations.
version (Windows)
{
version (DMC_RUNTIME) { } else
{
import core.stdc.stdint;
enum
{
STDIN_FILENO = 0,
STDOUT_FILENO = 1,
STDERR_FILENO = 2,
}
}
}
// POSIX API declarations.
version (Posix)
{
version (Darwin)
{
extern(C) char*** _NSGetEnviron() nothrow;
const(char**) getEnvironPtr() @trusted
{
return *_NSGetEnviron;
}
}
else
{
// Made available by the C runtime:
extern(C) extern __gshared const char** environ;
const(char**) getEnvironPtr() @trusted
{
return environ;
}
}
@system unittest
{
import core.thread : Thread;
new Thread({assert(getEnvironPtr !is null);}).start();
}
}
} // private
// =============================================================================
// Environment variable manipulation.
// =============================================================================
/**
Manipulates _environment variables using an associative-array-like
interface.
This class contains only static methods, and cannot be instantiated.
See below for examples of use.
*/
abstract final class environment
{
static import core.sys.posix.stdlib;
import core.stdc.errno : errno, EINVAL;
static:
/**
Retrieves the value of the environment variable with the given `name`.
---
auto path = environment["PATH"];
---
Throws:
$(OBJECTREF Exception) if the environment variable does not exist,
or $(REF UTFException, std,utf) if the variable contains invalid UTF-16
characters (Windows only).
See_also:
$(LREF environment.get), which doesn't throw on failure.
*/
string opIndex(scope const(char)[] name) @safe
{
import std.exception : enforce;
string value;
enforce(getImpl(name, value), "Environment variable not found: "~name);
return value;
}
/**
Retrieves the value of the environment variable with the given `name`,
or a default value if the variable doesn't exist.
Unlike $(LREF environment.opIndex), this function never throws on Posix.
---
auto sh = environment.get("SHELL", "/bin/sh");
---
This function is also useful in checking for the existence of an
environment variable.
---
auto myVar = environment.get("MYVAR");
if (myVar is null)
{
// Environment variable doesn't exist.
// Note that we have to use 'is' for the comparison, since
// myVar == null is also true if the variable exists but is
// empty.
}
---
Params:
name = name of the environment variable to retrieve
defaultValue = default value to return if the environment variable doesn't exist.
Returns:
the value of the environment variable if found, otherwise
`null` if the environment doesn't exist.
Throws:
$(REF UTFException, std,utf) if the variable contains invalid UTF-16
characters (Windows only).
*/
string get(scope const(char)[] name, string defaultValue = null) @safe
{
string value;
auto found = getImpl(name, value);
return found ? value : defaultValue;
}
/**
Assigns the given `value` to the environment variable with the given
`name`.
If `value` is null the variable is removed from environment.
If the variable does not exist, it will be created. If it already exists,
it will be overwritten.
---
environment["foo"] = "bar";
---
Throws:
$(OBJECTREF Exception) if the environment variable could not be added
(e.g. if the name is invalid).
Note:
On some platforms, modifying environment variables may not be allowed in
multi-threaded programs. See e.g.
$(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc).
*/
inout(char)[] opIndexAssign(inout char[] value, scope const(char)[] name) @trusted
{
version (Posix)
{
import std.exception : enforce, errnoEnforce;
if (value is null)
{
remove(name);
return value;
}
if (core.sys.posix.stdlib.setenv(name.tempCString(), value.tempCString(), 1) != -1)
{
return value;
}
// The default errno error message is very uninformative
// in the most common case, so we handle it manually.
enforce(errno != EINVAL,
"Invalid environment variable name: '"~name~"'");
errnoEnforce(false,
"Failed to add environment variable");
assert(0);
}
else version (Windows)
{
import std.exception : enforce;
enforce(
SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW()),
sysErrorString(GetLastError())
);
return value;
}
else static assert(0);
}
/**
Removes the environment variable with the given `name`.
If the variable isn't in the environment, this function returns
successfully without doing anything.
Note:
On some platforms, modifying environment variables may not be allowed in
multi-threaded programs. See e.g.
$(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc).
*/
void remove(scope const(char)[] name) @trusted nothrow @nogc // TODO: @safe
{
version (Windows) SetEnvironmentVariableW(name.tempCStringW(), null);
else version (Posix) core.sys.posix.stdlib.unsetenv(name.tempCString());
else static assert(0);
}
/**
Identify whether a variable is defined in the environment.
Because it doesn't return the value, this function is cheaper than `get`.
However, if you do need the value as well, you should just check the
return of `get` for `null` instead of using this function first.
Example:
-------------
// good usage
if ("MY_ENV_FLAG" in environment)
doSomething();
// bad usage
if ("MY_ENV_VAR" in environment)
doSomething(environment["MY_ENV_VAR"]);
// do this instead
if (auto var = environment.get("MY_ENV_VAR"))
doSomething(var);
-------------
*/
bool opBinaryRight(string op : "in")(scope const(char)[] name) @trusted
{
version (Posix)
return core.sys.posix.stdlib.getenv(name.tempCString()) !is null;
else version (Windows)
{
SetLastError(NO_ERROR);
if (GetEnvironmentVariableW(name.tempCStringW, null, 0) > 0)
return true;
immutable err = GetLastError();
if (err == NO_ERROR)
return true; // zero-length environment variable on Wine / XP
if (err == ERROR_ENVVAR_NOT_FOUND)
return false;
// Some other Windows error, throw.
throw new WindowsException(err);
}
else static assert(0);
}
/**
Copies all environment variables into an associative array.
Windows_specific:
While Windows environment variable names are case insensitive, D's
built-in associative arrays are not. This function will store all
variable names in uppercase (e.g. `PATH`).
Throws:
$(OBJECTREF Exception) if the environment variables could not
be retrieved (Windows only).
*/
string[string] toAA() @trusted
{
import std.conv : to;
string[string] aa;
version (Posix)
{
auto environ = getEnvironPtr;
for (int i=0; environ[i] != null; ++i)
{
import std.string : indexOf;
immutable varDef = to!string(environ[i]);
immutable eq = indexOf(varDef, '=');
assert(eq >= 0);
immutable name = varDef[0 .. eq];
immutable value = varDef[eq+1 .. $];
// In POSIX, environment variables may be defined more
// than once. This is a security issue, which we avoid
// by checking whether the key already exists in the array.
// For more info:
// http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/environment-variables.html
if (name !in aa) aa[name] = value;
}
}
else version (Windows)
{
import std.exception : enforce;
import std.uni : toUpper;
auto envBlock = GetEnvironmentStringsW();
enforce(envBlock, "Failed to retrieve environment variables.");
scope(exit) FreeEnvironmentStringsW(envBlock);
for (int i=0; envBlock[i] != '\0'; ++i)
{
auto start = i;
while (envBlock[i] != '=') ++i;
immutable name = toUTF8(toUpper(envBlock[start .. i]));
start = i+1;
while (envBlock[i] != '\0') ++i;
// Ignore variables with empty names. These are used internally
// by Windows to keep track of each drive's individual current
// directory.
if (!name.length)
continue;
// Just like in POSIX systems, environment variables may be
// defined more than once in an environment block on Windows,
// and it is just as much of a security issue there. Moreso,
// in fact, due to the case insensensitivity of variable names,
// which is not handled correctly by all programs.
auto val = toUTF8(envBlock[start .. i]);
if (name !in aa) aa[name] = val is null ? "" : val;
}
}
else static assert(0);
return aa;
}
private:
// Retrieves the environment variable, returns false on failure.
bool getImpl(scope const(char)[] name, out string value) @trusted
{
version (Windows)
{
// first we ask windows how long the environment variable is,
// then we try to read it in to a buffer of that length. Lots
// of error conditions because the windows API is nasty.
import std.conv : to;
const namezTmp = name.tempCStringW();
WCHAR[] buf;
// clear error because GetEnvironmentVariable only says it sets it
// if the environment variable is missing, not on other errors.
SetLastError(NO_ERROR);
// len includes terminating null
immutable len = GetEnvironmentVariableW(namezTmp, null, 0);
if (len == 0)
{
immutable err = GetLastError();
if (err == ERROR_ENVVAR_NOT_FOUND)
return false;
if (err != NO_ERROR) // Some other Windows error, throw.
throw new WindowsException(err);
}
if (len <= 1)
{
value = "";
return true;
}
buf.length = len;
while (true)
{
// lenRead is either the number of bytes read w/o null - if buf was long enough - or
// the number of bytes necessary *including* null if buf wasn't long enough
immutable lenRead = GetEnvironmentVariableW(namezTmp, buf.ptr, to!DWORD(buf.length));
if (lenRead == 0)
{
immutable err = GetLastError();
if (err == NO_ERROR) // sucessfully read a 0-length variable
{
value = "";
return true;
}
if (err == ERROR_ENVVAR_NOT_FOUND) // variable didn't exist
return false;
// some other windows error
throw new WindowsException(err);
}
assert(lenRead != buf.length, "impossible according to msft docs");
if (lenRead < buf.length) // the buffer was long enough
{
value = toUTF8(buf[0 .. lenRead]);
return true;
}
// resize and go around again, because the environment variable grew
buf.length = lenRead;
}
}
else version (Posix)
{
import core.stdc.string : strlen;
const vz = core.sys.posix.stdlib.getenv(name.tempCString());
if (vz == null) return false;
auto v = vz[0 .. strlen(vz)];
// Cache the last call's result.
static string lastResult;
if (v.empty)
{
// Return non-null array for blank result to distinguish from
// not-present result.
lastResult = "";
}
else if (v != lastResult)
{
lastResult = v.idup;
}
value = lastResult;
return true;
}
else static assert(0);
}
}
@safe unittest
{
import std.exception : assertThrown;
// New variable
environment["std_process"] = "foo";
assert(environment["std_process"] == "foo");
assert("std_process" in environment);
// Set variable again (also tests length 1 case)
environment["std_process"] = "b";
assert(environment["std_process"] == "b");
assert("std_process" in environment);
// Remove variable
environment.remove("std_process");
assert("std_process" !in environment);
// Remove again, should succeed
environment.remove("std_process");
assert("std_process" !in environment);
// Throw on not found.
assertThrown(environment["std_process"]);
// get() without default value
assert(environment.get("std_process") is null);
// get() with default value
assert(environment.get("std_process", "baz") == "baz");
// get() on an empty (but present) value
environment["std_process"] = "";
auto res = environment.get("std_process");
assert(res !is null);
assert(res == "");
assert("std_process" in environment);
// Important to do the following round-trip after the previous test
// because it tests toAA with an empty var
// Convert to associative array
auto aa = environment.toAA();
assert(aa.length > 0);
foreach (n, v; aa)
{
// Wine has some bugs related to environment variables:
// - Wine allows the existence of an env. variable with the name
// "\0", but GetEnvironmentVariable refuses to retrieve it.
// As of 2.067 we filter these out anyway (see comment in toAA).
assert(v == environment[n]);
}
// ... and back again.
foreach (n, v; aa)
environment[n] = v;
// Complete the roundtrip
auto aa2 = environment.toAA();
import std.conv : text;
assert(aa == aa2, text(aa, " != ", aa2));
assert("std_process" in environment);
// Setting null must have the same effect as remove
environment["std_process"] = null;
assert("std_process" !in environment);
}
// =============================================================================
// Functions and classes for process management.
// =============================================================================
/**
* Returns the process ID of the current process,
* which is guaranteed to be unique on the system.
*
* Example:
* ---
* writefln("Current process ID: %d", thisProcessID);
* ---
*/
@property int thisProcessID() @trusted nothrow //TODO: @safe
{
version (Windows) return GetCurrentProcessId();
else version (Posix) return core.sys.posix.unistd.getpid();
}
/**
* Returns the process ID of the current thread,
* which is guaranteed to be unique within the current process.
*
* Returns:
* A $(REF ThreadID, core,thread) value for the calling thread.
*
* Example:
* ---
* writefln("Current thread ID: %s", thisThreadID);
* ---
*/
@property ThreadID thisThreadID() @trusted nothrow //TODO: @safe
{
version (Windows)
return GetCurrentThreadId();
else
version (Posix)
{
import core.sys.posix.pthread : pthread_self;
return pthread_self();
}
}
@system unittest
{
int pidA, pidB;
ThreadID tidA, tidB;
pidA = thisProcessID;
tidA = thisThreadID;
import core.thread;
auto t = new Thread({
pidB = thisProcessID;
tidB = thisThreadID;
});
t.start();
t.join();
assert(pidA == pidB);
assert(tidA != tidB);
}
version (iOSDerived) {}
else:
/**
Spawns a new process, optionally assigning it an arbitrary set of standard
input, output, and error streams.
The function returns immediately, leaving the child process to execute
in parallel with its parent. It is recommended to always call $(LREF wait)
on the returned $(LREF Pid) unless the process was spawned with
`Config.detached` flag, as detailed in the documentation for `wait`.
Command_line:
There are four overloads of this function. The first two take an array
of strings, `args`, which should contain the program name as the
zeroth element and any command-line arguments in subsequent elements.
The third and fourth versions are included for convenience, and may be
used when there are no command-line arguments. They take a single string,
`program`, which specifies the program name.
Unless a directory is specified in `args[0]` or `program`,
`spawnProcess` will search for the program in a platform-dependent
manner. On POSIX systems, it will look for the executable in the
directories listed in the PATH environment variable, in the order
they are listed. On Windows, it will search for the executable in
the following sequence:
$(OL
$(LI The directory from which the application loaded.)
$(LI The current directory for the parent process.)
$(LI The 32-bit Windows system directory.)
$(LI The 16-bit Windows system directory.)
$(LI The Windows directory.)
$(LI The directories listed in the PATH environment variable.)
)
---
// Run an executable called "prog" located in the current working
// directory:
auto pid = spawnProcess("./prog");
scope(exit) wait(pid);
// We can do something else while the program runs. The scope guard
// ensures that the process is waited for at the end of the scope.
...
// Run DMD on the file "myprog.d", specifying a few compiler switches:
auto dmdPid = spawnProcess(["dmd", "-O", "-release", "-inline", "myprog.d" ]);
if (wait(dmdPid) != 0)
writeln("Compilation failed!");
---
Environment_variables:
By default, the child process inherits the environment of the parent
process, along with any additional variables specified in the `env`
parameter. If the same variable exists in both the parent's environment
and in `env`, the latter takes precedence.
If the $(LREF Config.newEnv) flag is set in `config`, the child
process will $(I not) inherit the parent's environment. Its entire
environment will then be determined by `env`.
---
wait(spawnProcess("myapp", ["foo" : "bar"], Config.newEnv));
---
Standard_streams:
The optional arguments `stdin`, `stdout` and `stderr` may
be used to assign arbitrary $(REF File, std,stdio) objects as the standard
input, output and error streams, respectively, of the child process. The
former must be opened for reading, while the latter two must be opened for
writing. The default is for the child process to inherit the standard
streams of its parent.
---
// Run DMD on the file myprog.d, logging any error messages to a
// file named errors.log.
auto logFile = File("errors.log", "w");
auto pid = spawnProcess(["dmd", "myprog.d"],
std.stdio.stdin,
std.stdio.stdout,
logFile);
if (wait(pid) != 0)
writeln("Compilation failed. See errors.log for details.");
---
Note that if you pass a `File` object that is $(I not)
one of the standard input/output/error streams of the parent process,
that stream will by default be $(I closed) in the parent process when
this function returns. See the $(LREF Config) documentation below for
information about how to disable this behaviour.
Beware of buffering issues when passing `File` objects to
`spawnProcess`. The child process will inherit the low-level raw
read/write offset associated with the underlying file descriptor, but
it will not be aware of any buffered data. In cases where this matters
(e.g. when a file should be aligned before being passed on to the
child process), it may be a good idea to use unbuffered streams, or at
least ensure all relevant buffers are flushed.
Params:
args = An array which contains the program name as the zeroth element
and any command-line arguments in the following elements.
stdin = The standard input stream of the child process.
This can be any $(REF File, std,stdio) that is opened for reading.
By default the child process inherits the parent's input
stream.
stdout = The standard output stream of the child process.
This can be any $(REF File, std,stdio) that is opened for writing.
By default the child process inherits the parent's output stream.
stderr = The standard error stream of the child process.
This can be any $(REF File, std,stdio) that is opened for writing.
By default the child process inherits the parent's error stream.
env = Additional environment variables for the child process.
config = Flags that control process creation. See $(LREF Config)
for an overview of available flags.
workDir = The working directory for the new process.
By default the child process inherits the parent's working
directory.
Returns:
A $(LREF Pid) object that corresponds to the spawned process.
Throws:
$(LREF ProcessException) on failure to start the process.$(BR)
$(REF StdioException, std,stdio) on failure to pass one of the streams
to the child process (Windows only).$(BR)
$(REF RangeError, core,exception) if `args` is empty.
*/
Pid spawnProcess(scope const(char[])[] args,
File stdin = std.stdio.stdin,
File stdout = std.stdio.stdout,
File stderr = std.stdio.stderr,
const string[string] env = null,
Config config = Config.none,
scope const char[] workDir = null)
@trusted // TODO: Should be @safe
{
version (Windows) auto args2 = escapeShellArguments(args);
else version (Posix) alias args2 = args;
return spawnProcessImpl(args2, stdin, stdout, stderr, env, config, workDir);
}
/// ditto
Pid spawnProcess(scope const(char[])[] args,
const string[string] env,
Config config = Config.none,
scope const(char)[] workDir = null)
@trusted // TODO: Should be @safe
{
return spawnProcess(args,
std.stdio.stdin,
std.stdio.stdout,
std.stdio.stderr,
env,
config,
workDir);
}
/// ditto
Pid spawnProcess(scope const(char)[] program,
File stdin = std.stdio.stdin,
File stdout = std.stdio.stdout,
File stderr = std.stdio.stderr,
const string[string] env = null,
Config config = Config.none,
scope const(char)[] workDir = null)
@trusted
{
return spawnProcess((&program)[0 .. 1],
stdin, stdout, stderr, env, config, workDir);
}
/// ditto
Pid spawnProcess(scope const(char)[] program,
const string[string] env,
Config config = Config.none,
scope const(char)[] workDir = null)
@trusted
{
return spawnProcess((&program)[0 .. 1], env, config, workDir);
}
version (Posix) private enum InternalError : ubyte
{
noerror,
exec,
chdir,
getrlimit,
doubleFork,
malloc,
}
/*
Implementation of spawnProcess() for POSIX.
envz should be a zero-terminated array of zero-terminated strings
on the form "var=value".
*/
version (Posix)
private Pid spawnProcessImpl(scope const(char[])[] args,
File stdin,
File stdout,
File stderr,
scope const string[string] env,
Config config,
scope const(char)[] workDir)
@trusted // TODO: Should be @safe
{
import core.exception : RangeError;
import std.algorithm.searching : any;
import std.conv : text;
import std.path : isDirSeparator;
import std.string : toStringz;
if (args.empty) throw new RangeError();
const(char)[] name = args[0];
if (!any!isDirSeparator(name))
{
name = searchPathFor(name);
if (name is null)
throw new ProcessException(text("Executable file not found: ", args[0]));
}
// Convert program name and arguments to C-style strings.
auto argz = new const(char)*[args.length+1];
argz[0] = toStringz(name);
foreach (i; 1 .. args.length) argz[i] = toStringz(args[i]);
argz[$-1] = null;
// Prepare environment.
auto envz = createEnv(env, !(config & Config.newEnv));
// Open the working directory.
// We use open in the parent and fchdir in the child
// so that most errors (directory doesn't exist, not a directory)
// can be propagated as exceptions before forking.
int workDirFD = -1;
scope(exit) if (workDirFD >= 0) close(workDirFD);
if (workDir.length)
{
import core.sys.posix.fcntl : open, O_RDONLY, stat_t, fstat, S_ISDIR;
workDirFD = open(workDir.tempCString(), O_RDONLY);
if (workDirFD < 0)
throw ProcessException.newFromErrno("Failed to open working directory");
stat_t s;
if (fstat(workDirFD, &s) < 0)
throw ProcessException.newFromErrno("Failed to stat working directory");
if (!S_ISDIR(s.st_mode))
throw new ProcessException("Not a directory: " ~ cast(string) workDir);
}
static int getFD(ref File f) { return core.stdc.stdio.fileno(f.getFP()); }
// Get the file descriptors of the streams.
// These could potentially be invalid, but that is OK. If so, later calls
// to dup2() and close() will just silently fail without causing any harm.
auto stdinFD = getFD(stdin);
auto stdoutFD = getFD(stdout);
auto stderrFD = getFD(stderr);
// We don't have direct access to the errors that may happen in a child process.
// So we use this pipe to deliver them.
int[2] forkPipe;
if (core.sys.posix.unistd.pipe(forkPipe) == 0)
setCLOEXEC(forkPipe[1], true);
else
throw ProcessException.newFromErrno("Could not create pipe to check startup of child");
scope(exit) close(forkPipe[0]);
/*
To create detached process, we use double fork technique
but we don't have a direct access to the second fork pid from the caller side thus use a pipe.
We also can't reuse forkPipe for that purpose
because we can't predict the order in which pid and possible error will be written
since the first and the second forks will run in parallel.
*/
int[2] pidPipe;
if (config & Config.detached)
{
if (core.sys.posix.unistd.pipe(pidPipe) != 0)
throw ProcessException.newFromErrno("Could not create pipe to get process pid");
setCLOEXEC(pidPipe[1], true);
}
scope(exit) if (config & Config.detached) close(pidPipe[0]);
static void abortOnError(int forkPipeOut, InternalError errorType, int error) nothrow
{
core.sys.posix.unistd.write(forkPipeOut, &errorType, errorType.sizeof);
core.sys.posix.unistd.write(forkPipeOut, &error, error.sizeof);
close(forkPipeOut);
core.sys.posix.unistd._exit(1);
assert(0);
}
void closePipeWriteEnds()
{
close(forkPipe[1]);
if (config & Config.detached)
close(pidPipe[1]);
}
auto id = core.sys.posix.unistd.fork();
if (id < 0)
{
closePipeWriteEnds();
throw ProcessException.newFromErrno("Failed to spawn new process");
}
void forkChild() nothrow @nogc
{
static import core.sys.posix.stdio;
// Child process
// no need for the read end of pipe on child side
if (config & Config.detached)
close(pidPipe[0]);
close(forkPipe[0]);
immutable forkPipeOut = forkPipe[1];
immutable pidPipeOut = pidPipe[1];
// Set the working directory.
if (workDirFD >= 0)
{
if (fchdir(workDirFD) < 0)
{
// Fail. It is dangerous to run a program
// in an unexpected working directory.
abortOnError(forkPipeOut, InternalError.chdir, .errno);
}
close(workDirFD);
}
void execProcess()
{
// Redirect streams and close the old file descriptors.