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 }