• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2015 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // makeparallel communicates with the GNU make jobserver
16 // (http://make.mad-scientist.net/papers/jobserver-implementation/)
17 // in order claim all available jobs, and then passes the number of jobs
18 // claimed to a subprocess with -j<jobs>.
19 
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <getopt.h>
23 #include <poll.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <sys/time.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 
33 #include <string>
34 #include <vector>
35 
36 #ifdef __linux__
37 #include <error.h>
38 #endif
39 
40 #ifdef __APPLE__
41 #include <err.h>
42 #define error(code, eval, fmt, ...) errc(eval, code, fmt, ##__VA_ARGS__)
43 // Darwin does not interrupt syscalls by default.
44 #define TEMP_FAILURE_RETRY(exp) (exp)
45 #endif
46 
47 // Throw an error if fd is not valid.
CheckFd(int fd)48 static void CheckFd(int fd) {
49   int ret = fcntl(fd, F_GETFD);
50   if (ret < 0) {
51     if (errno == EBADF) {
52       error(errno, 0, "no jobserver pipe, prefix recipe command with '+'");
53     } else {
54       error(errno, errno, "fnctl failed");
55     }
56   }
57 }
58 
59 // Extract flags from MAKEFLAGS that need to be propagated to subproccess
ReadMakeflags()60 static std::vector<std::string> ReadMakeflags() {
61   std::vector<std::string> args;
62 
63   const char* makeflags_env = getenv("MAKEFLAGS");
64   if (makeflags_env == nullptr) {
65     return args;
66   }
67 
68   // The MAKEFLAGS format is pretty useless.  The first argument might be empty
69   // (starts with a leading space), or it might be a set of one-character flags
70   // merged together with no leading space, or it might be a variable
71   // definition.
72 
73   std::string makeflags = makeflags_env;
74 
75   // Split makeflags into individual args on spaces.  Multiple spaces are
76   // elided, but an initial space will result in a blank arg.
77   size_t base = 0;
78   size_t found;
79   do {
80     found = makeflags.find_first_of(" ", base);
81     args.push_back(makeflags.substr(base, found - base));
82     base = found + 1;
83   } while (found != makeflags.npos);
84 
85   // Drop the first argument if it is empty
86   while (args.size() > 0 && args[0].size() == 0) {
87 	  args.erase(args.begin());
88   }
89 
90   // Prepend a - to the first argument if it does not have one and is not a
91   // variable definition
92   if (args.size() > 0 && args[0][0] != '-') {
93     if (args[0].find('=') == makeflags.npos) {
94       args[0] = '-' + args[0];
95     }
96   }
97 
98   return args;
99 }
100 
ParseMakeflags(std::vector<std::string> & args,int * in_fd,int * out_fd,bool * parallel,bool * keep_going)101 static bool ParseMakeflags(std::vector<std::string>& args,
102     int* in_fd, int* out_fd, bool* parallel, bool* keep_going) {
103 
104   std::vector<char*> getopt_argv;
105   // getopt starts reading at argv[1]
106   getopt_argv.reserve(args.size() + 1);
107   getopt_argv.push_back(strdup(""));
108   for (std::string& v : args) {
109     getopt_argv.push_back(strdup(v.c_str()));
110   }
111 
112   opterr = 0;
113   optind = 1;
114   while (1) {
115     const static option longopts[] = {
116         {"jobserver-fds", required_argument, 0, 0},
117         {0, 0, 0, 0},
118     };
119     int longopt_index = 0;
120 
121     int c = getopt_long(getopt_argv.size(), getopt_argv.data(), "kj",
122         longopts, &longopt_index);
123 
124     if (c == -1) {
125       break;
126     }
127 
128     switch (c) {
129     case 0:
130       switch (longopt_index) {
131       case 0:
132       {
133         // jobserver-fds
134         if (sscanf(optarg, "%d,%d", in_fd, out_fd) != 2) {
135           error(EXIT_FAILURE, 0, "incorrect format for --jobserver-fds: %s", optarg);
136         }
137         // TODO: propagate in_fd, out_fd
138         break;
139       }
140       default:
141         abort();
142       }
143       break;
144     case 'j':
145       *parallel = true;
146       break;
147     case 'k':
148       *keep_going = true;
149       break;
150     case '?':
151       // ignore unknown arguments
152       break;
153     default:
154       abort();
155     }
156   }
157 
158   for (char *v : getopt_argv) {
159     free(v);
160   }
161 
162   return true;
163 }
164 
165 // Read a single byte from fd, with timeout in milliseconds.  Returns true if
166 // a byte was read, false on timeout.  Throws away the read value.
167 // Non-reentrant, uses timer and signal handler global state, plus static
168 // variable to communicate with signal handler.
169 //
170 // Uses a SIGALRM timer to fire a signal after timeout_ms that will interrupt
171 // the read syscall if it hasn't yet completed.  If the timer fires before the
172 // read the read could block forever, so read from a dup'd fd and close it from
173 // the signal handler, which will cause the read to return EBADF if it occurs
174 // after the signal.
175 // The dup/read/close combo is very similar to the system described to avoid
176 // a deadlock between SIGCHLD and read at
177 // http://make.mad-scientist.net/papers/jobserver-implementation/
ReadByteTimeout(int fd,int timeout_ms)178 static bool ReadByteTimeout(int fd, int timeout_ms) {
179   // global variable to communicate with the signal handler
180   static int dup_fd = -1;
181 
182   // dup the fd so the signal handler can close it without losing the real one
183   dup_fd = dup(fd);
184   if (dup_fd < 0) {
185     error(errno, errno, "dup failed");
186   }
187 
188   // set up a signal handler that closes dup_fd on SIGALRM
189   struct sigaction action = {};
190   action.sa_flags = SA_SIGINFO,
191   action.sa_sigaction = [](int, siginfo_t*, void*) {
192     close(dup_fd);
193   };
194   struct sigaction oldaction = {};
195   int ret = sigaction(SIGALRM, &action, &oldaction);
196   if (ret < 0) {
197     error(errno, errno, "sigaction failed");
198   }
199 
200   // queue a SIGALRM after timeout_ms
201   const struct itimerval timeout = {{}, {0, timeout_ms * 1000}};
202   ret = setitimer(ITIMER_REAL, &timeout, NULL);
203   if (ret < 0) {
204     error(errno, errno, "setitimer failed");
205   }
206 
207   // start the blocking read
208   char buf;
209   int read_ret = read(dup_fd, &buf, 1);
210   int read_errno = errno;
211 
212   // cancel the alarm in case it hasn't fired yet
213   const struct itimerval cancel = {};
214   ret = setitimer(ITIMER_REAL, &cancel, NULL);
215   if (ret < 0) {
216     error(errno, errno, "reset setitimer failed");
217   }
218 
219   // remove the signal handler
220   ret = sigaction(SIGALRM, &oldaction, NULL);
221   if (ret < 0) {
222     error(errno, errno, "reset sigaction failed");
223   }
224 
225   // clean up the dup'd fd in case the signal never fired
226   close(dup_fd);
227   dup_fd = -1;
228 
229   if (read_ret == 0) {
230     error(EXIT_FAILURE, 0, "EOF on jobserver pipe");
231   } else if (read_ret > 0) {
232     return true;
233   } else if (read_errno == EINTR || read_errno == EBADF) {
234     return false;
235   } else {
236     error(read_errno, read_errno, "read failed");
237   }
238   abort();
239 }
240 
241 // Measure the size of the jobserver pool by reading from in_fd until it blocks
GetJobserverTokens(int in_fd)242 static int GetJobserverTokens(int in_fd) {
243   int tokens = 0;
244   pollfd pollfds[] = {{in_fd, POLLIN, 0}};
245   int ret;
246   while ((ret = TEMP_FAILURE_RETRY(poll(pollfds, 1, 0))) != 0) {
247     if (ret < 0) {
248       error(errno, errno, "poll failed");
249     } else if (pollfds[0].revents != POLLIN) {
250       error(EXIT_FAILURE, 0, "unexpected event %d\n", pollfds[0].revents);
251     }
252 
253     // There is probably a job token in the jobserver pipe.  There is a chance
254     // another process reads it first, which would cause a blocking read to
255     // block forever (or until another process put a token back in the pipe).
256     // The file descriptor can't be set to O_NONBLOCK as that would affect
257     // all users of the pipe, including the parent make process.
258     // ReadByteTimeout emulates a non-blocking read on a !O_NONBLOCK socket
259     // using a SIGALRM that fires after a short timeout.
260     bool got_token = ReadByteTimeout(in_fd, 10);
261     if (!got_token) {
262       // No more tokens
263       break;
264     } else {
265       tokens++;
266     }
267   }
268 
269   // This process implicitly gets a token, so pool size is measured size + 1
270   return tokens;
271 }
272 
273 // Return tokens to the jobserver pool.
PutJobserverTokens(int out_fd,int tokens)274 static void PutJobserverTokens(int out_fd, int tokens) {
275   // Return all the tokens to the pipe
276   char buf = '+';
277   for (int i = 0; i < tokens; i++) {
278     int ret = TEMP_FAILURE_RETRY(write(out_fd, &buf, 1));
279     if (ret < 0) {
280       error(errno, errno, "write failed");
281     } else if (ret == 0) {
282       error(EXIT_FAILURE, 0, "EOF on jobserver pipe");
283     }
284   }
285 }
286 
main(int argc,char * argv[])287 int main(int argc, char* argv[]) {
288   int in_fd = -1;
289   int out_fd = -1;
290   bool parallel = false;
291   bool keep_going = false;
292   bool ninja = false;
293   int tokens = 0;
294 
295   if (argc > 1 && strcmp(argv[1], "--ninja") == 0) {
296     ninja = true;
297     argv++;
298     argc--;
299   }
300 
301   if (argc < 2) {
302     error(EXIT_FAILURE, 0, "expected command to run");
303   }
304 
305   const char* path = argv[1];
306   std::vector<char*> args({argv[1]});
307 
308   std::vector<std::string> makeflags = ReadMakeflags();
309   if (ParseMakeflags(makeflags, &in_fd, &out_fd, &parallel, &keep_going)) {
310     if (in_fd >= 0 && out_fd >= 0) {
311       CheckFd(in_fd);
312       CheckFd(out_fd);
313       fcntl(in_fd, F_SETFD, FD_CLOEXEC);
314       fcntl(out_fd, F_SETFD, FD_CLOEXEC);
315       tokens = GetJobserverTokens(in_fd);
316     }
317   }
318 
319   std::string jarg = "-j" + std::to_string(tokens + 1);
320 
321   if (ninja) {
322     if (!parallel) {
323       // ninja is parallel by default, pass -j1 to disable parallelism if make wasn't parallel
324       args.push_back(strdup("-j1"));
325     } else if (tokens > 0) {
326       args.push_back(strdup(jarg.c_str()));
327     }
328     if (keep_going) {
329       args.push_back(strdup("-k0"));
330     }
331   } else {
332     args.push_back(strdup(jarg.c_str()));
333   }
334 
335   args.insert(args.end(), &argv[2], &argv[argc]);
336 
337   args.push_back(nullptr);
338 
339   pid_t pid = fork();
340   if (pid < 0) {
341     error(errno, errno, "fork failed");
342   } else if (pid == 0) {
343     // child
344     unsetenv("MAKEFLAGS");
345     unsetenv("MAKELEVEL");
346     int ret = execvp(path, args.data());
347     if (ret < 0) {
348       error(errno, errno, "exec %s failed", path);
349     }
350     abort();
351   }
352 
353   // parent
354   siginfo_t status = {};
355   int exit_status = 0;
356   int ret = waitid(P_PID, pid, &status, WEXITED);
357   if (ret < 0) {
358     error(errno, errno, "waitpid failed");
359   } else if (status.si_code == CLD_EXITED) {
360     exit_status = status.si_status;
361   } else {
362     exit_status = -(status.si_status);
363   }
364 
365   if (tokens > 0) {
366     PutJobserverTokens(out_fd, tokens);
367   }
368   exit(exit_status);
369 }
370