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 }