]>
git.proxmox.com Git - ceph.git/blob - ceph/src/boost/tools/build/src/engine/execunix.c
2 * Copyright 1993, 1995 Christopher Seiwald.
3 * Copyright 2007 Noel Belcourt.
5 * This file is part of Jam - see jam.c for Copyright information.
19 #include <unistd.h> /* vfork(), _exit(), STDOUT_FILENO and such */
20 #include <sys/resource.h>
21 #include <sys/times.h>
25 #if defined(sun) || defined(__sun)
31 #include <sys/times.h>
33 #if defined(__APPLE__)
38 #define vfork() fork()
43 * execunix.c - execute a shell script on UNIX/OS2/AmigaOS
45 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). The
46 * default is: /bin/sh -c
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.
52 * Each word must be an individual element in a jam variable value.
54 * Do not just set JAMSHELL to /bin/sh - it will not work!
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.
62 /* find a free slot in the running commands table */
63 static int get_free_cmdtab_slot();
65 /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
69 /* We hold stdout & stderr child process information in two element arrays
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 */
86 /* Function called when the command completes. */
89 /* Opaque data passed back to the 'func' callback. */
92 static int cmdtab_size
= 0;
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.
98 struct 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 ) ) ) )
103 * exec_init() - global initialization
105 void exec_init( void )
108 if ( globs
.jobs
> cmdtab_size
)
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
)
115 GET_WAIT_FD( i
)[ OUT
].fd
= -1;
116 GET_WAIT_FD( i
)[ OUT
].events
= POLLIN
;
117 if ( globs
.pipe_action
)
119 GET_WAIT_FD( i
)[ ERR
].fd
= -1;
120 GET_WAIT_FD( i
)[ ERR
].events
= POLLIN
;
123 cmdtab_size
= globs
.jobs
;
127 void exec_done( void )
130 BJAM_FREE( wait_fds
);
134 * exec_check() - preprocess and validate the command.
139 string
const * command
,
142 int * error_max_length
145 int const is_raw_cmd
= is_raw_command_request( *pShell
);
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.
150 if ( !command
->size
&& ( is_raw_cmd
|| list_empty( *pShell
) ) )
151 return EXEC_CHECK_NOOP
;
155 : check_cmd_for_too_long_lines( command
->value
, MAXLINE
, error_length
,
161 * exec_cmd() - launch an async command execution.
164 /* We hold file descriptors for pipes used to communicate with child processes
165 * in two element arrays indexed as follows.
167 #define EXECCMD_PIPE_READ 0
168 #define EXECCMD_PIPE_WRITE 1
172 string
const * command
,
173 ExecCmdCallback func
,
178 struct sigaction ignore
, saveintr
, savequit
;
179 sigset_t chldmask
, savemask
;
181 int const slot
= get_free_cmdtab_slot();
185 char const * argv
[ MAXARGC
+ 1 ]; /* +1 for NULL */
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" ) );
194 if ( list_empty( shell
) )
195 shell
= default_shell
;
197 /* Forumulate argv. If shell was defined, be prepared for % and ! subs.
198 * Otherwise, use stock /bin/sh.
200 argv_from_shell( argv
, shell
, command
->value
, slot
);
205 out_printf( "Using shell: " );
208 for ( i
= 0; argv
[ i
]; ++i
)
209 out_printf( " argv[%d] = '%s'\n", i
, argv
[ i
] );
212 /* Create pipes for collecting child output. */
213 if ( pipe( out
) < 0 || ( globs
.pipe_action
&& pipe( err
) < 0 ) )
219 /* Start the command */
221 timestamp_current( &cmdtab
[ slot
].start_dt
);
223 if ( 0 < globs
.timeout
)
225 /* Handle hung processes by manually tracking elapsed time and signal
226 * process when time limit expires.
229 cmdtab
[ slot
].start_time
= times( &buf
);
231 /* Make a global, only do this once. */
232 if ( !tps
) tps
= sysconf( _SC_CLK_TCK
);
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
);
240 /* ignore SIGINT and SIGQUIT */
241 ignore
.sa_handler
= SIG_IGN
;
242 sigemptyset(&ignore
.sa_mask
);
244 if (sigaction(SIGINT
, &ignore
, &saveintr
) < 0)
246 if (sigaction(SIGQUIT
, &ignore
, &savequit
) < 0)
250 sigemptyset(&chldmask
);
251 sigaddset(&chldmask
, SIGCHLD
);
252 if (sigprocmask(SIG_BLOCK
, &chldmask
, &savemask
) < 0)
255 if ( ( cmdtab
[ slot
].pid
= vfork() ) == -1 )
261 if ( cmdtab
[ slot
].pid
== 0 )
266 int const pid
= getpid();
268 /* restore previous signals */
269 sigaction(SIGINT
, &saveintr
, NULL
);
270 sigaction(SIGQUIT
, &savequit
, NULL
);
271 sigprocmask(SIG_SETMASK
, &savemask
, NULL
);
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
] );
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
286 if ( 0 < globs
.timeout
)
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
);
293 if (0 != setpgid( pid
, pid
)) {
294 perror("setpgid(child)");
295 /* exit( EXITBAD ); */
297 execvp( argv
[ 0 ], (char * *)argv
);
306 /* redundant call, ignore return value */
307 setpgid(cmdtab
[ slot
].pid
, cmdtab
[ slot
].pid
);
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
] );
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
);
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
] )
328 /* Parent reads from err[ EXECCMD_PIPE_READ ]. */
329 if ( globs
.pipe_action
)
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
] )
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
];
344 /* Save input data into the selected running commands table slot. */
345 cmdtab
[ slot
].func
= func
;
346 cmdtab
[ slot
].closure
= closure
;
348 /* restore previous signals */
349 sigaction(SIGINT
, &saveintr
, NULL
);
350 sigaction(SIGQUIT
, &savequit
, NULL
);
351 sigprocmask(SIG_SETMASK
, &savemask
, NULL
);
354 #undef EXECCMD_PIPE_READ
355 #undef EXECCMD_PIPE_WRITE
358 /* Returns 1 if file descriptor is closed, or 0 if it is still alive.
360 * i is index into cmdtab
362 * s (stream) indexes:
363 * - cmdtab[ i ].stream[ s ]
364 * - cmdtab[ i ].buffer[ s ]
365 * - cmdtab[ i ].fd [ s ]
368 static int read_descriptor( int i
, int s
)
371 char buffer
[ BUFSIZ
];
373 while ( 0 < ( ret
= fread( buffer
, sizeof( char ), BUFSIZ
- 1,
374 cmdtab
[ i
].stream
[ s
] ) ) )
377 if ( !cmdtab
[ i
].buffer
[ s
] )
379 /* Never been allocated. */
380 if ( globs
.max_buf
&& ret
> globs
.max_buf
)
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 );
391 /* Previously allocated. */
392 if ( cmdtab
[ i
].buf_size
[ s
] < globs
.max_buf
|| !globs
.max_buf
)
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 );
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.
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';
413 return feof( cmdtab
[ i
].stream
[ s
] );
418 * close_streams() - Close the stream and pipe descriptor.
421 static void close_streams( int const i
, int const s
)
423 fclose( cmdtab
[ i
].stream
[ s
] );
424 cmdtab
[ i
].stream
[ s
] = 0;
426 close( cmdtab
[ i
].fd
[ s
] );
427 cmdtab
[ i
].fd
[ s
] = 0;
429 GET_WAIT_FD( i
)[ s
].fd
= -1;
434 * exec_wait() - wait for any of the async command processes to terminate.
436 * May register more than one terminated child process but will exit as soon as
437 * at least one has been registered.
444 /* Process children that signaled. */
449 struct timeval
* ptv
= NULL
;
450 int select_timeout
= globs
.timeout
;
452 /* Check for timeouts:
453 * - kill children that already timed out
454 * - decide how long until the next one times out
456 if ( globs
.timeout
> 0 )
459 clock_t const current
= times( &buf
);
460 for ( i
= 0; i
< globs
.jobs
; ++i
)
461 if ( cmdtab
[ i
].pid
)
463 clock_t const consumed
=
464 ( current
- cmdtab
[ i
].start_time
) / tps
;
465 if ( consumed
>= globs
.timeout
)
467 killpg( cmdtab
[ i
].pid
, SIGKILL
);
468 cmdtab
[ i
].exit_reason
= EXIT_TIMEOUT
;
470 else if ( globs
.timeout
- consumed
< select_timeout
)
471 select_timeout
= globs
.timeout
- consumed
;
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.
478 tv
.tv_sec
= select_timeout
;
483 /* select() will wait for I/O on a descriptor, a signal, or timeout. */
485 /* disable child termination signals while in select */
488 sigemptyset(&sigmask
);
489 sigaddset(&sigmask
, SIGCHLD
);
490 sigprocmask(SIG_BLOCK
, &sigmask
, NULL
);
491 while ( ( ret
= poll( wait_fds
, WAIT_FDS_SIZE
, select_timeout
* 1000 ) ) == -1 )
492 if ( errno
!= EINTR
)
494 /* restore original signal mask by unblocking sigchld */
495 sigprocmask(SIG_UNBLOCK
, &sigmask
, NULL
);
500 for ( i
= 0; i
< globs
.jobs
; ++i
)
504 if ( GET_WAIT_FD( i
)[ OUT
].revents
)
505 out_done
= read_descriptor( i
, OUT
);
507 if ( globs
.pipe_action
&& ( GET_WAIT_FD( i
)[ ERR
].revents
) )
508 err_done
= read_descriptor( i
, ERR
);
510 /* If feof on either descriptor, we are done. */
511 if ( out_done
|| err_done
)
516 timing_info time_info
;
517 struct rusage cmd_usage
;
519 /* We found a terminated child process - our search is done. */
522 /* Close the stream and pipe descriptors. */
523 close_streams( i
, OUT
);
524 if ( globs
.pipe_action
)
525 close_streams( i
, ERR
);
527 /* Reap the child and release resources. */
528 while ( ( pid
= wait4( cmdtab
[ i
].pid
, &status
, 0, &cmd_usage
) ) == -1 )
529 if ( errno
!= EINTR
)
531 if ( pid
!= cmdtab
[ i
].pid
)
533 err_printf( "unknown pid %d with errno = %d\n", pid
, errno
);
537 /* Set reason for exit if not timed out. */
538 if ( WIFEXITED( status
) )
539 cmdtab
[ i
].exit_reason
= WEXITSTATUS( status
)
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
);
550 /* Drive the completion. */
552 rstat
= EXEC_CMD_INTR
;
554 rstat
= EXEC_CMD_FAIL
;
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
);
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;
568 BJAM_FREE( cmdtab
[ i
].buffer
[ ERR
] );
569 cmdtab
[ i
].buffer
[ ERR
] = 0;
570 cmdtab
[ i
].buf_size
[ ERR
] = 0;
573 cmdtab
[ i
].func
= 0;
574 cmdtab
[ i
].closure
= 0;
575 cmdtab
[ i
].start_time
= 0;
583 * Find a free slot in the running commands table.
586 static int get_free_cmdtab_slot()
589 for ( slot
= 0; slot
< globs
.jobs
; ++slot
)
590 if ( !cmdtab
[ slot
].pid
)
592 err_printf( "no slots for child!\n" );
596 # endif /* USE_EXECUNIX */