1 
2 module detached;
3 
4 version(Posix) private {
5     import core.sys.posix.unistd;
6     import core.sys.posix.fcntl;
7     import core.stdc.errno;
8     
9     static import std.stdio;
10     import std.typecons : tuple, Tuple;
11     import std.process : environ;
12     
13     import findexecutable;
14 }
15 
16 public import std.process : ProcessException, Config;
17 public import std.stdio : File;
18 
19 version(Posix) private @nogc @trusted char* mallocToStringz(in char[] s) nothrow
20 {
21     import core.stdc.string : strncpy;
22     import core.stdc.stdlib : malloc;
23     auto sz = cast(char*)malloc(s.length + 1);
24     if (s !is null) {
25         strncpy(sz, s.ptr, s.length);
26     }
27     sz[s.length] = '\0';
28     return sz;
29 }
30 
31 version(Posix) unittest
32 {
33     import core.stdc.stdlib : free;
34     import core.stdc.string : strcmp;
35     auto s = mallocToStringz("string");
36     assert(strcmp(s, "string") == 0);
37     free(s);
38     
39     assert(strcmp(mallocToStringz(null), "") == 0);
40 }
41 
42 version(Posix) private @nogc @trusted char** createExecArgv(in char[][] args, in char[] filePath) nothrow {
43     import core.stdc.stdlib : malloc;
44     auto argv = cast(char**)malloc((args.length+1)*(char*).sizeof);
45     argv[0] = mallocToStringz(filePath);
46     foreach(i; 1..args.length) {
47         argv[i] = mallocToStringz(args[i]);
48     }
49     argv[args.length] = null;
50     return argv;
51 }
52 
53 version(Posix) unittest
54 {
55     import core.stdc.string : strcmp;
56     auto argv= createExecArgv(["program", "arg", "arg2"], "/absolute/path/program");
57     assert(strcmp(argv[0], "/absolute/path/program") == 0);
58     assert(strcmp(argv[1], "arg") == 0);
59     assert(strcmp(argv[2], "arg2") == 0);
60     assert(argv[3] is null);
61 }
62 
63 version(Posix) private @trusted void ignorePipeErrors() nothrow
64 {
65     import core.sys.posix.signal;
66     import core.stdc.string : memset;
67     
68     sigaction_t ignoreAction;
69     memset(&ignoreAction, 0, sigaction_t.sizeof);
70     ignoreAction.sa_handler = SIG_IGN;
71     sigaction(SIGPIPE, &ignoreAction, null);
72 }
73 
74 //from std.process
75 version(Posix) private void setCLOEXEC(int fd, bool on) nothrow @nogc
76 {
77     import core.sys.posix.fcntl : fcntl, F_GETFD, FD_CLOEXEC, F_SETFD;
78     auto flags = fcntl(fd, F_GETFD);
79     if (flags >= 0)
80     {
81         if (on) flags |= FD_CLOEXEC;
82         else    flags &= ~(cast(typeof(flags)) FD_CLOEXEC);
83         flags = fcntl(fd, F_SETFD, flags);
84     }
85     assert (flags != -1 || .errno == EBADF);
86 }
87 
88 //From std.process
89 version(Posix) private const(char*)* createEnv(const string[string] childEnv, bool mergeWithParentEnv)
90 {
91     // Determine the number of strings in the parent's environment.
92     int parentEnvLength = 0;
93     if (mergeWithParentEnv)
94     {
95         if (childEnv.length == 0) return environ;
96         while (environ[parentEnvLength] != null) ++parentEnvLength;
97     }
98 
99     // Convert the "new" variables to C-style strings.
100     auto envz = new const(char)*[parentEnvLength + childEnv.length + 1];
101     int pos = 0;
102     foreach (var, val; childEnv)
103         envz[pos++] = (var~'='~val~'\0').ptr;
104 
105     // Add the parent's environment.
106     foreach (environStr; environ[0 .. parentEnvLength])
107     {
108         int eqPos = 0;
109         while (environStr[eqPos] != '=' && environStr[eqPos] != '\0') ++eqPos;
110         if (environStr[eqPos] != '=') continue;
111         auto var = environStr[0 .. eqPos];
112         if (var in childEnv) continue;
113         envz[pos++] = environStr;
114     }
115     envz[pos] = null;
116     return envz.ptr;
117 }
118 
119 //From std.process
120 version(Posix) @system unittest
121 {
122     auto e1 = createEnv(null, false);
123     assert (e1 != null && *e1 == null);
124 
125     auto e2 = createEnv(null, true);
126     assert (e2 != null);
127     int i = 0;
128     for (; environ[i] != null; ++i)
129     {
130         assert (e2[i] != null);
131         import core.stdc.string;
132         assert (strcmp(e2[i], environ[i]) == 0);
133     }
134     assert (e2[i] == null);
135 
136     auto e3 = createEnv(["foo" : "bar", "hello" : "world"], false);
137     assert (e3 != null && e3[0] != null && e3[1] != null && e3[2] == null);
138     assert ((e3[0][0 .. 8] == "foo=bar\0" && e3[1][0 .. 12] == "hello=world\0")
139          || (e3[0][0 .. 12] == "hello=world\0" && e3[1][0 .. 8] == "foo=bar\0"));
140 }
141 
142 private enum InternalError : ubyte
143 {
144     noerror,
145     doubleFork,
146     exec,
147     chdir,
148     getrlimit,
149     environment
150 }
151 
152 version(Posix) private Tuple!(int, string) spawnProcessDetachedImpl(in char[][] args, 
153                                                      ref File stdin, ref File stdout, ref File stderr, 
154                                                      const string[string] env, 
155                                                      Config config, 
156                                                      in char[] workingDirectory, 
157                                                      ulong* pid) nothrow
158 {
159     import std.path : baseName;
160     import std.string : toStringz;
161     
162     string filePath = args[0].idup;
163     if (filePath.baseName == filePath) {
164         auto candidate = findExecutable(filePath);
165         if (!candidate.length) {
166             return tuple(ENOENT, "Could not find executable: " ~ filePath);
167         }
168         filePath = candidate;
169     }
170     
171     if (access(toStringz(filePath), X_OK) != 0) {
172         return tuple(.errno, "Not an executable file: " ~ filePath);
173     }
174     
175     static @trusted @nogc int safePipe(ref int[2] pipefds) nothrow
176     {
177         int result = pipe(pipefds);
178         if (result != 0) {
179             return result;
180         }
181         if (fcntl(pipefds[0], F_SETFD, FD_CLOEXEC) == -1 || fcntl(pipefds[1], F_SETFD, FD_CLOEXEC) == -1) {
182             close(pipefds[0]);
183             close(pipefds[1]);
184             return -1;
185         }
186         return result;
187     }
188     
189     int[2] execPipe, pidPipe;
190     if (safePipe(execPipe) != 0) {
191         return tuple(.errno, "Could not create pipe to check startup of child");
192     }
193     scope(exit) close(execPipe[0]);
194     if (safePipe(pidPipe) != 0) {
195         close(execPipe[1]);
196         return tuple(.errno, "Could not create pipe to get pid of child");
197     }
198     scope(exit) close(pidPipe[0]);
199     
200     int getFD(ref File f) { 
201         import core.stdc.stdio : fileno;
202         return fileno(f.getFP()); 
203     }
204     
205     int stdinFD, stdoutFD, stderrFD;
206     try {
207         stdinFD  = getFD(stdin);
208         stdoutFD = getFD(stdout);
209         stderrFD = getFD(stderr);
210     } catch(Exception e) {
211         return tuple(.errno ? .errno : EBADF, "Could not get file descriptors of standard streams");
212     }
213     
214     static void abortOnError(int execPipeOut, InternalError errorType, int error) nothrow {
215         error = error ? error : EINVAL;
216         write(execPipeOut, &errorType, errorType.sizeof);
217         write(execPipeOut, &error, error.sizeof);
218         close(execPipeOut);
219         _exit(1);
220     }
221     
222     pid_t firstFork = fork();
223     int lastError = .errno;
224     if (firstFork == 0) {
225         close(execPipe[0]);
226         close(pidPipe[0]);
227         
228         ignorePipeErrors();
229         setsid();
230         
231         int execPipeOut = execPipe[1];
232         int pidPipeOut = pidPipe[1];
233         
234         pid_t secondFork = fork();
235         if (secondFork == 0) {
236             close(pidPipeOut);
237             ignorePipeErrors();
238         
239             if (workingDirectory.length) {
240                 import core.stdc.stdlib : free;
241                 auto workDir = mallocToStringz(workingDirectory);
242                 if (chdir(workDir) == -1) {
243                     free(workDir);
244                     abortOnError(execPipeOut, InternalError.chdir, .errno);
245                 } else {
246                     free(workDir);
247                 }
248             }
249             
250             // ===== From std.process =====
251             if (stderrFD == STDOUT_FILENO) {
252                 stderrFD = dup(stderrFD);
253             }
254             dup2(stdinFD,  STDIN_FILENO);
255             dup2(stdoutFD, STDOUT_FILENO);
256             dup2(stderrFD, STDERR_FILENO);
257 
258             setCLOEXEC(STDIN_FILENO, false);
259             setCLOEXEC(STDOUT_FILENO, false);
260             setCLOEXEC(STDERR_FILENO, false);
261             
262             if (!(config & Config.inheritFDs)) {
263                 import core.sys.posix.poll : pollfd, poll, POLLNVAL;
264                 import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE;
265 
266                 rlimit r;
267                 if (getrlimit(RLIMIT_NOFILE, &r) != 0) {
268                     abortOnError(execPipeOut, InternalError.getrlimit, .errno);
269                 }
270                 immutable maxDescriptors = cast(int)r.rlim_cur;
271                 immutable maxToClose = maxDescriptors - 3;
272 
273                 @nogc nothrow static bool pollClose(int maxToClose, int dontClose)
274                 {
275                     import core.stdc.stdlib : alloca;
276 
277                     pollfd* pfds = cast(pollfd*)alloca(pollfd.sizeof * maxToClose);
278                     foreach (i; 0 .. maxToClose) {
279                         pfds[i].fd = i + 3;
280                         pfds[i].events = 0;
281                         pfds[i].revents = 0;
282                     }
283                     if (poll(pfds, maxToClose, 0) >= 0) {
284                         foreach (i; 0 .. maxToClose) {
285                             if (pfds[i].fd != dontClose && !(pfds[i].revents & POLLNVAL)) {
286                                 close(pfds[i].fd);
287                             }
288                         }
289                         return true;
290                     }
291                     else {
292                         return false;
293                     }
294                 }
295 
296                 if (!pollClose(maxToClose, execPipeOut)) {
297                     foreach (i; 3 .. maxDescriptors) {
298                         if (i != execPipeOut) {
299                             close(i);
300                         }
301                     }
302                 }
303             } else {
304                 if (stdinFD  > STDERR_FILENO)  close(stdinFD);
305                 if (stdoutFD > STDERR_FILENO)  close(stdoutFD);
306                 if (stderrFD > STDERR_FILENO)  close(stderrFD);
307             }
308             // =====================
309             
310             const(char*)* envz;
311             try {
312                 envz = createEnv(env, !(config & Config.newEnv));
313             } catch(Exception e) {
314                 abortOnError(execPipeOut, InternalError.environment, EINVAL);
315             }
316             auto argv = createExecArgv(args, filePath);
317             execve(argv[0], argv, envz);
318             abortOnError(execPipeOut, InternalError.exec, .errno);
319         }
320         
321         write(pidPipeOut, &secondFork, pid_t.sizeof);
322         close(pidPipeOut);
323         
324         if (secondFork == -1) {
325             abortOnError(execPipeOut, InternalError.doubleFork, .errno);
326         } else {
327             close(execPipeOut);
328             _exit(0);
329         }
330     }
331     
332     close(execPipe[1]);
333     close(pidPipe[1]);
334     
335     if (firstFork == -1) {
336         return tuple(lastError, "Could not fork");
337     }
338     
339     InternalError status;
340     auto readExecResult = read(execPipe[0], &status, status.sizeof);
341     lastError = .errno;
342     
343     import core.sys.posix.sys.wait : waitpid;
344     int waitResult;
345     waitpid(firstFork, &waitResult, 0);
346     
347     if (readExecResult == -1) {
348         return tuple(lastError, "Could not read from pipe to get child status");
349     }
350     
351     try {
352         if (!(config & Config.retainStdin ) && stdinFD  > STDERR_FILENO
353                                         && stdinFD  != getFD(std.stdio.stdin ))
354         stdin.close();
355         if (!(config & Config.retainStdout) && stdoutFD > STDERR_FILENO
356                                             && stdoutFD != getFD(std.stdio.stdout))
357             stdout.close();
358         if (!(config & Config.retainStderr) && stderrFD > STDERR_FILENO
359                                             && stderrFD != getFD(std.stdio.stderr))
360             stderr.close();
361     } catch(Exception e) {
362         
363     }
364     
365     if (status == 0) {
366         if (pid !is null) {
367             pid_t actualPid = 0;
368             if (read(pidPipe[0], &actualPid, pid_t.sizeof) >= 0) {
369                 *pid = actualPid;
370             } else {
371                 *pid = 0;
372             }
373         }
374         return tuple(0, "");
375     } else {
376         int error;
377         readExecResult = read(execPipe[0], &error, error.sizeof);
378         if (readExecResult == -1) {
379             return tuple(.errno, "Error occured but could not read exec errno from pipe");
380         }
381         switch(status) {
382             case InternalError.doubleFork: return tuple(error, "Could not fork twice");
383             case InternalError.exec: return tuple(error, "Could not exec");
384             case InternalError.chdir: return tuple(error, "Could not set working directory");
385             case InternalError.getrlimit: return tuple(error, "getrlimit");
386             case InternalError.environment: return tuple(error, "Could not set environment variables");
387             default:return tuple(error, "Unknown error occured");
388         }
389     }
390 }
391 
392 version(Posix) private string makeErrorMessage(string msg, int error) {
393     import core.stdc.string : strlen, strerror;
394     import std.format : format;
395     
396     version (CRuntime_Glibc)
397     {
398         import core.stdc.string : strerror_r;
399         char[1024] buf;
400         auto errnoMsg = strerror_r(error, buf.ptr, buf.length);
401     }
402     else
403     {
404         import core.stdc.string : strerror;
405         auto errnoMsg = strerror(error);
406     }
407     return format("%s: %s", msg, errnoMsg[0..strlen(errnoMsg)]);
408 }
409 
410 void spawnProcessDetached(in char[][] args, 
411                           File stdin = std.stdio.stdin, 
412                           File stdout = std.stdio.stdout, 
413                           File stderr = std.stdio.stderr, 
414                           const string[string] env = null, 
415                           Config config = Config.none, 
416                           in char[] workingDirectory = null, 
417                           ulong* pid = null)
418 {
419     import core.exception : RangeError;
420     if (args.length == 0) throw new RangeError();
421     auto result = spawnProcessDetachedImpl(args, stdin, stdout, stderr, env, config, workingDirectory, pid);
422     if (result[0] != 0) {
423         throw new ProcessException(makeErrorMessage(result[1], result[0]));
424     }
425 }
426 
427 version(Posix) unittest
428 {
429     import std.file;
430     import std.string;
431     import std.path;
432     
433     import std.stdio;
434     
435     try {
436         auto devNull = File("/dev/null", "rwb");
437         ulong pid;
438         spawnProcessDetached(["pwd"], devNull, devNull, devNull, null, Config.none, "./test", &pid);
439         assert(pid != 0);
440         
441         import std.exception;
442         assertThrown(spawnProcessDetached(["./test/nonexistent"]));
443         assertThrown(spawnProcessDetached(["./test/executable.sh"], devNull, devNull, devNull, null, Config.none, "./test/nonexistent"));
444         assertThrown(spawnProcessDetached(["./dub.json"]));
445         assertThrown(spawnProcessDetached(["./test/notreallyexecutable"]));
446     } catch(Exception e) {
447         
448     }
449 }
450 
451 void spawnProcessDetached(in char[][] args, const string[string] env, Config config = Config.none, in char[] workingDirectory = null, ulong* pid = null)
452 {
453     spawnProcessDetached(args, std.stdio.stdin, std.stdio.stdout, std.stdio.stderr, env, config, workingDirectory, pid);
454 }