]>
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>
24 #if defined(sun) || defined(__sun)
30 #include <sys/times.h>
32 #if defined(__APPLE__)
37 #define vfork() fork()
42 * execunix.c - execute a shell script on UNIX/OS2/AmigaOS
44 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). The
45 * default is: /bin/sh -c
47 * In $(JAMSHELL), % expands to the command string and ! expands to the slot
48 * number (starting at 1) for multiprocess (-j) invocations. If $(JAMSHELL) does
49 * not include a %, it is tacked on as the last argument.
51 * Each word must be an individual element in a jam variable value.
53 * Do not just set JAMSHELL to /bin/sh - it will not work!
56 * exec_check() - preprocess and validate the command.
57 * exec_cmd() - launch an async command execution.
58 * exec_wait() - wait for any of the async command processes to terminate.
61 /* find a free slot in the running commands table */
62 static int get_free_cmdtab_slot();
64 /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
68 /* We hold stdout & stderr child process information in two element arrays
76 int pid
; /* on win32, a real process handle */
77 int fd
[ 2 ]; /* file descriptors for stdout and stderr */
78 FILE * stream
[ 2 ]; /* child's stdout and stderr file streams */
79 clock_t start_time
; /* start time of child process */
80 int exit_reason
; /* termination status */
81 char * buffer
[ 2 ]; /* buffers to hold stdout and stderr, if any */
82 int buf_size
[ 2 ]; /* buffer sizes in bytes */
83 timestamp start_dt
; /* start of command timestamp */
85 /* Function called when the command completes. */
88 /* Opaque data passed back to the 'func' callback. */
90 } cmdtab
[ MAXJOBS
] = { { 0 } };
94 * exec_check() - preprocess and validate the command.
99 string
const * command
,
102 int * error_max_length
105 int const is_raw_cmd
= is_raw_command_request( *pShell
);
107 /* We allow empty commands for non-default shells since we do not really
108 * know what they are going to do with such commands.
110 if ( !command
->size
&& ( is_raw_cmd
|| list_empty( *pShell
) ) )
111 return EXEC_CHECK_NOOP
;
115 : check_cmd_for_too_long_lines( command
->value
, MAXLINE
, error_length
,
121 * exec_cmd() - launch an async command execution.
124 /* We hold file descriptors for pipes used to communicate with child processes
125 * in two element arrays indexed as follows.
127 #define EXECCMD_PIPE_READ 0
128 #define EXECCMD_PIPE_WRITE 1
132 string
const * command
,
133 ExecCmdCallback func
,
138 struct sigaction ignore
, saveintr
, savequit
;
139 sigset_t chldmask
, savemask
;
141 int const slot
= get_free_cmdtab_slot();
145 char const * argv
[ MAXARGC
+ 1 ]; /* +1 for NULL */
147 /* Initialize default shell. */
148 static LIST
* default_shell
;
149 if ( !default_shell
)
150 default_shell
= list_push_back( list_new(
151 object_new( "/bin/sh" ) ),
152 object_new( "-c" ) );
154 if ( list_empty( shell
) )
155 shell
= default_shell
;
157 /* Forumulate argv. If shell was defined, be prepared for % and ! subs.
158 * Otherwise, use stock /bin/sh.
160 argv_from_shell( argv
, shell
, command
->value
, slot
);
165 out_printf( "Using shell: " );
168 for ( i
= 0; argv
[ i
]; ++i
)
169 out_printf( " argv[%d] = '%s'\n", i
, argv
[ i
] );
172 /* Create pipes for collecting child output. */
173 if ( pipe( out
) < 0 || ( globs
.pipe_action
&& pipe( err
) < 0 ) )
179 /* Start the command */
181 timestamp_current( &cmdtab
[ slot
].start_dt
);
183 if ( 0 < globs
.timeout
)
185 /* Handle hung processes by manually tracking elapsed time and signal
186 * process when time limit expires.
189 cmdtab
[ slot
].start_time
= times( &buf
);
191 /* Make a global, only do this once. */
192 if ( !tps
) tps
= sysconf( _SC_CLK_TCK
);
195 /* Child does not need the read pipe ends used by the parent. */
196 fcntl( out
[ EXECCMD_PIPE_READ
], F_SETFD
, FD_CLOEXEC
);
197 if ( globs
.pipe_action
)
198 fcntl( err
[ EXECCMD_PIPE_READ
], F_SETFD
, FD_CLOEXEC
);
200 /* ignore SIGINT and SIGQUIT */
201 ignore
.sa_handler
= SIG_IGN
;
202 sigemptyset(&ignore
.sa_mask
);
204 if (sigaction(SIGINT
, &ignore
, &saveintr
) < 0)
206 if (sigaction(SIGQUIT
, &ignore
, &savequit
) < 0)
210 sigemptyset(&chldmask
);
211 sigaddset(&chldmask
, SIGCHLD
);
212 if (sigprocmask(SIG_BLOCK
, &chldmask
, &savemask
) < 0)
215 if ( ( cmdtab
[ slot
].pid
= vfork() ) == -1 )
221 if ( cmdtab
[ slot
].pid
== 0 )
226 int const pid
= getpid();
228 /* restore previous signals */
229 sigaction(SIGINT
, &saveintr
, NULL
);
230 sigaction(SIGQUIT
, &savequit
, NULL
);
231 sigprocmask(SIG_SETMASK
, &savemask
, NULL
);
233 /* Redirect stdout and stderr to pipes inherited from the parent. */
234 dup2( out
[ EXECCMD_PIPE_WRITE
], STDOUT_FILENO
);
235 dup2( globs
.pipe_action
? err
[ EXECCMD_PIPE_WRITE
] :
236 out
[ EXECCMD_PIPE_WRITE
], STDERR_FILENO
);
237 close( out
[ EXECCMD_PIPE_WRITE
] );
238 if ( globs
.pipe_action
)
239 close( err
[ EXECCMD_PIPE_WRITE
] );
241 /* Make this process a process group leader so that when we kill it, all
242 * child processes of this process are terminated as well. We use
243 * killpg( pid, SIGKILL ) to kill the process group leader and all its
246 if ( 0 < globs
.timeout
)
248 struct rlimit r_limit
;
249 r_limit
.rlim_cur
= globs
.timeout
;
250 r_limit
.rlim_max
= globs
.timeout
;
251 setrlimit( RLIMIT_CPU
, &r_limit
);
253 if (0 != setpgid( pid
, pid
)) {
254 perror("setpgid(child)");
255 /* exit( EXITBAD ); */
257 execvp( argv
[ 0 ], (char * *)argv
);
266 /* redundant call, ignore return value */
267 setpgid(cmdtab
[ slot
].pid
, cmdtab
[ slot
].pid
);
269 /* Parent not need the write pipe ends used by the child. */
270 close( out
[ EXECCMD_PIPE_WRITE
] );
271 if ( globs
.pipe_action
)
272 close( err
[ EXECCMD_PIPE_WRITE
] );
274 /* Set both pipe read file descriptors to non-blocking. */
275 fcntl( out
[ EXECCMD_PIPE_READ
], F_SETFL
, O_NONBLOCK
);
276 if ( globs
.pipe_action
)
277 fcntl( err
[ EXECCMD_PIPE_READ
], F_SETFL
, O_NONBLOCK
);
279 /* Parent reads from out[ EXECCMD_PIPE_READ ]. */
280 cmdtab
[ slot
].fd
[ OUT
] = out
[ EXECCMD_PIPE_READ
];
281 cmdtab
[ slot
].stream
[ OUT
] = fdopen( cmdtab
[ slot
].fd
[ OUT
], "rb" );
282 if ( !cmdtab
[ slot
].stream
[ OUT
] )
288 /* Parent reads from err[ EXECCMD_PIPE_READ ]. */
289 if ( globs
.pipe_action
)
291 cmdtab
[ slot
].fd
[ ERR
] = err
[ EXECCMD_PIPE_READ
];
292 cmdtab
[ slot
].stream
[ ERR
] = fdopen( cmdtab
[ slot
].fd
[ ERR
], "rb" );
293 if ( !cmdtab
[ slot
].stream
[ ERR
] )
300 /* Save input data into the selected running commands table slot. */
301 cmdtab
[ slot
].func
= func
;
302 cmdtab
[ slot
].closure
= closure
;
304 /* restore previous signals */
305 sigaction(SIGINT
, &saveintr
, NULL
);
306 sigaction(SIGQUIT
, &savequit
, NULL
);
307 sigprocmask(SIG_SETMASK
, &savemask
, NULL
);
310 #undef EXECCMD_PIPE_READ
311 #undef EXECCMD_PIPE_WRITE
314 /* Returns 1 if file descriptor is closed, or 0 if it is still alive.
316 * i is index into cmdtab
318 * s (stream) indexes:
319 * - cmdtab[ i ].stream[ s ]
320 * - cmdtab[ i ].buffer[ s ]
321 * - cmdtab[ i ].fd [ s ]
324 static int read_descriptor( int i
, int s
)
327 char buffer
[ BUFSIZ
];
329 while ( 0 < ( ret
= fread( buffer
, sizeof( char ), BUFSIZ
- 1,
330 cmdtab
[ i
].stream
[ s
] ) ) )
333 if ( !cmdtab
[ i
].buffer
[ s
] )
335 /* Never been allocated. */
336 if ( globs
.max_buf
&& ret
> globs
.max_buf
)
341 cmdtab
[ i
].buf_size
[ s
] = ret
+ 1;
342 cmdtab
[ i
].buffer
[ s
] = (char*)BJAM_MALLOC_ATOMIC( ret
+ 1 );
343 memcpy( cmdtab
[ i
].buffer
[ s
], buffer
, ret
+ 1 );
347 /* Previously allocated. */
348 if ( cmdtab
[ i
].buf_size
[ s
] < globs
.max_buf
|| !globs
.max_buf
)
350 char * tmp
= cmdtab
[ i
].buffer
[ s
];
351 int const old_len
= cmdtab
[ i
].buf_size
[ s
] - 1;
352 int const new_len
= old_len
+ ret
+ 1;
353 cmdtab
[ i
].buf_size
[ s
] = new_len
;
354 cmdtab
[ i
].buffer
[ s
] = (char*)BJAM_MALLOC_ATOMIC( new_len
);
355 memcpy( cmdtab
[ i
].buffer
[ s
], tmp
, old_len
);
356 memcpy( cmdtab
[ i
].buffer
[ s
] + old_len
, buffer
, ret
+ 1 );
362 /* If buffer full, ensure last buffer char is newline so that jam log
363 * contains the command status at beginning of it own line instead of
364 * appended to end of the previous output.
366 if ( globs
.max_buf
&& globs
.max_buf
<= cmdtab
[ i
].buf_size
[ s
] )
367 cmdtab
[ i
].buffer
[ s
][ cmdtab
[ i
].buf_size
[ s
] - 2 ] = '\n';
369 return feof( cmdtab
[ i
].stream
[ s
] );
374 * close_streams() - Close the stream and pipe descriptor.
377 static void close_streams( int const i
, int const s
)
379 fclose( cmdtab
[ i
].stream
[ s
] );
380 cmdtab
[ i
].stream
[ s
] = 0;
382 close( cmdtab
[ i
].fd
[ s
] );
383 cmdtab
[ i
].fd
[ s
] = 0;
388 * Populate the file descriptors collection for use in select() and return the
389 * maximal included file descriptor value.
392 static int populate_file_descriptors( fd_set
* const fds
)
398 for ( i
= 0; i
< globs
.jobs
; ++i
)
401 if ( ( fd
= cmdtab
[ i
].fd
[ OUT
] ) > 0 )
403 if ( fd
> fd_max
) fd_max
= fd
;
406 if ( globs
.pipe_action
)
408 if ( ( fd
= cmdtab
[ i
].fd
[ ERR
] ) > 0 )
410 if ( fd
> fd_max
) fd_max
= fd
;
420 * exec_wait() - wait for any of the async command processes to terminate.
422 * May register more than one terminated child process but will exit as soon as
423 * at least one has been registered.
430 /* Process children that signaled. */
435 struct timeval
* ptv
= NULL
;
436 int select_timeout
= globs
.timeout
;
438 /* Prepare file descriptor information for use in select(). */
440 int const fd_max
= populate_file_descriptors( &fds
);
442 /* Check for timeouts:
443 * - kill children that already timed out
444 * - decide how long until the next one times out
446 if ( globs
.timeout
> 0 )
449 clock_t const current
= times( &buf
);
450 for ( i
= 0; i
< globs
.jobs
; ++i
)
451 if ( cmdtab
[ i
].pid
)
453 clock_t const consumed
=
454 ( current
- cmdtab
[ i
].start_time
) / tps
;
455 if ( consumed
>= globs
.timeout
)
457 killpg( cmdtab
[ i
].pid
, SIGKILL
);
458 cmdtab
[ i
].exit_reason
= EXIT_TIMEOUT
;
460 else if ( globs
.timeout
- consumed
< select_timeout
)
461 select_timeout
= globs
.timeout
- consumed
;
464 /* If nothing else causes our select() call to exit, force it after
465 * however long it takes for the next one of our child processes to
466 * crossed its alloted processing time so we can terminate it.
468 tv
.tv_sec
= select_timeout
;
473 /* select() will wait for I/O on a descriptor, a signal, or timeout. */
475 /* disable child termination signals while in select */
478 sigemptyset(&sigmask
);
479 sigaddset(&sigmask
, SIGCHLD
);
480 sigprocmask(SIG_BLOCK
, &sigmask
, NULL
);
481 while ( ( ret
= select( fd_max
+ 1, &fds
, 0, 0, ptv
) ) == -1 )
482 if ( errno
!= EINTR
)
484 /* restore original signal mask by unblocking sigchld */
485 sigprocmask(SIG_UNBLOCK
, &sigmask
, NULL
);
490 for ( i
= 0; i
< globs
.jobs
; ++i
)
494 if ( FD_ISSET( cmdtab
[ i
].fd
[ OUT
], &fds
) )
495 out_done
= read_descriptor( i
, OUT
);
497 if ( globs
.pipe_action
&& FD_ISSET( cmdtab
[ i
].fd
[ ERR
], &fds
) )
498 err_done
= read_descriptor( i
, ERR
);
500 /* If feof on either descriptor, we are done. */
501 if ( out_done
|| err_done
)
506 timing_info time_info
;
507 struct rusage cmd_usage
;
509 /* We found a terminated child process - our search is done. */
512 /* Close the stream and pipe descriptors. */
513 close_streams( i
, OUT
);
514 if ( globs
.pipe_action
)
515 close_streams( i
, ERR
);
517 /* Reap the child and release resources. */
518 while ( ( pid
= wait4( cmdtab
[ i
].pid
, &status
, 0, &cmd_usage
) ) == -1 )
519 if ( errno
!= EINTR
)
521 if ( pid
!= cmdtab
[ i
].pid
)
523 err_printf( "unknown pid %d with errno = %d\n", pid
, errno
);
527 /* Set reason for exit if not timed out. */
528 if ( WIFEXITED( status
) )
529 cmdtab
[ i
].exit_reason
= WEXITSTATUS( status
)
534 time_info
.system
= ((double)(cmd_usage
.ru_stime
.tv_sec
)*1000000.0+(double)(cmd_usage
.ru_stime
.tv_usec
))/1000000.0;
535 time_info
.user
= ((double)(cmd_usage
.ru_utime
.tv_sec
)*1000000.0+(double)(cmd_usage
.ru_utime
.tv_usec
))/1000000.0;
536 timestamp_copy( &time_info
.start
, &cmdtab
[ i
].start_dt
);
537 timestamp_current( &time_info
.end
);
540 /* Drive the completion. */
542 rstat
= EXEC_CMD_INTR
;
544 rstat
= EXEC_CMD_FAIL
;
548 /* Call the callback, may call back to jam rule land. */
549 (*cmdtab
[ i
].func
)( cmdtab
[ i
].closure
, rstat
, &time_info
,
550 cmdtab
[ i
].buffer
[ OUT
], cmdtab
[ i
].buffer
[ ERR
],
551 cmdtab
[ i
].exit_reason
);
553 /* Clean up the command's running commands table slot. */
554 BJAM_FREE( cmdtab
[ i
].buffer
[ OUT
] );
555 cmdtab
[ i
].buffer
[ OUT
] = 0;
556 cmdtab
[ i
].buf_size
[ OUT
] = 0;
558 BJAM_FREE( cmdtab
[ i
].buffer
[ ERR
] );
559 cmdtab
[ i
].buffer
[ ERR
] = 0;
560 cmdtab
[ i
].buf_size
[ ERR
] = 0;
563 cmdtab
[ i
].func
= 0;
564 cmdtab
[ i
].closure
= 0;
565 cmdtab
[ i
].start_time
= 0;
573 * Find a free slot in the running commands table.
576 static int get_free_cmdtab_slot()
579 for ( slot
= 0; slot
< MAXJOBS
; ++slot
)
580 if ( !cmdtab
[ slot
].pid
)
582 err_printf( "no slots for child!\n" );
586 # endif /* USE_EXECUNIX */