blob: 576fe8df8f87d393eaee74e6f6fb955c91e5e2a2 [file] [log] [blame]
Colin Crossa5554482015-09-05 21:16:19 -07001// 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>
Colin Cross69047fa2015-09-18 14:50:26 -070022#include <getopt.h>
Colin Crossa5554482015-09-05 21:16:19 -070023#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.
48static 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
Colin Cross69047fa2015-09-18 14:50:26 -070059// Extract flags from MAKEFLAGS that need to be propagated to subproccess
60static std::vector<std::string> ReadMakeflags() {
61 std::vector<std::string> args;
62
Colin Crossa5554482015-09-05 21:16:19 -070063 const char* makeflags_env = getenv("MAKEFLAGS");
64 if (makeflags_env == nullptr) {
Colin Cross69047fa2015-09-18 14:50:26 -070065 return args;
Colin Crossa5554482015-09-05 21:16:19 -070066 }
67
Colin Cross69047fa2015-09-18 14:50:26 -070068 // 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
Colin Crossa5554482015-09-05 21:16:19 -070073 std::string makeflags = makeflags_env;
74
Colin Cross69047fa2015-09-18 14:50:26 -070075 // 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);
Colin Crossa5554482015-09-05 21:16:19 -070084
Colin Cross69047fa2015-09-18 14:50:26 -070085 // Drop the first argument if it is empty
86 while (args.size() > 0 && args[0].size() == 0) {
87 args.erase(args.begin());
Colin Crossa5554482015-09-05 21:16:19 -070088 }
89
Colin Cross69047fa2015-09-18 14:50:26 -070090 // 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 }
Colin Crossa5554482015-09-05 21:16:19 -070096 }
97
Colin Cross69047fa2015-09-18 14:50:26 -070098 return args;
99}
Colin Crossa5554482015-09-05 21:16:19 -0700100
Colin Cross69047fa2015-09-18 14:50:26 -0700101static 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()));
Colin Crossa5554482015-09-05 21:16:19 -0700110 }
111
Colin Cross69047fa2015-09-18 14:50:26 -0700112 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 }
Colin Crossa5554482015-09-05 21:16:19 -0700161
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/
178static 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
242static 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.
274static 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
287int main(int argc, char* argv[]) {
288 int in_fd = -1;
289 int out_fd = -1;
Colin Cross69047fa2015-09-18 14:50:26 -0700290 bool parallel = false;
291 bool keep_going = false;
292 bool ninja = false;
Colin Crossa5554482015-09-05 21:16:19 -0700293 int tokens = 0;
294
Colin Cross69047fa2015-09-18 14:50:26 -0700295 if (argc > 1 && strcmp(argv[1], "--ninja") == 0) {
296 ninja = true;
297 argv++;
298 argc--;
299 }
300
Colin Cross466ea352015-10-20 15:41:05 -0700301 if (argc < 2) {
302 error(EXIT_FAILURE, 0, "expected command to run");
303 }
304
Colin Crossa5554482015-09-05 21:16:19 -0700305 const char* path = argv[1];
Colin Cross466ea352015-10-20 15:41:05 -0700306 std::vector<char*> args({argv[1]});
Colin Crossa5554482015-09-05 21:16:19 -0700307
Colin Cross69047fa2015-09-18 14:50:26 -0700308 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 }
Colin Crossa5554482015-09-05 21:16:19 -0700317 }
318
319 std::string jarg = "-j" + std::to_string(tokens + 1);
Colin Cross69047fa2015-09-18 14:50:26 -0700320
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
Colin Cross466ea352015-10-20 15:41:05 -0700335 args.insert(args.end(), &argv[2], &argv[argc]);
336
Colin Crossa5554482015-09-05 21:16:19 -0700337 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 int ret = execvp(path, args.data());
345 if (ret < 0) {
346 error(errno, errno, "exec failed");
347 }
348 abort();
349 }
350
351 // parent
352 siginfo_t status = {};
353 int exit_status = 0;
354 int ret = waitid(P_PID, pid, &status, WEXITED);
355 if (ret < 0) {
356 error(errno, errno, "waitpid failed");
357 } else if (status.si_code == CLD_EXITED) {
358 exit_status = status.si_status;
359 } else {
360 exit_status = -(status.si_status);
361 }
362
363 if (tokens > 0) {
364 PutJobserverTokens(out_fd, tokens);
365 }
366 exit(exit_status);
367}