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 }