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 } 94 catch (Exception e) { 95 return PidSession(); 96 } 97 98 // use the GC before forking to avoid possible problems with deadlocks 99 auto argz = new const(char)*[args.length + 1]; 100 foreach (i; 0 .. args.length) 101 argz[i] = toStringz(args[i]); 102 argz[$ - 1] = null; 103 104 const pid_t parent = getpid(); 105 106 // NO GC after this point. 107 auto pid = fork(); 108 if (pid < 0) { 109 // failed to fork 110 return PidSession(); 111 } else if (pid > 0) { 112 // parent 113 return PidSession(PidSession.Status.active, pid); 114 } 115 116 auto stdin_fd = getFD(stdin_); 117 auto stdout_fd = getFD(stdout_); 118 auto stderr_fd = getFD(stderr_); 119 120 dup2(stdin_fd, STDIN_FILENO); 121 dup2(stdout_fd, STDOUT_FILENO); 122 dup2(stderr_fd, STDERR_FILENO); 123 124 setCLOEXEC(STDIN_FILENO, false); 125 setCLOEXEC(STDOUT_FILENO, false); 126 setCLOEXEC(STDERR_FILENO, false); 127 128 auto sid = setsid(); 129 if (sid < 0) { 130 // unfortunately unable to inform the parent of the failure 131 exit(-1); 132 return PidSession(); 133 } 134 135 // note: if a pre execve function are to be called do it here. 136 137 auto sec_fork = fork(); 138 139 if (sec_fork < 0) { 140 // failed to fork 141 // unfortunately unable to inform the parent of the failure 142 exit(-1); 143 } else if (sec_fork == 0) { 144 // child 145 execve(argz[0], argz.ptr, envz); 146 } 147 148 const child = PidSession(PidSession.Status.active, sec_fork); 149 150 // poll the parent process to detect if the group become orphaned. 151 // suicide if it does. 152 while (true) { 153 if (parent != getppid()) { 154 // parent changed so is orphaned 155 import core.sys.posix.signal : SIGKILL; 156 157 killImpl(child.pid, SIGKILL); 158 killImpl(pid, SIGKILL); 159 exit(-1); // should never happen 160 } 161 162 // check the child 163 auto child_w = child.performWait(false); 164 if (child_w.terminated) 165 exit(child_w.status); 166 167 usleep(100); 168 } 169 } 170 171 /** 172 * trusted: no memory is accessed thus no memory unsafe operations are 173 * performed. 174 */ 175 private Wait performWait(const ref PidSession p, bool blocking) @trusted nothrow @nogc { 176 import core.sys.posix.unistd; 177 import core.sys.posix.sys.wait; 178 import core.stdc.errno : errno, ECHILD; 179 180 if (p.status != PidSession.Status.active) 181 return Wait(true); 182 183 int exitCode; 184 185 while (true) { 186 int status; 187 auto check = waitpid(p.pid, &status, blocking ? 0 : WNOHANG); 188 if (check == -1) { 189 if (errno == ECHILD) { 190 // process does not exist 191 return Wait(true, 0); 192 } else { 193 // interrupted by a signal 194 continue; 195 } 196 } 197 198 if (!blocking && check == 0) { 199 return Wait(false, 0); 200 } 201 202 if (WIFEXITED(status)) { 203 exitCode = WEXITSTATUS(status); 204 break; 205 } else if (WIFSIGNALED(status)) { 206 exitCode = -WTERMSIG(status); 207 break; 208 } 209 210 if (!blocking) 211 break; 212 } 213 214 return Wait(true, exitCode); 215 } 216 217 Wait tryWait(const ref PidSession p) @safe nothrow @nogc { 218 return performWait(p, false); 219 } 220 221 Wait wait(const ref PidSession p) @safe nothrow @nogc { 222 return performWait(p, true); 223 } 224 225 /** 226 * trusted: no memory is manipulated thus it is memory safe. 227 */ 228 KillResult kill(const ref PidSession p, int signal) @trusted nothrow @nogc { 229 if (p.status != PidSession.Status.active) 230 return KillResult.error; 231 232 return killImpl(p.pid, signal); 233 } 234 235 private KillResult killImpl(const pid_t p, int signal) @trusted nothrow @nogc { 236 import core.sys.posix.signal : killpg; 237 238 auto res = killpg(p, signal); 239 return res == 0 ? KillResult.success : KillResult.error; 240 } 241 242 // COPIED FROM PHOBOS. 243 private extern (C) extern __gshared const char** environ; 244 245 // Made available by the C runtime: 246 private const(char**) getEnvironPtr() @trusted { 247 return environ; 248 } 249 250 // Converts childEnv to a zero-terminated array of zero-terminated strings 251 // on the form "name=value", optionally adding those of the current process' 252 // environment strings that are not present in childEnv. If the parent's 253 // environment should be inherited without modification, this function 254 // returns environ directly. 255 private const(char*)* createEnv(const string[string] childEnv, bool mergeWithParentEnv) @trusted { 256 257 // Determine the number of strings in the parent's environment. 258 int parentEnvLength = 0; 259 auto environ = getEnvironPtr; 260 if (mergeWithParentEnv) { 261 if (childEnv.length == 0) 262 return environ; 263 while (environ[parentEnvLength] != null) 264 ++parentEnvLength; 265 } 266 267 // Convert the "new" variables to C-style strings. 268 auto envz = new const(char)*[parentEnvLength + childEnv.length + 1]; 269 int pos = 0; 270 foreach (var, val; childEnv) 271 envz[pos++] = (var ~ '=' ~ val ~ '\0').ptr; 272 273 // Add the parent's environment. 274 foreach (environStr; environ[0 .. parentEnvLength]) { 275 int eqPos = 0; 276 while (environStr[eqPos] != '=' && environStr[eqPos] != '\0') 277 ++eqPos; 278 if (environStr[eqPos] != '=') 279 continue; 280 auto var = environStr[0 .. eqPos]; 281 if (var in childEnv) 282 continue; 283 envz[pos++] = environStr; 284 } 285 envz[pos] = null; 286 return envz.ptr; 287 } 288 289 // Sets or unsets the FD_CLOEXEC flag on the given file descriptor. 290 private void setCLOEXEC(int fd, bool on) nothrow @nogc { 291 import core.stdc.errno : errno, EBADF; 292 import core.sys.posix.fcntl : fcntl, F_GETFD, FD_CLOEXEC, F_SETFD; 293 294 auto flags = fcntl(fd, F_GETFD); 295 if (flags >= 0) { 296 if (on) 297 flags |= FD_CLOEXEC; 298 else 299 flags &= ~(cast(typeof(flags)) FD_CLOEXEC); 300 flags = fcntl(fd, F_SETFD, flags); 301 } 302 assert(flags != -1 || errno == EBADF); 303 }