VirtualBox

source: kBuild/trunk/src/kmk/posixos.c

Last change on this file was 3581, checked in by bird, 17 months ago

kmk/posixos.c: Try fix race in make_job_rfd.

  • Property svn:eol-style set to native
File size: 12.0 KB
Line 
1/* POSIX-based operating system interface for GNU Make.
2Copyright (C) 2016 Free Software Foundation, Inc.
3This file is part of GNU Make.
4
5GNU Make is free software; you can redistribute it and/or modify it under the
6terms of the GNU General Public License as published by the Free Software
7Foundation; either version 3 of the License, or (at your option) any later
8version.
9
10GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
11WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License along with
15this program. If not, see <http://www.gnu.org/licenses/>. */
16
17#include "makeint.h"
18
19#include <stdio.h>
20
21#ifdef HAVE_FCNTL_H
22# include <fcntl.h>
23#endif
24#if defined(HAVE_PSELECT) && defined(HAVE_SYS_SELECT_H)
25# include <sys/select.h>
26#endif
27
28#include "debug.h"
29#include "job.h"
30#include "os.h"
31
32#ifdef MAKE_JOBSERVER
33
34/* This section provides OS-specific functions to support the jobserver. */
35
36/* These track the state of the jobserver pipe. Passed to child instances. */
37static int job_fds[2] = { -1, -1 };
38
39/* Used to signal read() that a SIGCHLD happened. Always CLOEXEC.
40 If we use pselect() this will never be created and always -1.
41 */
42static int volatile job_rfd = -1; /* bird: added volatile to try ensure atomic update. */
43
44/* Token written to the pipe (could be any character...) */
45static char token = '+';
46
47static int
48make_job_rfd (void)
49{
50#ifdef HAVE_PSELECT
51 /* Pretend we succeeded. */
52 return 0;
53#else
54 /* bird: modified to use local variable and only update job_rfd once, otherwise
55 we're racing the signal handler clearing and closing this. */
56 int new_job_rfd;
57 EINTRLOOP (new_job_rfd, dup (job_fds[0]));
58 if (new_job_rfd >= 0)
59 CLOSE_ON_EXEC (new_job_rfd);
60
61 job_rfd = new_job_rfd;
62 return new_job_rfd;
63#endif
64}
65
66static void
67set_blocking (int fd, int blocking)
68{
69 // If we're not using pselect() don't change the blocking
70#ifdef HAVE_PSELECT
71 int flags;
72 EINTRLOOP (flags, fcntl (fd, F_GETFL));
73 if (flags >= 0)
74 {
75 int r;
76 flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
77 EINTRLOOP (r, fcntl (fd, F_SETFL, flags));
78 if (r < 0)
79 pfatal_with_name ("fcntl(O_NONBLOCK)");
80 }
81#endif
82}
83
84unsigned int
85jobserver_setup (int slots)
86{
87 int r;
88
89 EINTRLOOP (r, pipe (job_fds));
90 if (r < 0)
91 pfatal_with_name (_("creating jobs pipe"));
92
93 if (make_job_rfd () < 0)
94 pfatal_with_name (_("duping jobs pipe"));
95
96 while (slots--)
97 {
98 EINTRLOOP (r, write (job_fds[1], &token, 1));
99 if (r != 1)
100 pfatal_with_name (_("init jobserver pipe"));
101 }
102
103 /* When using pselect() we want the read to be non-blocking. */
104 set_blocking (job_fds[0], 0);
105
106 return 1;
107}
108
109unsigned int
110jobserver_parse_auth (const char *auth)
111{
112 /* Given the command-line parameter, parse it. */
113 if (sscanf (auth, "%d,%d", &job_fds[0], &job_fds[1]) != 2)
114 OS (fatal, NILF,
115 _("internal error: invalid --jobserver-auth string '%s'"), auth);
116
117 DB (DB_JOBS,
118 (_("Jobserver client (fds %d,%d)\n"), job_fds[0], job_fds[1]));
119
120#ifdef HAVE_FCNTL_H
121# define FD_OK(_f) (fcntl ((_f), F_GETFD) != -1)
122#else
123# define FD_OK(_f) 1
124#endif
125
126 /* Make sure our pipeline is valid, and (possibly) create a duplicate pipe,
127 that will be closed in the SIGCHLD handler. If this fails with EBADF,
128 the parent has closed the pipe on us because it didn't think we were a
129 submake. If so, warn and default to -j1. */
130
131 if (!FD_OK (job_fds[0]) || !FD_OK (job_fds[1]) || make_job_rfd () < 0)
132 {
133 if (errno != EBADF)
134 pfatal_with_name (_("jobserver pipeline"));
135
136 job_fds[0] = job_fds[1] = -1;
137
138 return 0;
139 }
140
141 /* When using pselect() we want the read to be non-blocking. */
142 set_blocking (job_fds[0], 0);
143
144 return 1;
145}
146
147char *
148jobserver_get_auth (void)
149{
150 char *auth = xmalloc ((INTSTR_LENGTH * 2) + 2);
151 sprintf (auth, "%d,%d", job_fds[0], job_fds[1]);
152 return auth;
153}
154
155unsigned int
156jobserver_enabled (void)
157{
158 return job_fds[0] >= 0;
159}
160
161void
162jobserver_clear (void)
163{
164 if (job_fds[0] >= 0)
165 close (job_fds[0]);
166 if (job_fds[1] >= 0)
167 close (job_fds[1]);
168 if (job_rfd >= 0)
169 close (job_rfd);
170
171 job_fds[0] = job_fds[1] = job_rfd = -1;
172}
173
174void
175jobserver_release (int is_fatal)
176{
177 int r;
178 EINTRLOOP (r, write (job_fds[1], &token, 1));
179 if (r != 1)
180 {
181 if (is_fatal)
182 pfatal_with_name (_("write jobserver"));
183 perror_with_name ("write", "");
184 }
185}
186
187unsigned int
188jobserver_acquire_all (void)
189{
190 unsigned int tokens = 0;
191
192 /* Use blocking reads to wait for all outstanding jobs. */
193 set_blocking (job_fds[0], 1);
194
195 /* Close the write side, so the read() won't hang forever. */
196 close (job_fds[1]);
197 job_fds[1] = -1;
198
199 while (1)
200 {
201 char intake;
202 int r;
203 EINTRLOOP (r, read (job_fds[0], &intake, 1));
204 if (r != 1)
205 return tokens;
206 ++tokens;
207 }
208}
209
210/* Prepare the jobserver to start a child process. */
211void
212jobserver_pre_child (int recursive)
213{
214 /* If it's not a recursive make, avoid polutting the jobserver pipes. */
215 if (!recursive && job_fds[0] >= 0)
216 {
217 CLOSE_ON_EXEC (job_fds[0]);
218 CLOSE_ON_EXEC (job_fds[1]);
219 }
220}
221
222void
223jobserver_post_child (int recursive)
224{
225#if defined(F_GETFD) && defined(F_SETFD)
226 if (!recursive && job_fds[0] >= 0)
227 {
228 unsigned int i;
229 for (i = 0; i < 2; ++i)
230 {
231 int flags;
232 EINTRLOOP (flags, fcntl (job_fds[i], F_GETFD));
233 if (flags >= 0)
234 {
235 int r;
236 EINTRLOOP (r, fcntl (job_fds[i], F_SETFD, flags & ~FD_CLOEXEC));
237 }
238 }
239 }
240#endif
241}
242
243void
244jobserver_signal (void)
245{
246 if (job_rfd >= 0)
247 {
248 close (job_rfd);
249 job_rfd = -1;
250 }
251}
252
253void
254jobserver_pre_acquire (void)
255{
256 /* Make sure we have a dup'd FD. */
257 if (job_rfd < 0 && job_fds[0] >= 0 && make_job_rfd () < 0)
258 pfatal_with_name (_("duping jobs pipe"));
259}
260
261#ifdef HAVE_PSELECT
262
263/* Use pselect() to atomically wait for both a signal and a file descriptor.
264 It also provides a timeout facility so we don't need to use SIGALRM.
265
266 This method relies on the fact that SIGCHLD will be blocked everywhere,
267 and only unblocked (atomically) within the pselect() call, so we can
268 never miss a SIGCHLD.
269 */
270unsigned int
271jobserver_acquire (int timeout)
272{
273 struct timespec spec;
274 struct timespec *specp = NULL;
275 sigset_t empty;
276
277 sigemptyset (&empty);
278
279 if (timeout)
280 {
281 /* Alarm after one second (is this too granular?) */
282 spec.tv_sec = 1;
283 spec.tv_nsec = 0;
284 specp = &spec;
285 }
286
287 while (1)
288 {
289 fd_set readfds;
290 int r;
291 char intake;
292
293 FD_ZERO (&readfds);
294 FD_SET (job_fds[0], &readfds);
295
296 r = pselect (job_fds[0]+1, &readfds, NULL, NULL, specp, &empty);
297 if (r < 0)
298 switch (errno)
299 {
300 case EINTR:
301 /* SIGCHLD will show up as an EINTR. */
302 return 0;
303
304 case EBADF:
305 /* Someone closed the jobs pipe.
306 That shouldn't happen but if it does we're done. */
307 O (fatal, NILF, _("job server shut down"));
308
309 default:
310 pfatal_with_name (_("pselect jobs pipe"));
311 }
312
313 if (r == 0)
314 /* Timeout. */
315 return 0;
316
317 /* The read FD is ready: read it! This is non-blocking. */
318 EINTRLOOP (r, read (job_fds[0], &intake, 1));
319
320 if (r < 0)
321 {
322 /* Someone sniped our token! Try again. */
323 if (errno == EAGAIN)
324 continue;
325
326 pfatal_with_name (_("read jobs pipe"));
327 }
328
329 /* read() should never return 0: only the master make can reap all the
330 tokens and close the write side...?? */
331 return r > 0;
332 }
333}
334
335#else
336
337/* This method uses a "traditional" UNIX model for waiting on both a signal
338 and a file descriptor. However, it's complex and since we have a SIGCHLD
339 handler installed we need to check ALL system calls for EINTR: painful!
340
341 Read a token. As long as there's no token available we'll block. We
342 enable interruptible system calls before the read(2) so that if we get a
343 SIGCHLD while we're waiting, we'll return with EINTR and we can process the
344 death(s) and return tokens to the free pool.
345
346 Once we return from the read, we immediately reinstate restartable system
347 calls. This allows us to not worry about checking for EINTR on all the
348 other system calls in the program.
349
350 There is one other twist: there is a span between the time reap_children()
351 does its last check for dead children and the time the read(2) call is
352 entered, below, where if a child dies we won't notice. This is extremely
353 serious as it could cause us to deadlock, given the right set of events.
354
355 To avoid this, we do the following: before we reap_children(), we dup(2)
356 the read FD on the jobserver pipe. The read(2) call below uses that new
357 FD. In the signal handler, we close that FD. That way, if a child dies
358 during the section mentioned above, the read(2) will be invoked with an
359 invalid FD and will return immediately with EBADF. */
360
361static RETSIGTYPE
362job_noop (int sig UNUSED)
363{
364}
365
366/* Set the child handler action flags to FLAGS. */
367static void
368set_child_handler_action_flags (int set_handler, int set_alarm)
369{
370 struct sigaction sa;
371
372#ifdef __EMX__
373 /* The child handler must be turned off here. */
374 signal (SIGCHLD, SIG_DFL);
375#endif
376
377 memset (&sa, '\0', sizeof sa);
378 sa.sa_handler = child_handler;
379 sa.sa_flags = set_handler ? 0 : SA_RESTART;
380
381#if defined SIGCHLD
382 if (sigaction (SIGCHLD, &sa, NULL) < 0)
383 pfatal_with_name ("sigaction: SIGCHLD");
384#endif
385
386#if defined SIGCLD && SIGCLD != SIGCHLD
387 if (sigaction (SIGCLD, &sa, NULL) < 0)
388 pfatal_with_name ("sigaction: SIGCLD");
389#endif
390
391#if defined SIGALRM
392 if (set_alarm)
393 {
394 /* If we're about to enter the read(), set an alarm to wake up in a
395 second so we can check if the load has dropped and we can start more
396 work. On the way out, turn off the alarm and set SIG_DFL. */
397 if (set_handler)
398 {
399 sa.sa_handler = job_noop;
400 sa.sa_flags = 0;
401 if (sigaction (SIGALRM, &sa, NULL) < 0)
402 pfatal_with_name ("sigaction: SIGALRM");
403 alarm (1);
404 }
405 else
406 {
407 alarm (0);
408 sa.sa_handler = SIG_DFL;
409 sa.sa_flags = 0;
410 if (sigaction (SIGALRM, &sa, NULL) < 0)
411 pfatal_with_name ("sigaction: SIGALRM");
412 }
413 }
414#endif
415}
416
417unsigned int
418jobserver_acquire (int timeout)
419{
420 char intake;
421 int got_token;
422 int saved_errno;
423
424 /* Set interruptible system calls, and read() for a job token. */
425 set_child_handler_action_flags (1, timeout);
426
427 EINTRLOOP (got_token, read (job_rfd, &intake, 1));
428 saved_errno = errno;
429
430 set_child_handler_action_flags (0, timeout);
431
432 if (got_token == 1)
433 return 1;
434
435 /* If the error _wasn't_ expected (EINTR or EBADF), fatal. Otherwise,
436 go back and reap_children(), and try again. */
437 errno = saved_errno;
438
439 if (errno != EINTR && errno != EBADF)
440 pfatal_with_name (_("read jobs pipe"));
441
442 if (errno == EBADF)
443 DB (DB_JOBS, ("Read returned EBADF.\n"));
444
445 return 0;
446}
447
448#endif
449
450#endif /* MAKE_JOBSERVER */
451
452/* Create a "bad" file descriptor for stdin when parallel jobs are run. */
453int
454get_bad_stdin (void)
455{
456 static int bad_stdin = -1;
457
458 /* Set up a bad standard input that reads from a broken pipe. */
459
460 if (bad_stdin == -1)
461 {
462 /* Make a file descriptor that is the read end of a broken pipe.
463 This will be used for some children's standard inputs. */
464 int pd[2];
465 if (pipe (pd) == 0)
466 {
467 /* Close the write side. */
468 (void) close (pd[1]);
469 /* Save the read side. */
470 bad_stdin = pd[0];
471
472 /* Set the descriptor to close on exec, so it does not litter any
473 child's descriptor table. When it is dup2'd onto descriptor 0,
474 that descriptor will not close on exec. */
475 CLOSE_ON_EXEC (bad_stdin);
476 }
477 }
478
479 return bad_stdin;
480}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use