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