]> git.proxmox.com Git - ceph.git/blame - ceph/src/boost/tools/build/src/engine/execunix.c
update sources to v12.2.3
[ceph.git] / ceph / src / boost / tools / build / src / engine / execunix.c
CommitLineData
7c673cae
FG
1/*
2 * Copyright 1993, 1995 Christopher Seiwald.
3 * Copyright 2007 Noel Belcourt.
4 *
5 * This file is part of Jam - see jam.c for Copyright information.
6 */
7
8#include "jam.h"
9#include "execcmd.h"
10
11#include "lists.h"
12#include "output.h"
13#include "strings.h"
14
15#include <errno.h>
16#include <signal.h>
17#include <stdio.h>
18#include <time.h>
19#include <unistd.h> /* vfork(), _exit(), STDOUT_FILENO and such */
20#include <sys/resource.h>
21#include <sys/times.h>
22#include <sys/wait.h>
b32b8144 23#include <poll.h>
7c673cae
FG
24
25#if defined(sun) || defined(__sun)
26 #include <wait.h>
27#endif
28
29#ifdef USE_EXECUNIX
30
31#include <sys/times.h>
32
33#if defined(__APPLE__)
34 #define NO_VFORK
35#endif
36
37#ifdef NO_VFORK
38 #define vfork() fork()
39#endif
40
41
42/*
43 * execunix.c - execute a shell script on UNIX/OS2/AmigaOS
44 *
45 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). The
46 * default is: /bin/sh -c
47 *
48 * In $(JAMSHELL), % expands to the command string and ! expands to the slot
49 * number (starting at 1) for multiprocess (-j) invocations. If $(JAMSHELL) does
50 * not include a %, it is tacked on as the last argument.
51 *
52 * Each word must be an individual element in a jam variable value.
53 *
54 * Do not just set JAMSHELL to /bin/sh - it will not work!
55 *
56 * External routines:
57 * exec_check() - preprocess and validate the command.
58 * exec_cmd() - launch an async command execution.
59 * exec_wait() - wait for any of the async command processes to terminate.
60 */
61
62/* find a free slot in the running commands table */
63static int get_free_cmdtab_slot();
64
65/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
66
67static clock_t tps;
68
69/* We hold stdout & stderr child process information in two element arrays
70 * indexed as follows.
71 */
72#define OUT 0
73#define ERR 1
74
75static struct
76{
77 int pid; /* on win32, a real process handle */
78 int fd[ 2 ]; /* file descriptors for stdout and stderr */
79 FILE * stream[ 2 ]; /* child's stdout and stderr file streams */
80 clock_t start_time; /* start time of child process */
81 int exit_reason; /* termination status */
82 char * buffer[ 2 ]; /* buffers to hold stdout and stderr, if any */
83 int buf_size[ 2 ]; /* buffer sizes in bytes */
84 timestamp start_dt; /* start of command timestamp */
85
86 /* Function called when the command completes. */
87 ExecCmdCallback func;
88
89 /* Opaque data passed back to the 'func' callback. */
90 void * closure;
b32b8144
FG
91} * cmdtab = NULL;
92static int cmdtab_size = 0;
7c673cae 93
b32b8144
FG
94/* Contains both stdin and stdout of all processes.
95 * The length is either globs.jobs or globs.jobs * 2
96 * depending on globs.pipe_action.
97 */
98struct pollfd * wait_fds = NULL;
99#define WAIT_FDS_SIZE ( globs.jobs * ( globs.pipe_action ? 2 : 1 ) )
100#define GET_WAIT_FD( job_idx ) ( wait_fds + ( ( job_idx * ( globs.pipe_action ? 2 : 1 ) ) ) )
101
102/*
103 * exec_init() - global initialization
104 */
105void exec_init( void )
106{
107 int i;
108 if ( globs.jobs > cmdtab_size )
109 {
110 cmdtab = BJAM_REALLOC( cmdtab, globs.jobs * sizeof( *cmdtab ) );
111 memset( cmdtab + cmdtab_size, 0, ( globs.jobs - cmdtab_size ) * sizeof( *cmdtab ) );
112 wait_fds = BJAM_REALLOC( wait_fds, WAIT_FDS_SIZE * sizeof ( *wait_fds ) );
113 for ( i = cmdtab_size; i < globs.jobs; ++i )
114 {
115 GET_WAIT_FD( i )[ OUT ].fd = -1;
116 GET_WAIT_FD( i )[ OUT ].events = POLLIN;
117 if ( globs.pipe_action )
118 {
119 GET_WAIT_FD( i )[ ERR ].fd = -1;
120 GET_WAIT_FD( i )[ ERR ].events = POLLIN;
121 }
122 }
123 cmdtab_size = globs.jobs;
124 }
125}
126
127void exec_done( void )
128{
129 BJAM_FREE( cmdtab );
130 BJAM_FREE( wait_fds );
131}
7c673cae
FG
132
133/*
134 * exec_check() - preprocess and validate the command.
135 */
136
137int exec_check
138(
139 string const * command,
140 LIST * * pShell,
141 int * error_length,
142 int * error_max_length
143)
144{
145 int const is_raw_cmd = is_raw_command_request( *pShell );
146
147 /* We allow empty commands for non-default shells since we do not really
148 * know what they are going to do with such commands.
149 */
150 if ( !command->size && ( is_raw_cmd || list_empty( *pShell ) ) )
151 return EXEC_CHECK_NOOP;
152
153 return is_raw_cmd
154 ? EXEC_CHECK_OK
155 : check_cmd_for_too_long_lines( command->value, MAXLINE, error_length,
156 error_max_length );
157}
158
159
160/*
161 * exec_cmd() - launch an async command execution.
162 */
163
164/* We hold file descriptors for pipes used to communicate with child processes
165 * in two element arrays indexed as follows.
166 */
167#define EXECCMD_PIPE_READ 0
168#define EXECCMD_PIPE_WRITE 1
169
170void exec_cmd
171(
172 string const * command,
173 ExecCmdCallback func,
174 void * closure,
175 LIST * shell
176)
177{
178 struct sigaction ignore, saveintr, savequit;
179 sigset_t chldmask, savemask;
180
181 int const slot = get_free_cmdtab_slot();
182 int out[ 2 ];
183 int err[ 2 ];
184 int len;
185 char const * argv[ MAXARGC + 1 ]; /* +1 for NULL */
186
187 /* Initialize default shell. */
188 static LIST * default_shell;
189 if ( !default_shell )
190 default_shell = list_push_back( list_new(
191 object_new( "/bin/sh" ) ),
192 object_new( "-c" ) );
193
194 if ( list_empty( shell ) )
195 shell = default_shell;
196
197 /* Forumulate argv. If shell was defined, be prepared for % and ! subs.
198 * Otherwise, use stock /bin/sh.
199 */
200 argv_from_shell( argv, shell, command->value, slot );
201
202 if ( DEBUG_EXECCMD )
203 {
204 int i;
205 out_printf( "Using shell: " );
206 list_print( shell );
207 out_printf( "\n" );
208 for ( i = 0; argv[ i ]; ++i )
209 out_printf( " argv[%d] = '%s'\n", i, argv[ i ] );
210 }
211
212 /* Create pipes for collecting child output. */
213 if ( pipe( out ) < 0 || ( globs.pipe_action && pipe( err ) < 0 ) )
214 {
215 perror( "pipe" );
216 exit( EXITBAD );
217 }
218
219 /* Start the command */
220
221 timestamp_current( &cmdtab[ slot ].start_dt );
222
223 if ( 0 < globs.timeout )
224 {
225 /* Handle hung processes by manually tracking elapsed time and signal
226 * process when time limit expires.
227 */
228 struct tms buf;
229 cmdtab[ slot ].start_time = times( &buf );
230
231 /* Make a global, only do this once. */
232 if ( !tps ) tps = sysconf( _SC_CLK_TCK );
233 }
234
235 /* Child does not need the read pipe ends used by the parent. */
236 fcntl( out[ EXECCMD_PIPE_READ ], F_SETFD, FD_CLOEXEC );
237 if ( globs.pipe_action )
238 fcntl( err[ EXECCMD_PIPE_READ ], F_SETFD, FD_CLOEXEC );
239
240 /* ignore SIGINT and SIGQUIT */
241 ignore.sa_handler = SIG_IGN;
242 sigemptyset(&ignore.sa_mask);
243 ignore.sa_flags = 0;
244 if (sigaction(SIGINT, &ignore, &saveintr) < 0)
245 return;
246 if (sigaction(SIGQUIT, &ignore, &savequit) < 0)
247 return;
248
249 /* block SIGCHLD */
250 sigemptyset(&chldmask);
251 sigaddset(&chldmask, SIGCHLD);
252 if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0)
253 return;
254
255 if ( ( cmdtab[ slot ].pid = vfork() ) == -1 )
256 {
257 perror( "vfork" );
258 exit( EXITBAD );
259 }
260
261 if ( cmdtab[ slot ].pid == 0 )
262 {
263 /*****************/
264 /* Child process */
265 /*****************/
266 int const pid = getpid();
267
268 /* restore previous signals */
269 sigaction(SIGINT, &saveintr, NULL);
270 sigaction(SIGQUIT, &savequit, NULL);
271 sigprocmask(SIG_SETMASK, &savemask, NULL);
272
273 /* Redirect stdout and stderr to pipes inherited from the parent. */
274 dup2( out[ EXECCMD_PIPE_WRITE ], STDOUT_FILENO );
275 dup2( globs.pipe_action ? err[ EXECCMD_PIPE_WRITE ] :
276 out[ EXECCMD_PIPE_WRITE ], STDERR_FILENO );
277 close( out[ EXECCMD_PIPE_WRITE ] );
278 if ( globs.pipe_action )
279 close( err[ EXECCMD_PIPE_WRITE ] );
280
281 /* Make this process a process group leader so that when we kill it, all
282 * child processes of this process are terminated as well. We use
283 * killpg( pid, SIGKILL ) to kill the process group leader and all its
284 * children.
285 */
286 if ( 0 < globs.timeout )
287 {
288 struct rlimit r_limit;
289 r_limit.rlim_cur = globs.timeout;
290 r_limit.rlim_max = globs.timeout;
291 setrlimit( RLIMIT_CPU, &r_limit );
292 }
293 if (0 != setpgid( pid, pid )) {
294 perror("setpgid(child)");
295 /* exit( EXITBAD ); */
296 }
297 execvp( argv[ 0 ], (char * *)argv );
298 perror( "execvp" );
299 _exit( 127 );
300 }
301
302 /******************/
303 /* Parent process */
304 /******************/
305
306 /* redundant call, ignore return value */
307 setpgid(cmdtab[ slot ].pid, cmdtab[ slot ].pid);
308
309 /* Parent not need the write pipe ends used by the child. */
310 close( out[ EXECCMD_PIPE_WRITE ] );
311 if ( globs.pipe_action )
312 close( err[ EXECCMD_PIPE_WRITE ] );
313
314 /* Set both pipe read file descriptors to non-blocking. */
315 fcntl( out[ EXECCMD_PIPE_READ ], F_SETFL, O_NONBLOCK );
316 if ( globs.pipe_action )
317 fcntl( err[ EXECCMD_PIPE_READ ], F_SETFL, O_NONBLOCK );
318
319 /* Parent reads from out[ EXECCMD_PIPE_READ ]. */
320 cmdtab[ slot ].fd[ OUT ] = out[ EXECCMD_PIPE_READ ];
321 cmdtab[ slot ].stream[ OUT ] = fdopen( cmdtab[ slot ].fd[ OUT ], "rb" );
322 if ( !cmdtab[ slot ].stream[ OUT ] )
323 {
324 perror( "fdopen" );
325 exit( EXITBAD );
326 }
327
328 /* Parent reads from err[ EXECCMD_PIPE_READ ]. */
329 if ( globs.pipe_action )
330 {
331 cmdtab[ slot ].fd[ ERR ] = err[ EXECCMD_PIPE_READ ];
332 cmdtab[ slot ].stream[ ERR ] = fdopen( cmdtab[ slot ].fd[ ERR ], "rb" );
333 if ( !cmdtab[ slot ].stream[ ERR ] )
334 {
335 perror( "fdopen" );
336 exit( EXITBAD );
337 }
338 }
339
b32b8144
FG
340 GET_WAIT_FD( slot )[ OUT ].fd = out[ EXECCMD_PIPE_READ ];
341 if ( globs.pipe_action )
342 GET_WAIT_FD( slot )[ ERR ].fd = err[ EXECCMD_PIPE_READ ];
343
7c673cae
FG
344 /* Save input data into the selected running commands table slot. */
345 cmdtab[ slot ].func = func;
346 cmdtab[ slot ].closure = closure;
347
348 /* restore previous signals */
349 sigaction(SIGINT, &saveintr, NULL);
350 sigaction(SIGQUIT, &savequit, NULL);
351 sigprocmask(SIG_SETMASK, &savemask, NULL);
352}
353
354#undef EXECCMD_PIPE_READ
355#undef EXECCMD_PIPE_WRITE
356
357
358/* Returns 1 if file descriptor is closed, or 0 if it is still alive.
359 *
360 * i is index into cmdtab
361 *
362 * s (stream) indexes:
363 * - cmdtab[ i ].stream[ s ]
364 * - cmdtab[ i ].buffer[ s ]
365 * - cmdtab[ i ].fd [ s ]
366 */
367
368static int read_descriptor( int i, int s )
369{
370 int ret;
371 char buffer[ BUFSIZ ];
372
373 while ( 0 < ( ret = fread( buffer, sizeof( char ), BUFSIZ - 1,
374 cmdtab[ i ].stream[ s ] ) ) )
375 {
376 buffer[ ret ] = 0;
377 if ( !cmdtab[ i ].buffer[ s ] )
378 {
379 /* Never been allocated. */
380 if ( globs.max_buf && ret > globs.max_buf )
381 {
382 ret = globs.max_buf;
383 buffer[ ret ] = 0;
384 }
385 cmdtab[ i ].buf_size[ s ] = ret + 1;
386 cmdtab[ i ].buffer[ s ] = (char*)BJAM_MALLOC_ATOMIC( ret + 1 );
387 memcpy( cmdtab[ i ].buffer[ s ], buffer, ret + 1 );
388 }
389 else
390 {
391 /* Previously allocated. */
392 if ( cmdtab[ i ].buf_size[ s ] < globs.max_buf || !globs.max_buf )
393 {
394 char * tmp = cmdtab[ i ].buffer[ s ];
395 int const old_len = cmdtab[ i ].buf_size[ s ] - 1;
396 int const new_len = old_len + ret + 1;
397 cmdtab[ i ].buf_size[ s ] = new_len;
398 cmdtab[ i ].buffer[ s ] = (char*)BJAM_MALLOC_ATOMIC( new_len );
399 memcpy( cmdtab[ i ].buffer[ s ], tmp, old_len );
400 memcpy( cmdtab[ i ].buffer[ s ] + old_len, buffer, ret + 1 );
401 BJAM_FREE( tmp );
402 }
403 }
404 }
405
406 /* If buffer full, ensure last buffer char is newline so that jam log
407 * contains the command status at beginning of it own line instead of
408 * appended to end of the previous output.
409 */
410 if ( globs.max_buf && globs.max_buf <= cmdtab[ i ].buf_size[ s ] )
411 cmdtab[ i ].buffer[ s ][ cmdtab[ i ].buf_size[ s ] - 2 ] = '\n';
412
413 return feof( cmdtab[ i ].stream[ s ] );
414}
415
416
417/*
418 * close_streams() - Close the stream and pipe descriptor.
419 */
420
421static void close_streams( int const i, int const s )
422{
423 fclose( cmdtab[ i ].stream[ s ] );
424 cmdtab[ i ].stream[ s ] = 0;
425
426 close( cmdtab[ i ].fd[ s ] );
427 cmdtab[ i ].fd[ s ] = 0;
7c673cae 428
b32b8144 429 GET_WAIT_FD( i )[ s ].fd = -1;
7c673cae
FG
430}
431
432
433/*
434 * exec_wait() - wait for any of the async command processes to terminate.
435 *
436 * May register more than one terminated child process but will exit as soon as
437 * at least one has been registered.
438 */
439
440void exec_wait()
441{
442 int finished = 0;
443
444 /* Process children that signaled. */
445 while ( !finished )
446 {
447 int i;
448 struct timeval tv;
449 struct timeval * ptv = NULL;
450 int select_timeout = globs.timeout;
451
7c673cae
FG
452 /* Check for timeouts:
453 * - kill children that already timed out
454 * - decide how long until the next one times out
455 */
456 if ( globs.timeout > 0 )
457 {
458 struct tms buf;
459 clock_t const current = times( &buf );
460 for ( i = 0; i < globs.jobs; ++i )
461 if ( cmdtab[ i ].pid )
462 {
463 clock_t const consumed =
464 ( current - cmdtab[ i ].start_time ) / tps;
465 if ( consumed >= globs.timeout )
466 {
467 killpg( cmdtab[ i ].pid, SIGKILL );
468 cmdtab[ i ].exit_reason = EXIT_TIMEOUT;
469 }
470 else if ( globs.timeout - consumed < select_timeout )
471 select_timeout = globs.timeout - consumed;
472 }
473
474 /* If nothing else causes our select() call to exit, force it after
475 * however long it takes for the next one of our child processes to
476 * crossed its alloted processing time so we can terminate it.
477 */
478 tv.tv_sec = select_timeout;
479 tv.tv_usec = 0;
480 ptv = &tv;
481 }
482
483 /* select() will wait for I/O on a descriptor, a signal, or timeout. */
484 {
485 /* disable child termination signals while in select */
486 int ret;
487 sigset_t sigmask;
488 sigemptyset(&sigmask);
489 sigaddset(&sigmask, SIGCHLD);
490 sigprocmask(SIG_BLOCK, &sigmask, NULL);
b32b8144 491 while ( ( ret = poll( wait_fds, WAIT_FDS_SIZE, select_timeout * 1000 ) ) == -1 )
7c673cae
FG
492 if ( errno != EINTR )
493 break;
494 /* restore original signal mask by unblocking sigchld */
495 sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
496 if ( ret <= 0 )
497 continue;
498 }
499
500 for ( i = 0; i < globs.jobs; ++i )
501 {
502 int out_done = 0;
503 int err_done = 0;
b32b8144 504 if ( GET_WAIT_FD( i )[ OUT ].revents )
7c673cae
FG
505 out_done = read_descriptor( i, OUT );
506
b32b8144 507 if ( globs.pipe_action && ( GET_WAIT_FD( i )[ ERR ].revents ) )
7c673cae
FG
508 err_done = read_descriptor( i, ERR );
509
510 /* If feof on either descriptor, we are done. */
511 if ( out_done || err_done )
512 {
513 int pid;
514 int status;
515 int rstat;
516 timing_info time_info;
517 struct rusage cmd_usage;
518
519 /* We found a terminated child process - our search is done. */
520 finished = 1;
521
522 /* Close the stream and pipe descriptors. */
523 close_streams( i, OUT );
524 if ( globs.pipe_action )
525 close_streams( i, ERR );
526
527 /* Reap the child and release resources. */
528 while ( ( pid = wait4( cmdtab[ i ].pid, &status, 0, &cmd_usage ) ) == -1 )
529 if ( errno != EINTR )
530 break;
531 if ( pid != cmdtab[ i ].pid )
532 {
533 err_printf( "unknown pid %d with errno = %d\n", pid, errno );
534 exit( EXITBAD );
535 }
536
537 /* Set reason for exit if not timed out. */
538 if ( WIFEXITED( status ) )
539 cmdtab[ i ].exit_reason = WEXITSTATUS( status )
540 ? EXIT_FAIL
541 : EXIT_OK;
542
543 {
544 time_info.system = ((double)(cmd_usage.ru_stime.tv_sec)*1000000.0+(double)(cmd_usage.ru_stime.tv_usec))/1000000.0;
545 time_info.user = ((double)(cmd_usage.ru_utime.tv_sec)*1000000.0+(double)(cmd_usage.ru_utime.tv_usec))/1000000.0;
546 timestamp_copy( &time_info.start, &cmdtab[ i ].start_dt );
547 timestamp_current( &time_info.end );
548 }
549
550 /* Drive the completion. */
551 if ( interrupted() )
552 rstat = EXEC_CMD_INTR;
553 else if ( status )
554 rstat = EXEC_CMD_FAIL;
555 else
556 rstat = EXEC_CMD_OK;
557
558 /* Call the callback, may call back to jam rule land. */
559 (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time_info,
560 cmdtab[ i ].buffer[ OUT ], cmdtab[ i ].buffer[ ERR ],
561 cmdtab[ i ].exit_reason );
562
563 /* Clean up the command's running commands table slot. */
564 BJAM_FREE( cmdtab[ i ].buffer[ OUT ] );
565 cmdtab[ i ].buffer[ OUT ] = 0;
566 cmdtab[ i ].buf_size[ OUT ] = 0;
567
568 BJAM_FREE( cmdtab[ i ].buffer[ ERR ] );
569 cmdtab[ i ].buffer[ ERR ] = 0;
570 cmdtab[ i ].buf_size[ ERR ] = 0;
571
572 cmdtab[ i ].pid = 0;
573 cmdtab[ i ].func = 0;
574 cmdtab[ i ].closure = 0;
575 cmdtab[ i ].start_time = 0;
576 }
577 }
578 }
579}
580
581
582/*
583 * Find a free slot in the running commands table.
584 */
585
586static int get_free_cmdtab_slot()
587{
588 int slot;
b32b8144 589 for ( slot = 0; slot < globs.jobs; ++slot )
7c673cae
FG
590 if ( !cmdtab[ slot ].pid )
591 return slot;
592 err_printf( "no slots for child!\n" );
593 exit( EXITBAD );
594}
595
596# endif /* USE_EXECUNIX */