blob: 4ae8f61d9dc89f13c0ae9effd15549b80088cd83 [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>
Colin Cross8f9a5322016-04-06 17:59:14 -070029#include <sys/resource.h>
Colin Crossa5554482015-09-05 21:16:19 -070030#include <sys/time.h>
31#include <sys/types.h>
32#include <sys/wait.h>
33
34#include <string>
35#include <vector>
36
37#ifdef __linux__
38#include <error.h>
39#endif
40
41#ifdef __APPLE__
42#include <err.h>
43#define error(code, eval, fmt, ...) errc(eval, code, fmt, ##__VA_ARGS__)
44// Darwin does not interrupt syscalls by default.
45#define TEMP_FAILURE_RETRY(exp) (exp)
46#endif
47
48// Throw an error if fd is not valid.
49static void CheckFd(int fd) {
50 int ret = fcntl(fd, F_GETFD);
51 if (ret < 0) {
52 if (errno == EBADF) {
53 error(errno, 0, "no jobserver pipe, prefix recipe command with '+'");
54 } else {
55 error(errno, errno, "fnctl failed");
56 }
57 }
58}
59
Colin Cross69047fa2015-09-18 14:50:26 -070060// Extract flags from MAKEFLAGS that need to be propagated to subproccess
61static std::vector<std::string> ReadMakeflags() {
62 std::vector<std::string> args;
63
Colin Crossa5554482015-09-05 21:16:19 -070064 const char* makeflags_env = getenv("MAKEFLAGS");
65 if (makeflags_env == nullptr) {
Colin Cross69047fa2015-09-18 14:50:26 -070066 return args;
Colin Crossa5554482015-09-05 21:16:19 -070067 }
68
Colin Cross69047fa2015-09-18 14:50:26 -070069 // The MAKEFLAGS format is pretty useless. The first argument might be empty
70 // (starts with a leading space), or it might be a set of one-character flags
71 // merged together with no leading space, or it might be a variable
72 // definition.
73
Colin Crossa5554482015-09-05 21:16:19 -070074 std::string makeflags = makeflags_env;
75
Colin Cross69047fa2015-09-18 14:50:26 -070076 // Split makeflags into individual args on spaces. Multiple spaces are
77 // elided, but an initial space will result in a blank arg.
78 size_t base = 0;
79 size_t found;
80 do {
81 found = makeflags.find_first_of(" ", base);
82 args.push_back(makeflags.substr(base, found - base));
83 base = found + 1;
84 } while (found != makeflags.npos);
Colin Crossa5554482015-09-05 21:16:19 -070085
Colin Cross69047fa2015-09-18 14:50:26 -070086 // Drop the first argument if it is empty
87 while (args.size() > 0 && args[0].size() == 0) {
88 args.erase(args.begin());
Colin Crossa5554482015-09-05 21:16:19 -070089 }
90
Colin Cross69047fa2015-09-18 14:50:26 -070091 // Prepend a - to the first argument if it does not have one and is not a
92 // variable definition
93 if (args.size() > 0 && args[0][0] != '-') {
94 if (args[0].find('=') == makeflags.npos) {
95 args[0] = '-' + args[0];
96 }
Colin Crossa5554482015-09-05 21:16:19 -070097 }
98
Colin Cross69047fa2015-09-18 14:50:26 -070099 return args;
100}
Colin Crossa5554482015-09-05 21:16:19 -0700101
Colin Cross69047fa2015-09-18 14:50:26 -0700102static bool ParseMakeflags(std::vector<std::string>& args,
103 int* in_fd, int* out_fd, bool* parallel, bool* keep_going) {
104
105 std::vector<char*> getopt_argv;
106 // getopt starts reading at argv[1]
107 getopt_argv.reserve(args.size() + 1);
108 getopt_argv.push_back(strdup(""));
109 for (std::string& v : args) {
110 getopt_argv.push_back(strdup(v.c_str()));
Colin Crossa5554482015-09-05 21:16:19 -0700111 }
112
Colin Cross69047fa2015-09-18 14:50:26 -0700113 opterr = 0;
114 optind = 1;
115 while (1) {
116 const static option longopts[] = {
117 {"jobserver-fds", required_argument, 0, 0},
118 {0, 0, 0, 0},
119 };
120 int longopt_index = 0;
121
122 int c = getopt_long(getopt_argv.size(), getopt_argv.data(), "kj",
123 longopts, &longopt_index);
124
125 if (c == -1) {
126 break;
127 }
128
129 switch (c) {
130 case 0:
131 switch (longopt_index) {
132 case 0:
133 {
134 // jobserver-fds
135 if (sscanf(optarg, "%d,%d", in_fd, out_fd) != 2) {
136 error(EXIT_FAILURE, 0, "incorrect format for --jobserver-fds: %s", optarg);
137 }
138 // TODO: propagate in_fd, out_fd
139 break;
140 }
141 default:
142 abort();
143 }
144 break;
145 case 'j':
146 *parallel = true;
147 break;
148 case 'k':
149 *keep_going = true;
150 break;
151 case '?':
152 // ignore unknown arguments
153 break;
154 default:
155 abort();
156 }
157 }
158
159 for (char *v : getopt_argv) {
160 free(v);
161 }
Colin Crossa5554482015-09-05 21:16:19 -0700162
163 return true;
164}
165
166// Read a single byte from fd, with timeout in milliseconds. Returns true if
167// a byte was read, false on timeout. Throws away the read value.
168// Non-reentrant, uses timer and signal handler global state, plus static
169// variable to communicate with signal handler.
170//
171// Uses a SIGALRM timer to fire a signal after timeout_ms that will interrupt
172// the read syscall if it hasn't yet completed. If the timer fires before the
173// read the read could block forever, so read from a dup'd fd and close it from
174// the signal handler, which will cause the read to return EBADF if it occurs
175// after the signal.
176// The dup/read/close combo is very similar to the system described to avoid
177// a deadlock between SIGCHLD and read at
178// http://make.mad-scientist.net/papers/jobserver-implementation/
179static bool ReadByteTimeout(int fd, int timeout_ms) {
180 // global variable to communicate with the signal handler
181 static int dup_fd = -1;
182
183 // dup the fd so the signal handler can close it without losing the real one
184 dup_fd = dup(fd);
185 if (dup_fd < 0) {
186 error(errno, errno, "dup failed");
187 }
188
189 // set up a signal handler that closes dup_fd on SIGALRM
190 struct sigaction action = {};
191 action.sa_flags = SA_SIGINFO,
192 action.sa_sigaction = [](int, siginfo_t*, void*) {
193 close(dup_fd);
194 };
195 struct sigaction oldaction = {};
196 int ret = sigaction(SIGALRM, &action, &oldaction);
197 if (ret < 0) {
198 error(errno, errno, "sigaction failed");
199 }
200
201 // queue a SIGALRM after timeout_ms
202 const struct itimerval timeout = {{}, {0, timeout_ms * 1000}};
203 ret = setitimer(ITIMER_REAL, &timeout, NULL);
204 if (ret < 0) {
205 error(errno, errno, "setitimer failed");
206 }
207
208 // start the blocking read
209 char buf;
210 int read_ret = read(dup_fd, &buf, 1);
211 int read_errno = errno;
212
213 // cancel the alarm in case it hasn't fired yet
214 const struct itimerval cancel = {};
215 ret = setitimer(ITIMER_REAL, &cancel, NULL);
216 if (ret < 0) {
217 error(errno, errno, "reset setitimer failed");
218 }
219
220 // remove the signal handler
221 ret = sigaction(SIGALRM, &oldaction, NULL);
222 if (ret < 0) {
223 error(errno, errno, "reset sigaction failed");
224 }
225
226 // clean up the dup'd fd in case the signal never fired
227 close(dup_fd);
228 dup_fd = -1;
229
230 if (read_ret == 0) {
231 error(EXIT_FAILURE, 0, "EOF on jobserver pipe");
232 } else if (read_ret > 0) {
233 return true;
234 } else if (read_errno == EINTR || read_errno == EBADF) {
235 return false;
236 } else {
237 error(read_errno, read_errno, "read failed");
238 }
239 abort();
240}
241
242// Measure the size of the jobserver pool by reading from in_fd until it blocks
243static int GetJobserverTokens(int in_fd) {
244 int tokens = 0;
245 pollfd pollfds[] = {{in_fd, POLLIN, 0}};
246 int ret;
247 while ((ret = TEMP_FAILURE_RETRY(poll(pollfds, 1, 0))) != 0) {
248 if (ret < 0) {
249 error(errno, errno, "poll failed");
250 } else if (pollfds[0].revents != POLLIN) {
251 error(EXIT_FAILURE, 0, "unexpected event %d\n", pollfds[0].revents);
252 }
253
254 // There is probably a job token in the jobserver pipe. There is a chance
255 // another process reads it first, which would cause a blocking read to
256 // block forever (or until another process put a token back in the pipe).
257 // The file descriptor can't be set to O_NONBLOCK as that would affect
258 // all users of the pipe, including the parent make process.
259 // ReadByteTimeout emulates a non-blocking read on a !O_NONBLOCK socket
260 // using a SIGALRM that fires after a short timeout.
261 bool got_token = ReadByteTimeout(in_fd, 10);
262 if (!got_token) {
263 // No more tokens
264 break;
265 } else {
266 tokens++;
267 }
268 }
269
270 // This process implicitly gets a token, so pool size is measured size + 1
271 return tokens;
272}
273
274// Return tokens to the jobserver pool.
275static void PutJobserverTokens(int out_fd, int tokens) {
276 // Return all the tokens to the pipe
277 char buf = '+';
278 for (int i = 0; i < tokens; i++) {
279 int ret = TEMP_FAILURE_RETRY(write(out_fd, &buf, 1));
280 if (ret < 0) {
281 error(errno, errno, "write failed");
282 } else if (ret == 0) {
283 error(EXIT_FAILURE, 0, "EOF on jobserver pipe");
284 }
285 }
286}
287
288int main(int argc, char* argv[]) {
289 int in_fd = -1;
290 int out_fd = -1;
Colin Cross69047fa2015-09-18 14:50:26 -0700291 bool parallel = false;
292 bool keep_going = false;
293 bool ninja = false;
Colin Crossa5554482015-09-05 21:16:19 -0700294 int tokens = 0;
295
Colin Cross69047fa2015-09-18 14:50:26 -0700296 if (argc > 1 && strcmp(argv[1], "--ninja") == 0) {
297 ninja = true;
298 argv++;
299 argc--;
300 }
301
Colin Cross466ea352015-10-20 15:41:05 -0700302 if (argc < 2) {
303 error(EXIT_FAILURE, 0, "expected command to run");
304 }
305
Colin Crossa5554482015-09-05 21:16:19 -0700306 const char* path = argv[1];
Colin Cross466ea352015-10-20 15:41:05 -0700307 std::vector<char*> args({argv[1]});
Colin Crossa5554482015-09-05 21:16:19 -0700308
Colin Cross69047fa2015-09-18 14:50:26 -0700309 std::vector<std::string> makeflags = ReadMakeflags();
310 if (ParseMakeflags(makeflags, &in_fd, &out_fd, &parallel, &keep_going)) {
311 if (in_fd >= 0 && out_fd >= 0) {
312 CheckFd(in_fd);
313 CheckFd(out_fd);
314 fcntl(in_fd, F_SETFD, FD_CLOEXEC);
315 fcntl(out_fd, F_SETFD, FD_CLOEXEC);
316 tokens = GetJobserverTokens(in_fd);
317 }
Colin Crossa5554482015-09-05 21:16:19 -0700318 }
319
320 std::string jarg = "-j" + std::to_string(tokens + 1);
Colin Cross69047fa2015-09-18 14:50:26 -0700321
322 if (ninja) {
323 if (!parallel) {
324 // ninja is parallel by default, pass -j1 to disable parallelism if make wasn't parallel
325 args.push_back(strdup("-j1"));
326 } else if (tokens > 0) {
327 args.push_back(strdup(jarg.c_str()));
328 }
329 if (keep_going) {
330 args.push_back(strdup("-k0"));
331 }
332 } else {
333 args.push_back(strdup(jarg.c_str()));
334 }
335
Colin Cross466ea352015-10-20 15:41:05 -0700336 args.insert(args.end(), &argv[2], &argv[argc]);
337
Colin Crossa5554482015-09-05 21:16:19 -0700338 args.push_back(nullptr);
339
Colin Cross49c45ae2016-10-03 13:40:32 -0700340 static pid_t pid;
341
342 // Set up signal handlers to forward SIGHUP, SIGINT, SIGQUIT, SIGTERM, and
343 // SIGALRM to child
344 struct sigaction action = {};
345 action.sa_flags = SA_SIGINFO | SA_RESTART,
346 action.sa_sigaction = [](int signal, siginfo_t*, void*) {
347 if (pid > 0) {
348 kill(pid, signal);
349 }
350 };
351
352 int ret = 0;
353 if (!ret) ret = sigaction(SIGHUP, &action, NULL);
354 if (!ret) ret = sigaction(SIGINT, &action, NULL);
355 if (!ret) ret = sigaction(SIGQUIT, &action, NULL);
356 if (!ret) ret = sigaction(SIGTERM, &action, NULL);
357 if (!ret) ret = sigaction(SIGALRM, &action, NULL);
358 if (ret < 0) {
359 error(errno, errno, "sigaction failed");
360 }
361
362 pid = fork();
Colin Crossa5554482015-09-05 21:16:19 -0700363 if (pid < 0) {
364 error(errno, errno, "fork failed");
365 } else if (pid == 0) {
366 // child
Colin Crossd8f0d682016-02-05 00:10:20 -0800367 unsetenv("MAKEFLAGS");
368 unsetenv("MAKELEVEL");
Colin Cross8f9a5322016-04-06 17:59:14 -0700369
370 // make 3.81 sets the stack ulimit to unlimited, which may cause problems
371 // for child processes
372 struct rlimit rlim{};
373 if (getrlimit(RLIMIT_STACK, &rlim) == 0 && rlim.rlim_cur == RLIM_INFINITY) {
374 rlim.rlim_cur = 8*1024*1024;
375 setrlimit(RLIMIT_STACK, &rlim);
376 }
377
Colin Crossa5554482015-09-05 21:16:19 -0700378 int ret = execvp(path, args.data());
379 if (ret < 0) {
Colin Cross28624582016-01-11 13:01:01 -0800380 error(errno, errno, "exec %s failed", path);
Colin Crossa5554482015-09-05 21:16:19 -0700381 }
382 abort();
383 }
384
385 // parent
Colin Cross49c45ae2016-10-03 13:40:32 -0700386
Colin Crossa5554482015-09-05 21:16:19 -0700387 siginfo_t status = {};
388 int exit_status = 0;
Colin Cross49c45ae2016-10-03 13:40:32 -0700389 ret = waitid(P_PID, pid, &status, WEXITED);
Colin Crossa5554482015-09-05 21:16:19 -0700390 if (ret < 0) {
391 error(errno, errno, "waitpid failed");
392 } else if (status.si_code == CLD_EXITED) {
393 exit_status = status.si_status;
394 } else {
395 exit_status = -(status.si_status);
396 }
397
398 if (tokens > 0) {
399 PutJobserverTokens(out_fd, tokens);
400 }
401 exit(exit_status);
402}