1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 10 This module contains process funtions to use session groups when running 11 processes. 12 */ 13 module dextool.plugin.mutate.backend.linux_process; 14 15 import core.sys.posix.unistd : pid_t; 16 17 enum KillResult { 18 error, 19 success 20 } 21 22 struct Wait { 23 bool terminated; 24 int status; 25 } 26 27 struct PidSession { 28 import core.sys.posix.unistd : pid_t; 29 30 enum Status { 31 failed, 32 active, 33 } 34 35 Status status; 36 pid_t pid; 37 38 @disable this(this); 39 40 ~this() @safe nothrow @nogc { 41 import core.sys.posix.signal : SIGKILL; 42 43 killImpl(pid, SIGKILL); 44 } 45 } 46 47 /** Fork to a new session and process group. 48 * 49 * The first index of args must be the path to the program. 50 * The path must be absolute for the effect to be visible when running e.g. ps. 51 * 52 * trusted: the input and memory allocations use the GC and the facilities in D 53 * to keep array's safe. 54 * 55 * Params: 56 * args = arguments to run. 57 * stdout_p = write stdout to this file (if null then /dev/null is used) 58 * stderr_p = write stderr to this file (if null then /dev/null is used) 59 */ 60 PidSession spawnSession(const char[][] args, string stdout_p = null, 61 string stderr_p = null, bool debug_ = false) @trusted { 62 import core.stdc.stdlib : exit; 63 import core.sys.posix.unistd; 64 import core.sys.posix.signal; 65 import std.string : toStringz; 66 import std.stdio : File; 67 68 static import core.stdc.stdio; 69 70 static int getFD(ref File f) { 71 return core.stdc.stdio.fileno(f.getFP()); 72 } 73 74 // running a compiler/make etc requires stdin/out/err 75 auto stdin_ = File("/dev/null", "r"); 76 auto stdout_ = () { 77 if (stdout_p.length == 0) 78 return File("/dev/null", "w"); 79 else 80 return File(stdout_p, "w"); 81 }(); 82 auto stderr_ = () { 83 if (stderr_p.length == 0) 84 return File("/dev/null", "w"); 85 else 86 return File(stderr_p, "w"); 87 }(); 88 89 const(char*)* envz; 90 91 try { 92 envz = createEnv(null, true); 93 } catch (Exception e) { 94 return PidSession(); 95 } 96 97 // use the GC before forking to avoid possible problems with deadlocks 98 auto argz = new const(char)*[args.length + 1]; 99 foreach (i; 0 .. args.length) 100 argz[i] = toStringz(args[i]); 101 argz[$ - 1] = null; 102 103 const pid_t parent = getpid(); 104 105 // NO GC after this point. 106 auto pid = fork(); 107 if (pid < 0) { 108 // failed to fork 109 return PidSession(); 110 } else if (pid > 0) { 111 // parent 112 return PidSession(PidSession.Status.active, pid); 113 } 114 115 auto stdin_fd = getFD(stdin_); 116 auto stdout_fd = getFD(stdout_); 117 auto stderr_fd = getFD(stderr_); 118 119 dup2(stdin_fd, STDIN_FILENO); 120 dup2(stdout_fd, STDOUT_FILENO); 121 dup2(stderr_fd, STDERR_FILENO); 122 123 setCLOEXEC(STDIN_FILENO, false); 124 setCLOEXEC(STDOUT_FILENO, false); 125 setCLOEXEC(STDERR_FILENO, false); 126 127 auto sid = setsid(); 128 if (sid < 0) { 129 // unfortunately unable to inform the parent of the failure 130 exit(-1); 131 return PidSession(); 132 } 133 134 // note: if a pre execve function are to be called do it here. 135 136 auto sec_fork = fork(); 137 138 if (sec_fork < 0) { 139 // failed to fork 140 // unfortunately unable to inform the parent of the failure 141 exit(-1); 142 } else if (sec_fork == 0) { 143 // child 144 execve(argz[0], argz.ptr, envz); 145 } 146 147 const child = PidSession(PidSession.Status.active, sec_fork); 148 149 // poll the parent process to detect if the group become orphaned. 150 // suicide if it does. 151 while (true) { 152 if (parent != getppid()) { 153 // parent changed so is orphaned 154 import core.sys.posix.signal : SIGKILL; 155 156 killImpl(child.pid, SIGKILL); 157 killImpl(pid, SIGKILL); 158 exit(-1); // should never happen 159 } 160 161 // check the child 162 auto child_w = child.performWait(false); 163 if (child_w.terminated) 164 exit(child_w.status); 165 166 usleep(100); 167 } 168 } 169 170 /** 171 * trusted: no memory is accessed thus no memory unsafe operations are 172 * performed. 173 */ 174 private Wait performWait(const ref PidSession p, bool blocking) @trusted nothrow @nogc { 175 import core.sys.posix.unistd; 176 import core.sys.posix.sys.wait; 177 import core.stdc.errno : errno, ECHILD; 178 179 if (p.status != PidSession.Status.active) 180 return Wait(true); 181 182 int exitCode; 183 184 while (true) { 185 int status; 186 auto check = waitpid(p.pid, &status, blocking ? 0 : WNOHANG); 187 if (check == -1) { 188 if (errno == ECHILD) { 189 // process does not exist 190 return Wait(true, 0); 191 } else { 192 // interrupted by a signal 193 continue; 194 } 195 } 196 197 if (!blocking && check == 0) { 198 return Wait(false, 0); 199 } 200 201 if (WIFEXITED(status)) { 202 exitCode = WEXITSTATUS(status); 203 break; 204 } else if (WIFSIGNALED(status)) { 205 exitCode = -WTERMSIG(status); 206 break; 207 } 208 209 if (!blocking) 210 break; 211 } 212 213 return Wait(true, exitCode); 214 } 215 216 Wait tryWait(const ref PidSession p) @safe nothrow @nogc { 217 return performWait(p, false); 218 } 219 220 Wait wait(const ref PidSession p) @safe nothrow @nogc { 221 return performWait(p, true); 222 } 223 224 /** 225 * trusted: no memory is manipulated thus it is memory safe. 226 */ 227 KillResult kill(const ref PidSession p, int signal) @trusted nothrow @nogc { 228 if (p.status != PidSession.Status.active) 229 return KillResult.error; 230 231 return killImpl(p.pid, signal); 232 } 233 234 private KillResult killImpl(const pid_t p, int signal) @trusted nothrow @nogc { 235 import core.sys.posix.signal : killpg; 236 237 auto res = killpg(p, signal); 238 return res == 0 ? KillResult.success : KillResult.error; 239 } 240 241 // COPIED FROM PHOBOS. 242 private extern (C) extern __gshared const char** environ; 243 244 // Made available by the C runtime: 245 private const(char**) getEnvironPtr() @trusted { 246 return environ; 247 } 248 249 // Converts childEnv to a zero-terminated array of zero-terminated strings 250 // on the form "name=value", optionally adding those of the current process' 251 // environment strings that are not present in childEnv. If the parent's 252 // environment should be inherited without modification, this function 253 // returns environ directly. 254 private const(char*)* createEnv(const string[string] childEnv, bool mergeWithParentEnv) @trusted { 255 256 // Determine the number of strings in the parent's environment. 257 int parentEnvLength = 0; 258 auto environ = getEnvironPtr; 259 if (mergeWithParentEnv) { 260 if (childEnv.length == 0) 261 return environ; 262 while (environ[parentEnvLength] != null) 263 ++parentEnvLength; 264 } 265 266 // Convert the "new" variables to C-style strings. 267 auto envz = new const(char)*[parentEnvLength + childEnv.length + 1]; 268 int pos = 0; 269 foreach (var, val; childEnv) 270 envz[pos++] = (var ~ '=' ~ val ~ '\0').ptr; 271 272 // Add the parent's environment. 273 foreach (environStr; environ[0 .. parentEnvLength]) { 274 int eqPos = 0; 275 while (environStr[eqPos] != '=' && environStr[eqPos] != '\0') 276 ++eqPos; 277 if (environStr[eqPos] != '=') 278 continue; 279 auto var = environStr[0 .. eqPos]; 280 if (var in childEnv) 281 continue; 282 envz[pos++] = environStr; 283 } 284 envz[pos] = null; 285 return envz.ptr; 286 } 287 288 // Sets or unsets the FD_CLOEXEC flag on the given file descriptor. 289 private void setCLOEXEC(int fd, bool on) nothrow @nogc { 290 import core.stdc.errno : errno, EBADF; 291 import core.sys.posix.fcntl : fcntl, F_GETFD, FD_CLOEXEC, F_SETFD; 292 293 auto flags = fcntl(fd, F_GETFD); 294 if (flags >= 0) { 295 if (on) 296 flags |= FD_CLOEXEC; 297 else 298 flags &= ~(cast(typeof(flags)) FD_CLOEXEC); 299 flags = fcntl(fd, F_SETFD, flags); 300 } 301 assert(flags != -1 || errno == EBADF); 302 }