1 /**
2  * Copyright: Copyright Jason White, 2016
3  * License:   MIT
4  * Authors:   Jason White
5  *
6  * Description:
7  * Delegates dependency detection to the child process.
8  *
9  * This is done by creating pipes for the child process to send back the
10  * dependency information. The environment variables BUTTON_INPUTS and
11  * BUTTON_OUTPUTS are set to the file descriptors that the child should write
12  * to. This is also useful for the child process to determine if it is running
13  * under this build system or not. The child only needs to check if both of
14  * those environment variables are set.
15  *
16  * This handler should be used for commands that know how to communicate with
17  * Button. It is also commonly used by other handlers to run the command.
18  */
19 module button.handlers.base;
20 
21 import button.events;
22 import button.resource;
23 import button.context;
24 
25 // Open /dev/null to be used by all child processes as its standard input.
26 version (Posix)
27 {
28     private __gshared static int devnull;
29 
30     shared static this()
31     {
32         import io.file.stream : sysEnforce;
33         import core.sys.posix.fcntl : open, O_RDONLY;
34         devnull = open("/dev/null", O_RDONLY);
35         sysEnforce(devnull != -1, "Failed to open /dev/null");
36     }
37 
38     shared static ~this()
39     {
40         import core.sys.posix.unistd : close;
41         close(devnull);
42     }
43 }
44 
45 version (Posix)
46 void execute(
47         ref BuildContext ctx,
48         const(string)[] args,
49         string workDir,
50         ref Resources inputs,
51         ref Resources outputs
52         )
53 {
54     // FIXME: Commands should use a separate logger. It only uses the
55     // Events interface because there used to never be more than one command in
56     // a task.
57 
58     import core.sys.posix.unistd;
59     import core.stdc.stdio : sprintf;
60 
61     import io.file.stream : sysEnforce;
62 
63     import std.string : toStringz;
64     import std.array : array;
65 
66     import button.deps : deps;
67     import button.exceptions : CommandError;
68 
69     int[2] stdfds, inputfds, outputfds;
70 
71     sysEnforce(pipe(stdfds)    != -1); // Standard output
72     sysEnforce(pipe(inputfds)  != -1); // Implicit inputs
73     sysEnforce(pipe(outputfds) != -1); // Implicit outputs
74 
75     // Convert D command argument list to a null-terminated argument list
76     auto argv = new const(char)*[args.length+1];
77     foreach (i; 0 .. args.length)
78         argv[i] = toStringz(args[i]);
79     argv[$-1] = null;
80 
81     // Working directory
82     const(char)* cwd = null;
83     if (workDir.length)
84         cwd = workDir.toStringz();
85 
86     char[16] inputsenv, outputsenv;
87     sprintf(inputsenv.ptr, "%d", inputfds[1]);
88     sprintf(outputsenv.ptr, "%d", outputfds[1]);
89 
90     immutable pid = fork();
91     sysEnforce(pid >= 0, "Failed to fork current process");
92 
93     // Child process
94     if (pid == 0)
95     {
96         close(stdfds[0]);
97         close(inputfds[0]);
98         close(outputfds[0]);
99 
100         executeChild(argv, cwd, devnull, stdfds[1], inputfds[1],
101                 outputfds[1], inputsenv.ptr, outputsenv.ptr);
102 
103         // Unreachable
104     }
105 
106     // In the parent process
107     close(stdfds[1]);
108     close(inputfds[1]);
109     close(outputfds[1]);
110 
111     immutable worker = ctx.pool.workerIndex;
112 
113     // TODO: Parse the resources as they come in instead of all at once at
114     // the end.
115     auto implicit = readOutput(stdfds[0], inputfds[0], outputfds[0], worker,
116             ctx.events);
117 
118     // Add the inputs and outputs
119     inputs.put(implicit.inputs.deps);
120     outputs.put(implicit.outputs.deps);
121 
122     // Wait for the child to exit
123     immutable exitCode = waitFor(pid);
124 
125     if (exitCode != 0)
126         throw new CommandError(exitCode);
127 }
128 
129 private version (Posix)
130 {
131     import std.array : Appender;
132 
133     auto readOutput(int stdfd, int inputsfd, int outputsfd, size_t worker, Events events)
134     {
135         import std.array : appender;
136         import std.algorithm : max;
137         import std.typecons : tuple;
138         import std.exception : assumeUnique;
139 
140         import core.stdc.errno;
141         import core.sys.posix.unistd;
142         import core.sys.posix.sys.select;
143 
144         import io.file.stream : SysException;
145 
146         ubyte[4096] buf;
147         fd_set readfds = void;
148 
149         auto inputs  = appender!(ubyte[]);
150         auto outputs = appender!(ubyte[]);
151 
152         while (true)
153         {
154             FD_ZERO(&readfds);
155 
156             int nfds = 0;
157 
158             if (stdfd != -1)
159             {
160                 FD_SET(stdfd, &readfds);
161                 nfds = max(nfds, stdfd);
162             }
163 
164             if (inputsfd != -1)
165             {
166                 FD_SET(inputsfd, &readfds);
167                 nfds = max(nfds, inputsfd);
168             }
169 
170             if (outputsfd != -1)
171             {
172                 FD_SET(outputsfd, &readfds);
173                 nfds = max(nfds, outputsfd);
174             }
175 
176             if (nfds == 0)
177                 break;
178 
179             immutable r = select(nfds + 1, &readfds, null, null, null);
180 
181             if (r == -1)
182             {
183                 if (errno == EINTR)
184                     continue;
185 
186                 throw new SysException("select() failed");
187             }
188 
189             if (r == 0) break; // Nothing in the set
190 
191             // Read stdout/stderr from child
192             if (FD_ISSET(stdfd, &readfds))
193             {
194                 immutable len = read(stdfd, buf.ptr, buf.length);
195                 if (len > 0)
196                 {
197                     events.taskOutput(worker, buf[0 .. len]);
198                 }
199                 else
200                 {
201                     close(stdfd);
202                     stdfd = -1;
203                 }
204             }
205 
206             // Read inputs from child
207             if (FD_ISSET(inputsfd, &readfds))
208                 readFromChild(inputsfd, inputs, buf);
209 
210             // Read inputs from child
211             if (FD_ISSET(outputsfd, &readfds))
212                 readFromChild(outputsfd, outputs, buf);
213         }
214 
215         return tuple!("inputs", "outputs")(
216                 assumeUnique(inputs.data),
217                 assumeUnique(outputs.data)
218                 );
219     }
220 
221     void readFromChild(ref int fd, ref Appender!(ubyte[]) a, ubyte[] buf)
222     {
223         import core.sys.posix.unistd : read, close;
224 
225         immutable len = read(fd, buf.ptr, buf.length);
226 
227         if (len > 0)
228         {
229             a.put(buf[0 .. len]);
230         }
231         else
232         {
233             // Either the other end of the pipe was closed or the end of the
234             // stream was reached.
235             close(fd);
236             fd = -1;
237         }
238     }
239 
240     int waitFor(int pid)
241     {
242         import core.sys.posix.sys.wait;
243         import core.stdc.errno;
244         import io.file.stream : SysException;
245 
246         while (true)
247         {
248             int status;
249             immutable check = waitpid(pid, &status, 0) == -1;
250             if (check == -1)
251             {
252                 if (errno == ECHILD)
253                 {
254                     throw new SysException("Child process does not exist");
255                 }
256                 else
257                 {
258                     // Keep waiting
259                     assert(errno == EINTR);
260                     continue;
261                 }
262             }
263 
264             if (WIFEXITED(status))
265                 return WEXITSTATUS(status);
266             else if (WIFSIGNALED(status))
267                 return -WTERMSIG(status);
268         }
269     }
270 
271     /**
272      * Executes the child process. This is called after the fork().
273      *
274      * NOTE: Memory should not be allocated here. It can cause the child process
275      * to hang.
276      */
277     void executeChild(const(char*)[] argv, const(char)* cwd,
278             int devnull, int stdfd,
279             int inputsfd, int outputsfd,
280             const(char)* inputsenv, const(char)* outputsenv)
281     {
282         import core.sys.posix.unistd;
283         import core.sys.posix.stdlib : setenv;
284         import core.stdc.stdio : perror, stderr, fprintf;
285         import core.stdc.string : strerror;
286         import core.stdc.errno : errno;
287 
288         // Get standard input from /dev/null. With potentially multiple tasks
289         // executing in parallel, the child cannot use standard input.
290         if (dup2(devnull, STDIN_FILENO) == -1)
291         {
292             perror("dup2");
293             _exit(1);
294         }
295 
296         close(devnull);
297 
298         // Let the child know two bits of information: (1) that it is being run
299         // under this build system and (2) which file descriptors to use to send
300         // back dependency information.
301         setenv("BUTTON_INPUTS", inputsenv, 1);
302         setenv("BUTTON_OUTPUTS", outputsenv, 1);
303 
304         // Redirect stdout/stderr to the pipe the parent reads from. There is no
305         // differentiation between stdout and stderr.
306         if (dup2(stdfd, STDOUT_FILENO) == -1)
307         {
308             perror("dup2");
309             _exit(1);
310         }
311 
312         if (dup2(stdfd, STDERR_FILENO) == -1)
313         {
314             perror("dup2");
315             _exit(1);
316         }
317 
318         close(stdfd);
319 
320         if (cwd && (chdir(cwd) != 0))
321         {
322             fprintf(stderr, "button: Error: Invalid working directory '%s' (%s)\n",
323                     cwd, strerror(errno));
324             _exit(1);
325         }
326 
327         execvp(argv[0], argv.ptr);
328 
329         // If we get this far, something went wrong. Most likely, the command does
330         // not exist.
331         fprintf(stderr, "button: Failed executing process '%s' (%s)\n",
332                 argv[0], strerror(errno));
333         _exit(1);
334     }
335 }