Imported Upstream version 201104
[platform/upstream/boost-jam.git] / execunix.c
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 "lists.h"
10 #include "execcmd.h"
11 #include "output.h"
12 #include <errno.h>
13 #include <signal.h>
14 #include <stdio.h>
15 #include <time.h>
16 #include <unistd.h>  /* needed for vfork(), _exit() prototypes */
17 #include <sys/resource.h>
18 #include <sys/times.h>
19 #include <sys/wait.h>
20
21 #if defined(sun) || defined(__sun) || defined(linux)
22     #include <wait.h>
23 #endif
24
25 #ifdef USE_EXECUNIX
26
27 #include <sys/times.h>
28
29 #if defined(__APPLE__)
30     #define NO_VFORK
31 #endif
32
33 #ifdef NO_VFORK
34     #define vfork() fork()
35 #endif
36
37
38 /*
39  * execunix.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS
40  *
41  * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
42  * The default is:
43  *
44  *  /bin/sh -c %        [ on UNIX/AmigaOS ]
45  *  cmd.exe /c %        [ on OS2/WinNT ]
46  *
47  * Each word must be an individual element in a jam variable value.
48  *
49  * In $(JAMSHELL), % expands to the command string and ! expands to the slot
50  * number (starting at 1) for multiprocess (-j) invocations. If $(JAMSHELL) does
51  * not include a %, it is tacked on as the last argument.
52  *
53  * Do not just set JAMSHELL to /bin/sh or cmd.exe - it will not work!
54  *
55  * External routines:
56  *  exec_cmd() - launch an async command execution.
57  *  exec_wait() - wait and drive at most one execution completion.
58  *
59  * Internal routines:
60  *  onintr() - bump intr to note command interruption.
61  *
62  * 04/08/94 (seiwald) - Coherent/386 support added.
63  * 05/04/94 (seiwald) - async multiprocess interface
64  * 01/22/95 (seiwald) - $(JAMSHELL) support
65  * 06/02/97 (gsar)    - full async multiprocess support for Win32
66  */
67
68 static clock_t tps = 0;
69 static struct timeval tv;
70 static int select_timeout = 0;
71 static int intr = 0;
72 static int cmdsrunning = 0;
73 static struct tms old_time;
74
75 #define OUT 0
76 #define ERR 1
77
78 static struct
79 {
80     int     pid;              /* on win32, a real process handle */
81     int     fd[2];            /* file descriptors for stdout and stderr */
82     FILE   *stream[2];        /* child's stdout (0) and stderr (1) file stream */
83     clock_t start_time;       /* start time of child process */
84     int     exit_reason;      /* termination status */
85     int     action_length;    /* length of action string */
86     int     target_length;    /* length of target string */
87     char   *action;           /* buffer to hold action and target invoked */
88     char   *target;           /* buffer to hold action and target invoked */
89     char   *command;          /* buffer to hold command being invoked */
90     char   *buffer[2];        /* buffer to hold stdout and stderr, if any */
91     void    (*func)( void *closure, int status, timing_info*, char *, char * );
92     void   *closure;
93     time_t  start_dt;         /* start of command timestamp */
94 } cmdtab[ MAXJOBS ] = {{0}};
95
96 /*
97  * onintr() - bump intr to note command interruption
98  */
99
100 void onintr( int disp )
101 {
102     ++intr;
103     printf( "...interrupted\n" );
104 }
105
106
107 /*
108  * exec_cmd() - launch an async command execution.
109  */
110
111 void exec_cmd
112 (
113     char * string,
114     void (*func)( void *closure, int status, timing_info*, char *, char * ),
115     void * closure,
116     LIST * shell,
117     char * action,
118     char * target
119 )
120 {
121     static int initialized = 0;
122     int    out[2];
123     int    err[2];
124     int    slot;
125     int    len;
126     char * argv[ MAXARGC + 1 ];  /* +1 for NULL */
127
128     /* Find a slot in the running commands table for this one. */
129     for ( slot = 0; slot < MAXJOBS; ++slot )
130         if ( !cmdtab[ slot ].pid )
131             break;
132
133     if ( slot == MAXJOBS )
134     {
135         printf( "no slots for child!\n" );
136         exit( EXITBAD );
137     }
138
139     /* Forumulate argv. If shell was defined, be prepared for % and ! subs.
140      * Otherwise, use stock /bin/sh on unix or cmd.exe on NT.
141      */
142     if ( shell )
143     {
144         int  i;
145         char jobno[4];
146         int  gotpercent = 0;
147
148         sprintf( jobno, "%d", slot + 1 );
149
150         for ( i = 0; shell && i < MAXARGC; ++i, shell = list_next( shell ) )
151         {
152             switch ( shell->string[0] )
153             {
154                 case '%': argv[ i ] = string; ++gotpercent; break;
155                 case '!': argv[ i ] = jobno;                break;
156                 default : argv[ i ] = shell->string;
157             }
158             if ( DEBUG_EXECCMD )
159                 printf( "argv[%d] = '%s'\n", i, argv[ i ] );
160         }
161
162         if ( !gotpercent )
163         argv[ i++ ] = string;
164
165         argv[ i ] = 0;
166     }
167     else
168     {
169         argv[ 0 ] = "/bin/sh";
170         argv[ 1 ] = "-c";
171         argv[ 2 ] = string;
172         argv[ 3 ] = 0;
173     }
174
175     /* Increment jobs running. */
176     ++cmdsrunning;
177
178     /* Save off actual command string. */
179     cmdtab[ slot ].command = BJAM_MALLOC_ATOMIC( strlen( string ) + 1 );
180     strcpy( cmdtab[ slot ].command, string );
181
182     /* Initialize only once. */
183     if ( !initialized )
184     {
185         times( &old_time );
186         initialized = 1;
187     }
188
189     /* Create pipes from child to parent. */
190     {
191         if ( pipe( out ) < 0 )
192             exit( EXITBAD );
193
194         if ( pipe( err ) < 0 )
195             exit( EXITBAD );
196     }
197
198     /* Start the command */
199
200     cmdtab[ slot ].start_dt = time(0);
201
202     if ( 0 < globs.timeout )
203     {
204         /*
205          * Handle hung processes by manually tracking elapsed time and signal
206          * process when time limit expires.
207          */
208         struct tms buf;
209         cmdtab[ slot ].start_time = times( &buf );
210
211         /* Make a global, only do this once. */
212         if ( tps == 0 ) tps = sysconf( _SC_CLK_TCK );
213     }
214
215     if ( ( cmdtab[ slot ].pid = vfork() ) == 0 )
216     {
217         int pid = getpid();
218
219         close( out[0] );
220         close( err[0] );
221
222         dup2( out[1], STDOUT_FILENO );
223
224         if ( globs.pipe_action == 0 )
225             dup2( out[1], STDERR_FILENO );
226         else
227             dup2( err[1], STDERR_FILENO );
228
229         close( out[1] );
230         close( err[1] );
231
232         /* Make this process a process group leader so that when we kill it, all
233          * child processes of this process are terminated as well. We use
234          * killpg(pid, SIGKILL) to kill the process group leader and all its
235          * children.
236          */
237         if ( 0 < globs.timeout )
238         {
239             struct rlimit r_limit;
240             r_limit.rlim_cur = globs.timeout;
241             r_limit.rlim_max = globs.timeout;
242             setrlimit( RLIMIT_CPU, &r_limit );
243         }
244         setpgid( pid,pid );
245         execvp( argv[0], argv );
246         perror( "execvp" );
247         _exit( 127 );
248     }
249     else if ( cmdtab[ slot ].pid == -1 )
250     {
251         perror( "vfork" );
252         exit( EXITBAD );
253     }
254
255     setpgid( cmdtab[ slot ].pid, cmdtab[ slot ].pid );
256
257     /* close write end of pipes */
258     close( out[1] );
259     close( err[1] );
260
261     /* set both file descriptors to non-blocking */
262     fcntl(out[0], F_SETFL, O_NONBLOCK);
263     fcntl(err[0], F_SETFL, O_NONBLOCK);
264
265     /* child writes stdout to out[1], parent reads from out[0] */
266     cmdtab[ slot ].fd[ OUT ] = out[0];
267     cmdtab[ slot ].stream[ OUT ] = fdopen( cmdtab[ slot ].fd[ OUT ], "rb" );
268     if ( cmdtab[ slot ].stream[ OUT ] == NULL )
269     {
270         perror( "fdopen" );
271         exit( EXITBAD );
272     }
273
274     /* child writes stderr to err[1], parent reads from err[0] */
275     if (globs.pipe_action == 0)
276     {
277       close(err[0]);
278     }
279     else
280     {
281         cmdtab[ slot ].fd[ ERR ] = err[0];
282         cmdtab[ slot ].stream[ ERR ] = fdopen( cmdtab[ slot ].fd[ ERR ], "rb" );
283         if ( cmdtab[ slot ].stream[ ERR ] == NULL )
284         {
285             perror( "fdopen" );
286             exit( EXITBAD );
287         }
288     }
289
290     /* Ensure enough room for rule and target name. */
291     if ( action && target )
292     {
293         len = strlen( action ) + 1;
294         if ( cmdtab[ slot ].action_length < len )
295         {
296             BJAM_FREE( cmdtab[ slot ].action );
297             cmdtab[ slot ].action = BJAM_MALLOC_ATOMIC( len );
298             cmdtab[ slot ].action_length = len;
299         }
300         strcpy( cmdtab[ slot ].action, action );
301         len = strlen( target ) + 1;
302         if ( cmdtab[ slot ].target_length < len )
303         {
304             BJAM_FREE( cmdtab[ slot ].target );
305             cmdtab[ slot ].target = BJAM_MALLOC_ATOMIC( len );
306             cmdtab[ slot ].target_length = len;
307         }
308         strcpy( cmdtab[ slot ].target, target );
309     }
310     else
311     {
312         BJAM_FREE( cmdtab[ slot ].action );
313         BJAM_FREE( cmdtab[ slot ].target );
314         cmdtab[ slot ].action = 0;
315         cmdtab[ slot ].target = 0;
316         cmdtab[ slot ].action_length = 0;
317         cmdtab[ slot ].target_length = 0;
318     }
319
320     /* Save the operation for exec_wait() to find. */
321     cmdtab[ slot ].func = func;
322     cmdtab[ slot ].closure = closure;
323
324     /* Wait until we are under the limit of concurrent commands. Do not trust
325      * globs.jobs alone.
326      */
327     while ( ( cmdsrunning >= MAXJOBS ) || ( cmdsrunning >= globs.jobs ) )
328         if ( !exec_wait() )
329             break;
330 }
331
332
333 /* Returns 1 if file is closed, 0 if descriptor is still live.
334  *
335  * i is index into cmdtab
336  *
337  * s (stream) indexes:
338  *  - cmdtab[ i ].stream[ s ]
339  *  - cmdtab[ i ].buffer[ s ]
340  *  - cmdtab[ i ].fd    [ s ]
341  */
342
343 int read_descriptor( int i, int s )
344 {
345     int  ret;
346     int  len;
347     char buffer[BUFSIZ];
348
349     while ( 0 < ( ret = fread( buffer, sizeof(char),  BUFSIZ-1, cmdtab[ i ].stream[ s ] ) ) )
350     {
351         buffer[ret] = 0;
352         if  ( !cmdtab[ i ].buffer[ s ] )
353         {
354             /* Never been allocated. */
355             cmdtab[ i ].buffer[ s ] = (char*)BJAM_MALLOC_ATOMIC( ret + 1 );
356             memcpy( cmdtab[ i ].buffer[ s ], buffer, ret + 1 );
357         }
358         else
359         {
360             /* Previously allocated. */
361             char * tmp = cmdtab[ i ].buffer[ s ];
362             len = strlen( tmp );
363             cmdtab[ i ].buffer[ s ] = (char*)BJAM_MALLOC_ATOMIC( len + ret + 1 );
364             memcpy( cmdtab[ i ].buffer[ s ], tmp, len );
365             memcpy( cmdtab[ i ].buffer[ s ] + len, buffer, ret + 1 );
366             BJAM_FREE( tmp );
367         }
368     }
369
370     return feof(cmdtab[ i ].stream[ s ]);
371 }
372
373
374 void close_streams( int i, int s )
375 {
376     /* Close the stream and pipe descriptor. */
377     fclose(cmdtab[ i ].stream[ s ]);
378     cmdtab[ i ].stream[ s ] = 0;
379
380     close(cmdtab[ i ].fd[ s ]);
381     cmdtab[ i ].fd[ s ] = 0;
382 }
383
384
385 void populate_file_descriptors( int * fmax, fd_set * fds)
386 {
387     int i, fd_max = 0;
388     struct tms buf;
389     clock_t current = times( &buf );
390     select_timeout = globs.timeout;
391
392     /* Compute max read file descriptor for use in select. */
393     FD_ZERO(fds);
394     for ( i = 0; i < globs.jobs; ++i )
395     {
396         if ( 0 < cmdtab[ i ].fd[ OUT ] )
397         {
398             fd_max = fd_max < cmdtab[ i ].fd[ OUT ] ? cmdtab[ i ].fd[ OUT ] : fd_max;
399             FD_SET(cmdtab[ i ].fd[ OUT ], fds);
400         }
401         if ( globs.pipe_action != 0 )
402         {
403             if (0 < cmdtab[ i ].fd[ ERR ])
404             {
405                 fd_max = fd_max < cmdtab[ i ].fd[ ERR ] ? cmdtab[ i ].fd[ ERR ] : fd_max;
406                 FD_SET(cmdtab[ i ].fd[ ERR ], fds);
407             }
408         }
409
410         if (globs.timeout && cmdtab[ i ].pid) {
411             clock_t consumed = (current - cmdtab[ i ].start_time) / tps;
412             clock_t process_timesout = globs.timeout - consumed;
413             if (0 < process_timesout && process_timesout < select_timeout) {
414                 select_timeout = process_timesout;
415             }
416             if ( globs.timeout <= consumed )
417             {
418                 killpg( cmdtab[ i ].pid, SIGKILL );
419                 cmdtab[ i ].exit_reason = EXIT_TIMEOUT;
420             }
421         }
422     }
423     *fmax = fd_max;
424 }
425
426
427 /*
428  * exec_wait() - wait and drive at most one execution completion.
429  */
430
431 int exec_wait()
432 {
433     int         i;
434     int         ret;
435     int         fd_max;
436     int         pid;
437     int         status;
438     int         finished;
439     int         rstat;
440     timing_info time_info;
441     fd_set      fds;
442     struct tms  new_time;
443
444     /* Handle naive make1() which does not know if commands are running. */
445     if ( !cmdsrunning )
446         return 0;
447
448     /* Process children that signaled. */
449     finished = 0;
450     while ( !finished && cmdsrunning )
451     {
452         /* Compute max read file descriptor for use in select(). */
453         populate_file_descriptors( &fd_max, &fds );
454
455         if ( 0 < globs.timeout )
456         {
457             /* Force select() to timeout so we can terminate expired processes.
458              */
459             tv.tv_sec = select_timeout;
460             tv.tv_usec = 0;
461
462             /* select() will wait until: i/o on a descriptor, a signal, or we
463              * time out.
464              */
465             ret = select( fd_max + 1, &fds, 0, 0, &tv );
466         }
467         else
468         {
469             /* select() will wait until i/o on a descriptor or a signal. */
470             ret = select( fd_max + 1, &fds, 0, 0, 0 );
471         }
472
473         if ( 0 < ret )
474         {
475             for ( i = 0; i < globs.jobs; ++i )
476             {
477                 int out = 0;
478                 int err = 0;
479                 if ( FD_ISSET( cmdtab[ i ].fd[ OUT ], &fds ) )
480                     out = read_descriptor( i, OUT );
481
482                 if ( ( globs.pipe_action != 0 ) &&
483                     ( FD_ISSET( cmdtab[ i ].fd[ ERR ], &fds ) ) )
484                     err = read_descriptor( i, ERR );
485
486                 /* If feof on either descriptor, then we are done. */
487                 if ( out || err )
488                 {
489                     /* Close the stream and pipe descriptors. */
490                     close_streams( i, OUT );
491                     if ( globs.pipe_action != 0 )
492                         close_streams( i, ERR );
493
494                     /* Reap the child and release resources. */
495                     pid = waitpid( cmdtab[ i ].pid, &status, 0 );
496
497                     if ( pid == cmdtab[ i ].pid )
498                     {
499                         finished = 1;
500                         pid = 0;
501                         cmdtab[ i ].pid = 0;
502
503                         /* Set reason for exit if not timed out. */
504                         if ( WIFEXITED( status ) )
505                         {
506                             cmdtab[ i ].exit_reason = 0 == WEXITSTATUS( status )
507                                 ? EXIT_OK
508                                 : EXIT_FAIL;
509                         }
510
511                         /* Print out the rule and target name. */
512                         out_action( cmdtab[ i ].action, cmdtab[ i ].target,
513                             cmdtab[ i ].command, cmdtab[ i ].buffer[ OUT ],
514                             cmdtab[ i ].buffer[ ERR ], cmdtab[ i ].exit_reason
515                         );
516
517                         times( &new_time );
518
519                         time_info.system = (double)( new_time.tms_cstime - old_time.tms_cstime ) / CLOCKS_PER_SEC;
520                         time_info.user   = (double)( new_time.tms_cutime - old_time.tms_cutime ) / CLOCKS_PER_SEC;
521                         time_info.start  = cmdtab[ i ].start_dt;
522                         time_info.end    = time( 0 );
523
524                         old_time = new_time;
525
526                         /* Drive the completion. */
527                         --cmdsrunning;
528
529                         if ( intr )
530                             rstat = EXEC_CMD_INTR;
531                         else if ( status != 0 )
532                             rstat = EXEC_CMD_FAIL;
533                         else
534                             rstat = EXEC_CMD_OK;
535
536                         /* Assume -p0 in effect so only pass buffer[ 0 ]
537                          * containing merged output.
538                          */
539                         (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat,
540                             &time_info, cmdtab[ i ].command,
541                             cmdtab[ i ].buffer[ 0 ] );
542
543                         BJAM_FREE( cmdtab[ i ].buffer[ OUT ] );
544                         cmdtab[ i ].buffer[ OUT ] = 0;
545
546                         BJAM_FREE( cmdtab[ i ].buffer[ ERR ] );
547                         cmdtab[ i ].buffer[ ERR ] = 0;
548
549                         BJAM_FREE( cmdtab[ i ].command );
550                         cmdtab[ i ].command = 0;
551
552                         cmdtab[ i ].func = 0;
553                         cmdtab[ i ].closure = 0;
554                         cmdtab[ i ].start_time = 0;
555                     }
556                     else
557                     {
558                         printf( "unknown pid %d with errno = %d\n", pid, errno );
559                         exit( EXITBAD );
560                     }
561                 }
562             }
563         }
564     }
565
566     return 1;
567 }
568
569 # endif /* USE_EXECUNIX */