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. 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 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 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/ 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 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. 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 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, ¶llel, &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