1101 lines
32 KiB
C
1101 lines
32 KiB
C
/* $RCSfile: runargv.c,v $
|
|
-- $Revision: 1.14 $
|
|
-- last change: $Author: kz $ $Date: 2008-03-05 18:39:41 $
|
|
--
|
|
-- SYNOPSIS
|
|
-- Invoke a sub process.
|
|
--
|
|
-- DESCRIPTION
|
|
-- Use the standard methods of executing a sub process.
|
|
--
|
|
-- AUTHOR
|
|
-- Dennis Vadura, dvadura@dmake.wticorp.com
|
|
--
|
|
-- WWW
|
|
-- http://dmake.wticorp.com/
|
|
--
|
|
-- COPYRIGHT
|
|
-- Copyright (c) 1996,1997 by WTI Corp. All rights reserved.
|
|
--
|
|
-- This program is NOT free software; you can redistribute it and/or
|
|
-- modify it under the terms of the Software License Agreement Provided
|
|
-- in the file <distribution-root>/readme/license.txt.
|
|
--
|
|
-- LOG
|
|
-- Use cvs log to obtain detailed change logs.
|
|
*/
|
|
/*
|
|
This file (runargv.c) provides all the parallel process handling routines
|
|
for dmake on unix like operating systems. The following text briefly
|
|
describes the process flow.
|
|
|
|
Exec_commands() [make.c] builds the recipes associated to the given target.
|
|
They are build sequentially in a loop that calls Do_cmnd() for each of them.
|
|
|
|
Do_cmnd() [sysintf.c] feeds the given command or command group to runargv().
|
|
|
|
The following flowchart decripes the process flow starting with runargv,
|
|
descriptions for each of the functions are following.
|
|
|
|
+--------------------------------+
|
|
| runargv | <+
|
|
+--------------------------------+ |
|
|
| ^ |
|
|
| | returns if |
|
|
| calls | wfc is false |
|
|
v | |
|
|
+--------------------------------+ |
|
|
| _add_child | |
|
|
+--------------------------------+ |
|
|
| ^ |
|
|
| calls if | | if another process
|
|
| wfc is true | returns | is queued:
|
|
v | | recursive call
|
|
+--------------------------------+ |
|
|
| Wait_for_Child | |
|
|
+--------------------------------+ |
|
|
| ^ |
|
|
| | process queue |
|
|
| calls | is empty |
|
|
v | |
|
|
+--------------------------------+ |
|
|
| _finished_child | -+
|
|
+--------------------------------+
|
|
|
|
|
|
|
|
runargv() [unix/runargv] The runargv function manages up to MAXPROCESS
|
|
process queues (_procs[i]) for parallel process execution and hands
|
|
the actual commands down to the operating system.
|
|
Each of the process queues handles the sequential execution of commands
|
|
that belong to that process queue. Usually this means the sequential
|
|
execution of the recipe lines that belong to one target.
|
|
Even in non parallel builds (MAXPROCESS==1) child processes are
|
|
created and handled.
|
|
If recipes for a target are currently running attach them to the
|
|
corresponding process queue (_procs[i]) of that target and return.
|
|
If the maximum number (MAXPROCESS) of concurrently running queues is
|
|
reached use Wait_for_child(?, -1) to wait for a process queue to become
|
|
available.
|
|
New child processes are started using:
|
|
spawn: posix_spawnp (POSIX) or spawnvp (cygwin).
|
|
fork/execvp: Create a client process with fork and run the command
|
|
with execvp.
|
|
The parent calls _add_child() to track the child.
|
|
|
|
_add_child(..., wfc) [unix/runargv] creates (or reuses) a process queue
|
|
and enters the child's parameters.
|
|
If wfc (wait for completion) is TRUE the function calls
|
|
Wait_for_child to wait for the whole process queue to be finished.
|
|
|
|
Wait_for_child(abort_flg, pqid) [unix/runargv] waits either for the current
|
|
process from process queue pqid to finish or if the W_WFC attribute is
|
|
set for all entries of that process queue (recursively) to finish.
|
|
All finished processes are handled by calling _finished_child() for each
|
|
of them.
|
|
If pqid == -1 wait for the next process to finish but honor the A_WFC
|
|
attribute of that process (queue) and wait for the whole queue if needed.
|
|
If abort_flg is TRUE no further processes will be added to any process
|
|
queue.
|
|
If a pqid is given but a process from another process queue finishes
|
|
first that process is handled and A_WFC is also honored.
|
|
All finished processes are processed until the process from the given pqid
|
|
is reached or gone (might have been handled while finishing another process
|
|
queue).
|
|
|
|
_finished_child(pid, status) [unix/runargv] handles the finished child. If
|
|
there are more commands in the corresponding process queue start the next
|
|
with runargv().
|
|
*/
|
|
|
|
#include <signal.h>
|
|
|
|
#include "extern.h"
|
|
|
|
#ifdef HAVE_WAIT_H
|
|
# include <wait.h>
|
|
#else
|
|
# ifdef HAVE_SYS_WAIT_H
|
|
# include <sys/wait.h>
|
|
# endif
|
|
#endif
|
|
|
|
#if HAVE_SPAWN_H && ENABLE_SPAWN
|
|
# include <spawn.h>
|
|
#endif
|
|
|
|
#if __CYGWIN__ && ENABLE_SPAWN
|
|
# include <process.h>
|
|
#endif
|
|
|
|
#ifdef __EMX__
|
|
# include <process.h>
|
|
#define _P_NOWAIT P_NOWAIT
|
|
#endif
|
|
|
|
#include "sysintf.h"
|
|
#if HAVE_ERRNO_H
|
|
# include <errno.h>
|
|
#else
|
|
extern int errno;
|
|
#endif
|
|
|
|
typedef struct prp {
|
|
char *prp_cmd;
|
|
int prp_group;
|
|
t_attr prp_attr;
|
|
int prp_last;
|
|
struct prp *prp_next;
|
|
} RCP, *RCPPTR;
|
|
|
|
#if defined(USE_CREATEPROCESS)
|
|
/* MS's HANDLE is basically a (void *) (winnt.h). */
|
|
typedef HANDLE DMHANDLE;
|
|
#else
|
|
typedef int DMHANDLE;
|
|
#endif
|
|
|
|
typedef struct pr {
|
|
int pr_valid;
|
|
DMHANDLE pr_pid;
|
|
DMHANDLE pr_tid;
|
|
CELLPTR pr_target;
|
|
int pr_ignore;
|
|
int pr_last;
|
|
int pr_wfc;
|
|
RCPPTR pr_recipe;
|
|
RCPPTR pr_recipe_end;
|
|
char *pr_dir;
|
|
} PR;
|
|
|
|
typedef struct tpid {
|
|
DMHANDLE pid;
|
|
DMHANDLE tid;
|
|
} TPID;
|
|
|
|
const TPID DMNOPID = { (DMHANDLE)-1, (DMHANDLE)0 };
|
|
|
|
static PR *_procs = NIL(PR); /* Array to hold concurrent processes. */
|
|
static int _procs_size = 0; /* Savegard to find MAXPROCESS changes. */
|
|
static int _proc_cnt = 0; /* Number of running processes. */
|
|
static int _abort_flg= FALSE;
|
|
static int _use_i = -1;
|
|
#if defined(USE_CREATEPROCESS)
|
|
static HANDLE *_wpList = NIL(HANDLE); /* Array to hold pids to wait for. */
|
|
#endif
|
|
|
|
static int _add_child ANSI((TPID, CELLPTR, int, int, int));
|
|
static void _attach_cmd ANSI((char *, int, CELLPTR, t_attr, int));
|
|
static void _finished_child ANSI((DMHANDLE, int));
|
|
static int _running ANSI((CELLPTR));
|
|
|
|
/* Machine/OS dependent helpers. */
|
|
static int dmwaitnext ANSI((DMHANDLE *, int *));
|
|
static int dmwaitpid ANSI((int, DMHANDLE *, int *));
|
|
|
|
#if defined( USE_SPAWN )
|
|
|
|
int terrno; /* Temporarily store errno. */
|
|
|
|
static TPID dmspawn ANSI((char **));
|
|
|
|
static TPID
|
|
dmspawn( argv )
|
|
char **argv;
|
|
{
|
|
TPID pid;
|
|
|
|
/* No error output is done here as stdout/stderr might be redirected. */
|
|
#if defined( __CYGWIN__) || defined( __EMX__)
|
|
pid.pid = spawnvp(_P_NOWAIT, argv[0], (const char**) argv);
|
|
pid.tid = 0;
|
|
#elif defined(USE_CREATEPROCESS)
|
|
static STARTUPINFO si;
|
|
static int initSTARTUPINFO = FALSE;
|
|
PROCESS_INFORMATION pi;
|
|
|
|
/* si can be reused. */
|
|
if( initSTARTUPINFO == FALSE ) {
|
|
initSTARTUPINFO = TRUE;
|
|
ZeroMemory( &si, sizeof(si) );
|
|
si.cb = sizeof(si);
|
|
}
|
|
ZeroMemory( &pi, sizeof(pi) );
|
|
|
|
/* Start the child process. CreateProcess() parameters:
|
|
* No module name (use command line).
|
|
* Command line. This fails if the path to the program contains spaces.
|
|
* Process handle not inheritable.
|
|
* Thread handle not inheritable.
|
|
* Set handle inheritance (stdout, stderr, etc.) to TRUE.
|
|
* No creation flags.
|
|
* Use parent's environment block.
|
|
* Use parent's starting directory.
|
|
* Pointer to STARTUPINFO structure.
|
|
* Pointer to PROCESS_INFORMATION structure. */
|
|
if( CreateProcess(NULL, argv[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) ) {
|
|
pid.pid = pi.hProcess;
|
|
pid.tid = pi.hThread;
|
|
} else {
|
|
fprintf(stderr, "CreateProcess failed (%d).\n", GetLastError() );
|
|
pid.pid = (DMHANDLE)-1;
|
|
}
|
|
#else /* Non cygwin, OS/2, MinGW and MSC */
|
|
int tpid;
|
|
if (posix_spawnp (&tpid, argv[0], NULL, NULL, argv, (char *)NULL))
|
|
tpid = -1; /* posix_spawn failed */
|
|
|
|
pid.pid = tpid;
|
|
pid.tid = 0;
|
|
#endif /* __CYGWIN__ */
|
|
return pid;
|
|
}
|
|
|
|
#endif /* USE_SPAWN */
|
|
|
|
static int
|
|
dmwaitnext( wid, status )
|
|
DMHANDLE *wid; /* Id we waited for. */
|
|
int *status; /* status of the finished process. */
|
|
/* return 1 if a process finished, -1 if there
|
|
* was nothing to wait for (ECHILD) and -2 for other errors. */
|
|
{
|
|
|
|
#if !defined(USE_CREATEPROCESS)
|
|
/* Here might be the culprit for the famous OOo build hang. If
|
|
* cygwin manages to "loose" a process and none else is left the
|
|
* wait() will wait forever. */
|
|
*wid = wait(status);
|
|
|
|
/* If ECHILD is set from waitpid/wait then no child was left. */
|
|
if( *wid == -1 ) {
|
|
int realErr = errno; // fprintf can pollute errno
|
|
fprintf(stderr, "%s: Internal Error: wait() failed: %d - %s\n",
|
|
Pname, errno, strerror(errno) );
|
|
if( realErr != ECHILD ) {
|
|
/* Wait was interrupted or a child was terminated (SIGCHLD) */
|
|
return -2;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
#else
|
|
DWORD pEvent;
|
|
DWORD dwExitCode;
|
|
int i;
|
|
int numProc = 0;
|
|
|
|
*status = 0;
|
|
|
|
/* Create a list of possible objects to wait for. */
|
|
for( i=0; i<Max_proc; i++ ) {
|
|
if(_procs[i].pr_valid) {
|
|
_wpList[numProc++] = _procs[i].pr_pid;
|
|
}
|
|
}
|
|
if( numProc == 0 ) {
|
|
fprintf(stderr, "%s: Internal Error: dmwaitnext() failed: "
|
|
"Nothing to wait for.\n", Pname );
|
|
return -1;
|
|
}
|
|
|
|
/* Wait ... */
|
|
/* number of objects in array, array of objects,
|
|
* wait for any object, wait for the next child to finish */
|
|
pEvent = WaitForMultipleObjects( numProc, _wpList, FALSE, INFINITE);
|
|
|
|
if( pEvent >= 0 && pEvent < WAIT_OBJECT_0 + numProc ) {
|
|
*wid = _wpList[pEvent - WAIT_OBJECT_0];
|
|
for( i=0; i<Max_proc && _procs[i].pr_pid != *wid; i++ )
|
|
;
|
|
if( i == Max_proc )
|
|
Fatal("Internal Error: Process not in pq !");
|
|
|
|
GetExitCodeProcess(*wid, &dwExitCode);
|
|
if(dwExitCode == STILL_ACTIVE) {
|
|
/* Process did not terminate -> force it, with exit code 1. */
|
|
TerminateProcess(*wid, 1);
|
|
dwExitCode = 1;
|
|
fprintf(stderr, "%s: Internal Error: Process still running - "
|
|
"terminate it!\n", Pname );
|
|
}
|
|
|
|
/* Close process and thread handles. */
|
|
CloseHandle( *wid );
|
|
CloseHandle( _procs[i].pr_tid );
|
|
*status = dwExitCode;
|
|
}
|
|
else {
|
|
int err = GetLastError();
|
|
LPVOID lpMsgBuf;
|
|
|
|
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
err,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR) &lpMsgBuf,
|
|
0, NULL );
|
|
|
|
fprintf(stderr, "%s: Internal Error: WaitForMultipleObjects() (%d) failed:"
|
|
" %d - %s\n", Pname, numProc, err, lpMsgBuf);
|
|
LocalFree(lpMsgBuf);
|
|
|
|
/* No way to identify something comparable to ECHILD, always return -2.*/
|
|
return -2;
|
|
}
|
|
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int
|
|
dmwaitpid( pqid, wid, status )
|
|
int pqid; /* Process queue to wait for. */
|
|
DMHANDLE *wid; /* Id we waited for. */
|
|
int *status; /* status of the finished process. */
|
|
/* return 1 if the process finished, 0 if it didn't finish yet, -1 if there
|
|
* was nothing to wait for (ECHILD) and -2 for other errors. */
|
|
{
|
|
|
|
#if !defined(USE_CREATEPROCESS)
|
|
*wid = waitpid(_procs[pqid].pr_pid, status, WNOHANG);
|
|
|
|
/* Process still running. */
|
|
if( *wid == 0 ) {
|
|
*status = 0;
|
|
return 0;
|
|
}
|
|
/* If ECHILD is set from waitpid/wait then no child was left. */
|
|
if( *wid == -1 ) {
|
|
int realErr = errno; // fprintf can pollute errno
|
|
fprintf(stderr, "%s: Internal Error: waitpid() failed: %d - %s\n",
|
|
Pname, errno, strerror(errno) );
|
|
if(realErr != ECHILD) {
|
|
/* Wait was interrupted or a child was terminated (SIGCHLD) */
|
|
return -2;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
#else
|
|
DWORD pEvent;
|
|
DWORD dwExitCode;
|
|
|
|
*wid = _procs[pqid].pr_pid;
|
|
*status = 0;
|
|
|
|
/* Wait ... (Check status and return) */
|
|
pEvent = WaitForSingleObject(*wid, 0);
|
|
|
|
if( pEvent == WAIT_OBJECT_0 ) {
|
|
GetExitCodeProcess(*wid, &dwExitCode);
|
|
if(dwExitCode == STILL_ACTIVE) {
|
|
/* Process did not terminate -> force it, with exit code 1. */
|
|
TerminateProcess(*wid, 1);
|
|
dwExitCode = 1;
|
|
fprintf(stderr, "%s: Internal Error: Process still running - "
|
|
"terminate it!\n", Pname );
|
|
}
|
|
|
|
/* Close process and thread handles. */
|
|
CloseHandle( *wid );
|
|
CloseHandle( _procs[pqid].pr_tid );
|
|
*status = dwExitCode;
|
|
}
|
|
else if( pEvent == WAIT_TIMEOUT ) {
|
|
return 0;
|
|
}
|
|
else {
|
|
int err = GetLastError();
|
|
LPVOID lpMsgBuf;
|
|
|
|
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
err,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR) &lpMsgBuf,
|
|
0, NULL );
|
|
|
|
fprintf(stderr, "%s: Internal Error: WaitForSingleObject() failed:"
|
|
" %d - %s\n", Pname, err, lpMsgBuf);
|
|
LocalFree(lpMsgBuf);
|
|
|
|
/* No way to identify something comparable to ECHILD, always return -2.*/
|
|
return -2;
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
#if ! HAVE_STRERROR
|
|
static char *
|
|
private_strerror (errnum)
|
|
int errnum;
|
|
{
|
|
#ifndef __APPLE__
|
|
# if defined(arm32) || defined(linux) || defined(__FreeBSD__) || \
|
|
defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
|
extern const char * const sys_errlist[];
|
|
# else
|
|
extern char *sys_errlist[];
|
|
# endif
|
|
#endif
|
|
extern int sys_nerr;
|
|
|
|
if (errnum > 0 && errnum <= sys_nerr)
|
|
return sys_errlist[errnum];
|
|
return "Unknown system error";
|
|
}
|
|
#define strerror private_strerror
|
|
#endif /* HAVE_STRERROR */
|
|
|
|
PUBLIC int
|
|
runargv(target, group, last, cmnd_attr, cmd)/*
|
|
==============================================
|
|
Execute the command given by cmd.
|
|
|
|
Return 0 if the command executed and finished or
|
|
1 if the command started and is running.
|
|
*/
|
|
CELLPTR target;
|
|
int group;
|
|
int last;
|
|
t_attr cmnd_attr; /* Attributes for current cmnd. */
|
|
char **cmd; /* Simulate a reference to *cmd. */
|
|
{
|
|
int ignore = (cmnd_attr & A_IGNORE)!= 0; /* Ignore errors ('-'). */
|
|
int shell = (cmnd_attr & A_SHELL) != 0; /* Use shell ('+'). */
|
|
int mute = (cmnd_attr & A_MUTE) != 0; /* Mute output ('@@'). */
|
|
int wfc = (cmnd_attr & A_WFC) != 0; /* Wait for completion. */
|
|
|
|
TPID pid;
|
|
int st_pq = 0; /* Current _exec_shell target process index */
|
|
char *tcmd = *cmd; /* For saver/easier string arithmetic on *cmd. */
|
|
char **argv;
|
|
|
|
int old_stdout = -1; /* For shell escapes and */
|
|
int old_stderr = -1; /* @@-recipe silencing. */
|
|
int internal = 0; /* Used to indicate internal command. */
|
|
|
|
DB_ENTER( "runargv" );
|
|
|
|
/* Special handling for the shell function macro is required. If the
|
|
* currend command is called as part of a shell escape in a recipe make
|
|
* sure that all previous recipe lines of this target have finished. */
|
|
if( Is_exec_shell ) {
|
|
if( (st_pq = _running(Shell_exec_target)) != -1 ) {
|
|
RCPPTR rp;
|
|
/* Add WFC to _procs[st_pq]. */
|
|
_procs[st_pq].pr_wfc = TRUE;
|
|
/* Set also the A_WFC flag in the recipe attributes. */
|
|
for( rp = _procs[st_pq].pr_recipe ; rp != NIL(RCP); rp = rp->prp_next )
|
|
rp->prp_attr |= A_WFC;
|
|
|
|
Wait_for_child(FALSE, st_pq);
|
|
}
|
|
} else {
|
|
if( _running(target) != -1 /*&& Max_proc != 1*/ ) {
|
|
/* The command will be executed when the previous recipe
|
|
* line completes. */
|
|
_attach_cmd( *cmd, group, target, cmnd_attr, last );
|
|
DB_RETURN( 1 );
|
|
}
|
|
}
|
|
|
|
/* If all process array entries are used wait until we get a free
|
|
* slot. For Max_proc == 1 this forces sequential execution. */
|
|
while( _proc_cnt == Max_proc ) {
|
|
Wait_for_child(FALSE, -1);
|
|
}
|
|
|
|
/* Return immediately for empty line or noop command. */
|
|
if ( !*tcmd || /* empty line */
|
|
( strncmp(tcmd, "noop", 4) == 0 && /* noop command */
|
|
(iswhite(tcmd[4]) || tcmd[4] == '\0')) ) {
|
|
internal = 1;
|
|
}
|
|
else if( !shell && /* internal echo only if not in shell */
|
|
strncmp(tcmd, "echo", 4) == 0 &&
|
|
(iswhite(tcmd[4]) || tcmd[4] == '\0') ) {
|
|
int nl = 1;
|
|
|
|
tcmd = tcmd+4;
|
|
while( iswhite(*tcmd) ) ++tcmd;
|
|
if ( strncmp(tcmd,"-n",2 ) == 0) {
|
|
nl = 0;
|
|
tcmd = tcmd+2;
|
|
while( iswhite(*tcmd) ) ++tcmd;
|
|
}
|
|
|
|
/* redirect output for _exec_shell / @@-recipes. */
|
|
if( Is_exec_shell ) {
|
|
/* Add error checking? */
|
|
old_stdout = dup(1);
|
|
dup2( fileno(stdout_redir), 1 );
|
|
}
|
|
if( mute ) {
|
|
old_stderr = dup(2);
|
|
dup2( zerofd, 2 );
|
|
|
|
if( !Is_exec_shell ) {
|
|
old_stdout = dup(1);
|
|
dup2( zerofd, 1 );
|
|
}
|
|
}
|
|
|
|
printf("%s%s", tcmd, nl ? "\n" : "");
|
|
fflush(stdout);
|
|
|
|
/* Restore stdout/stderr if needed. */
|
|
if( old_stdout != -1 ) {
|
|
dup2(old_stdout, 1);
|
|
close(old_stdout);
|
|
if( old_stderr != -1 ) {
|
|
dup2(old_stderr, 2);
|
|
close(old_stderr);
|
|
}
|
|
}
|
|
|
|
internal = 1;
|
|
}
|
|
if ( internal ) {
|
|
/* Use _add_child() / _finished_child() with internal command. */
|
|
int cur_proc = _add_child(DMNOPID, target, ignore, last, FALSE);
|
|
_finished_child( (DMHANDLE)-cur_proc, 0 );
|
|
DB_RETURN( 0 );
|
|
}
|
|
|
|
/* Pack cmd in argument vector. */
|
|
argv = Pack_argv( group, shell, cmd );
|
|
|
|
/* Really spawn or fork a child. */
|
|
#if defined( USE_SPAWN )
|
|
/* As no other childs are started while the output is redirected this
|
|
* is save. */
|
|
if( Is_exec_shell ) {
|
|
/* Add error checking? */
|
|
old_stdout = dup(1);
|
|
dup2( fileno(stdout_redir), 1 );
|
|
}
|
|
if( mute ) {
|
|
old_stderr = dup(2);
|
|
dup2( zerofd, 2 );
|
|
|
|
if( !Is_exec_shell ) {
|
|
old_stdout = dup(1);
|
|
dup2( zerofd, 1 );
|
|
}
|
|
}
|
|
|
|
pid = dmspawn( argv );
|
|
terrno = errno;
|
|
|
|
if( old_stdout != -1 ) {
|
|
dup2(old_stdout, 1);
|
|
close(old_stdout);
|
|
if( old_stderr != -1 ) {
|
|
dup2(old_stderr, 2);
|
|
close(old_stderr);
|
|
}
|
|
}
|
|
if(pid.pid == (DMHANDLE)-1) {
|
|
/* spawn failed */
|
|
int cur_proc;
|
|
|
|
fprintf(stderr, "%s: Error executing '%s': %s",
|
|
Pname, argv[0], strerror(terrno) );
|
|
if( ignore||Continue ) {
|
|
fprintf(stderr, " (Ignored)" );
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
/* Use _add_child() / _finished_child() to treat the failure
|
|
* gracefully, if so requested. */
|
|
cur_proc = _add_child(DMNOPID, target, ignore, last, FALSE);
|
|
_finished_child((DMHANDLE)cur_proc, SIGTERM);
|
|
|
|
/* _finished_child() aborts dmake if we are not told to
|
|
* ignore errors. If we reach the this point return 0 as
|
|
* errors are obviously ignored and indicate that the process
|
|
* finished. */
|
|
DB_RETURN( 0 );
|
|
} else {
|
|
_add_child(pid, target, ignore, last, wfc);
|
|
}
|
|
#else /* USE_SPAWN */
|
|
|
|
fflush(stdout);
|
|
switch( pid.pid = fork() ){
|
|
|
|
case -1: /* fork failed */
|
|
Fatal("fork failed: %s: %s", argv[0], strerror( errno ));
|
|
|
|
case 0: /* child */
|
|
/* redirect output for _exec_shell / @@-recipes. */
|
|
if( Is_exec_shell ) {
|
|
/* Add error checking? */
|
|
old_stdout = dup(1);
|
|
dup2( fileno(stdout_redir), 1 );
|
|
}
|
|
if( mute ) {
|
|
old_stderr = dup(2);
|
|
dup2( zerofd, 2 );
|
|
|
|
if( !Is_exec_shell ) {
|
|
old_stdout = dup(1);
|
|
dup2( zerofd, 1 );
|
|
}
|
|
}
|
|
execvp(argv[0], argv);
|
|
/* restoring output to catch potential error output if execvp()
|
|
* failed. */
|
|
if( old_stdout != -1 ) {
|
|
dup2(old_stdout, 1);
|
|
close(old_stdout);
|
|
if( old_stderr != -1 ) {
|
|
dup2(old_stderr, 2);
|
|
close(old_stderr);
|
|
}
|
|
}
|
|
fprintf(stderr, "%s: Error executing '%s': %s",
|
|
Pname, argv[0], strerror(errno) );
|
|
if( ignore||Continue ) {
|
|
fprintf(stderr, " (Ignored)" );
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
kill(getpid(), SIGTERM);
|
|
/*NOTREACHED*/
|
|
Fatal("\nInternal Error - kill could't kill child %d.\n", getpid());
|
|
|
|
default: /* parent */
|
|
_add_child(pid, target, ignore, last, wfc);
|
|
}
|
|
|
|
#endif /* USE_SPAWN */
|
|
|
|
/* If wfc is set this command must have been finished. */
|
|
if( wfc ) {
|
|
DB_RETURN( 0 );
|
|
} else {
|
|
DB_RETURN( 1 );
|
|
}
|
|
}
|
|
|
|
|
|
PUBLIC int
|
|
Wait_for_child( abort_flg, pqid )/*
|
|
===================================
|
|
Wait for the next processes from process queue pqid to finish. All finished
|
|
processes are handled by calling _finished_child() for each of them.
|
|
If pqid == -1 wait for the next process to finish.
|
|
If abort_flg is TRUE no further processes will be added to any process
|
|
queue. The A_WFC attribute is honored, see the documentation at the top
|
|
of this file.
|
|
Return 0 if we successfully waited for a process and -1 if there was nothing
|
|
to wait for.
|
|
*/
|
|
int abort_flg;
|
|
int pqid;
|
|
{
|
|
DMHANDLE pid;
|
|
DMHANDLE wid;
|
|
int status;
|
|
int waitret; /* return value of the dmwait functions. */
|
|
/* Never wait for internal commands. */
|
|
int waitchild;
|
|
int is_exec_shell_status = Is_exec_shell;
|
|
|
|
if( !_procs ) {
|
|
/* No process was ever created, i.e. _procs is not yet initialized.
|
|
* Nothing to wait for. */
|
|
return -1;
|
|
}
|
|
|
|
if( pqid > Max_proc ) Fatal("Internal Error: pqid > Max_proc !");
|
|
|
|
if( pqid == -1 ) {
|
|
/* Check if there is something to wait for. */
|
|
int i;
|
|
for( i=0; i<Max_proc && !_procs[i].pr_valid; i++ )
|
|
;
|
|
if( i == Max_proc )
|
|
return(-1);
|
|
|
|
pid = (DMHANDLE)-1;
|
|
waitchild = FALSE;
|
|
}
|
|
else {
|
|
/* Check if pqid is active. */
|
|
if( !_procs[pqid].pr_valid ) {
|
|
/* Make this an error? */
|
|
Warning("Internal Warning: pqid is not active!?");
|
|
return(-1);
|
|
}
|
|
|
|
pid = _procs[pqid].pr_pid;
|
|
waitchild = _procs[pqid].pr_wfc;
|
|
}
|
|
|
|
|
|
/* It is impossible that processes that were started from _exec_shell
|
|
* have follow-up commands in its process entry. Unset Is_exec_shell
|
|
* to prevent piping of child processes that are started from the
|
|
* _finished_child subroutine and reset to its original value when
|
|
* leaving this function. */
|
|
Is_exec_shell = FALSE;
|
|
|
|
do {
|
|
/* Wait for the next process to finish. */
|
|
if( (pid != (DMHANDLE)-1) && (waitret = dmwaitpid(pqid, &wid, &status)) != 0 ) {
|
|
/* if dmwaitpid returns 0 this means that pid didn't finish yet.
|
|
* In this case just handle the next finished process in the
|
|
* following "else". If an error is returned (waitret < 0) the else
|
|
* clause is not evaluated and the error is handled in the following
|
|
* lines. If a process was waited for (waitret == 0) also proceed to
|
|
* the following lines. */
|
|
;
|
|
}
|
|
else {
|
|
waitret = dmwaitnext(&wid, &status);
|
|
/* If we get an error tell the error handling routine below that we
|
|
* were not waiting for a specific pid. */
|
|
if( waitret < 0 ) {
|
|
pid = (DMHANDLE)-1;
|
|
}
|
|
}
|
|
|
|
/* If ECHILD is set from waitpid/wait then no child was left. */
|
|
if( waitret < 0 ) {
|
|
if(waitret == -2) {
|
|
/* Wait was interrupted or a child was terminated (SIGCHLD) */
|
|
if ( in_quit() ) {
|
|
/* We're already terminating, just continue. */
|
|
return 0;
|
|
} else {
|
|
Fatal( "dmake was interrupted or a child terminated. "
|
|
"Stopping all childs ..." );
|
|
}
|
|
} else {
|
|
/* The child we were waiting for is missing or no child is
|
|
* left to wait for. */
|
|
if( pid != (DMHANDLE)-1 ) {
|
|
/* If we know the pid disable the pq entry. */
|
|
if( _procs[pqid].pr_valid ) {
|
|
_procs[pqid].pr_valid = 0;
|
|
_procs[pqid].pr_recipe = NIL(RCP);
|
|
_proc_cnt--;
|
|
}
|
|
} else {
|
|
/* otherwise disable all remaining pq's. As we don't know
|
|
* which pid failed there is no gracefull way to terminate. */
|
|
int i;
|
|
for( i=0; i<Max_proc; i++ ) {
|
|
_procs[i].pr_valid = 0;
|
|
_procs[i].pr_recipe = NIL(RCP);
|
|
}
|
|
_proc_cnt = 0;
|
|
}
|
|
/* The pid we were waiting for or any of the remaining childs
|
|
* (pid == -1) is missing. This should not happen and means
|
|
* that the process got lost or was treated elsewhere. */
|
|
Fatal( "Internal Error: Child is missing but still listed in _procs[x] %d: %s\n"
|
|
"\nTemporary or .ERRREMOVE targets might not have been removed!\n",
|
|
errno, strerror( errno ) );
|
|
}
|
|
}
|
|
|
|
_abort_flg = abort_flg;
|
|
_finished_child(wid, status);
|
|
_abort_flg = FALSE;
|
|
if( waitchild ) {
|
|
/* If pid != wid the process we're waiting for might have been
|
|
* finished from a "Wait_for_child(FALSE, -1)" call from
|
|
* _finished_child() -> runargv(). */
|
|
if( pid != wid ) {
|
|
if( !_procs[pqid].pr_valid || _procs[pqid].pr_pid != pid ) {
|
|
/* Someone finished pid, no need to wait further. */
|
|
waitchild = FALSE;
|
|
}
|
|
}
|
|
else
|
|
/* We finished pid, no need to wait further. */
|
|
waitchild = FALSE;
|
|
}
|
|
}
|
|
while( waitchild );
|
|
|
|
Is_exec_shell = is_exec_shell_status;
|
|
return(0);
|
|
}
|
|
|
|
|
|
PUBLIC void
|
|
Clean_up_processes()
|
|
{
|
|
register int i;
|
|
int ret;
|
|
|
|
if( _procs != NIL(PR) ) {
|
|
for( i=0; i<Max_proc; i++ )
|
|
if( _procs[i].pr_valid ) {
|
|
#if !defined(USE_CREATEPROCESS)
|
|
if( (ret = kill(_procs[i].pr_pid, SIGTERM)) ) {
|
|
fprintf(stderr, "Killing of pid %d from pq[%d] failed with: %s - %d ret: %d\n",
|
|
_procs[i].pr_pid, i,
|
|
strerror(errno), SIGTERM, ret );
|
|
}
|
|
#else
|
|
TerminateProcess(_procs[i].pr_pid, 1);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
_add_child( pid, target, ignore, last, wfc )/*
|
|
==============================================
|
|
Creates/amend a process queue entry and enters the child parameters.
|
|
The pid == -1 represents an internal command and the function returns
|
|
the used process array index. For non-internal commands the function
|
|
returns -1.
|
|
If wfc (wait for completion) is TRUE the function calls
|
|
Wait_for_child to wait for the whole process queue to be finished.
|
|
*/
|
|
TPID pid;
|
|
CELLPTR target;
|
|
int ignore;
|
|
int last;
|
|
int wfc;
|
|
{
|
|
register int i;
|
|
register PR *pp;
|
|
|
|
/* Never change MAXPROCESS after _procs is allocated. */
|
|
if( _procs_size != Max_proc ) {
|
|
/* If procs was never initialize this is OK, do it now. */
|
|
if( _procs == NIL(PR) ) {
|
|
_procs_size = Max_proc;
|
|
TALLOC( _procs, Max_proc, PR );
|
|
#if defined(USE_CREATEPROCESS)
|
|
TALLOC( _wpList, Max_proc, HANDLE );
|
|
|
|
/* Signed int values are cast to DMHANDLE in various places, use this
|
|
* sanity check to verify that DMHANDLE is large enough. */
|
|
if( sizeof(int) > sizeof(DMHANDLE) )
|
|
Fatal( "Internal Error: Check type of DMHANDLE!" );
|
|
#endif
|
|
}
|
|
else {
|
|
Fatal( "MAXPROCESS changed from `%d' to `%d' after a command was executed!", _procs_size, Max_proc );
|
|
}
|
|
}
|
|
|
|
if( Measure & M_RECIPE )
|
|
Do_profile_output( "s", M_RECIPE, target );
|
|
|
|
/* If _use_i ! =-1 then this function is called by _finished_child() ( through runargv() ),
|
|
and we re-use the process queue number given by _use_i. */
|
|
if( (i = _use_i) == -1 ) {
|
|
for( i=0; i<Max_proc; i++ )
|
|
if( !_procs[i].pr_valid )
|
|
break;
|
|
}
|
|
|
|
pp = &(_procs[i]);
|
|
|
|
pp->pr_valid = 1;
|
|
pp->pr_pid = pid.pid;
|
|
pp->pr_tid = pid.tid;
|
|
pp->pr_target = target;
|
|
pp->pr_ignore = ignore;
|
|
pp->pr_last = last;
|
|
pp->pr_wfc = wfc;
|
|
|
|
if( pp->pr_dir != NIL(char) )
|
|
FREE(pp->pr_dir);
|
|
pp->pr_dir = DmStrDup(Get_current_dir());
|
|
|
|
Current_target = NIL(CELL);
|
|
|
|
_proc_cnt++;
|
|
|
|
if( pid.pid != (DMHANDLE)-1 ) {
|
|
/* Wait for each recipe to finish if wfc is TRUE. This
|
|
* basically forces sequential execution. */
|
|
if( wfc ) {
|
|
Wait_for_child( FALSE, i );
|
|
}
|
|
|
|
return -1;
|
|
} else
|
|
return i;
|
|
}
|
|
|
|
|
|
static void
|
|
_finished_child(cid, status)/*
|
|
==============================
|
|
Handle process array entry for finished child. This can be a finished
|
|
process or a finished internal command depending on the content of cid.
|
|
For cid >= 1 the value of cid is used as the pid to of the finished
|
|
process and for cid < 1 -cid is used as the process array index of the
|
|
internal command.
|
|
*/
|
|
DMHANDLE cid;
|
|
int status;
|
|
{
|
|
register int i;
|
|
char *dir;
|
|
|
|
if((int)cid < 1) { /* Force int. */
|
|
/* internal command */
|
|
i = -((int)cid);
|
|
}
|
|
else {
|
|
for( i=0; i<Max_proc; i++ )
|
|
if( _procs[i].pr_valid && _procs[i].pr_pid == cid )
|
|
break;
|
|
|
|
/* Some children we didn't make esp true if using /bin/sh to execute a
|
|
* a pipe and feed the output as a makefile into dmake. */
|
|
if( i == Max_proc ) {
|
|
Warning("Internal Warning: finished pid %d is not in pq!?", cid);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Not a running process anymore, the next runargv() will not use
|
|
* _attach_cmd(). */
|
|
_procs[i].pr_valid = 0;
|
|
|
|
if( Measure & M_RECIPE )
|
|
Do_profile_output( "e", M_RECIPE, _procs[i].pr_target );
|
|
|
|
_proc_cnt--;
|
|
dir = DmStrDup(Get_current_dir());
|
|
Set_dir( _procs[i].pr_dir );
|
|
|
|
if( _procs[i].pr_recipe != NIL(RCP) && !_abort_flg ) {
|
|
RCPPTR rp = _procs[i].pr_recipe;
|
|
|
|
|
|
Current_target = _procs[i].pr_target;
|
|
Handle_result( status, _procs[i].pr_ignore, FALSE, _procs[i].pr_target );
|
|
Current_target = NIL(CELL);
|
|
|
|
if ( _procs[i].pr_target->ce_attr & A_ERROR ) {
|
|
_procs[i].pr_last = TRUE;
|
|
goto ABORT_REMAINDER_OF_RECIPE;
|
|
}
|
|
|
|
_procs[i].pr_recipe = rp->prp_next;
|
|
|
|
_use_i = i;
|
|
/* Run next recipe line. The rp->prp_attr propagates a possible
|
|
* wfc condition. */
|
|
runargv( _procs[i].pr_target, rp->prp_group,
|
|
rp->prp_last, rp->prp_attr, &rp->prp_cmd );
|
|
_use_i = -1;
|
|
|
|
FREE( rp->prp_cmd );
|
|
FREE( rp );
|
|
|
|
/* If all process queues are used wait for the next process to
|
|
* finish. Is this really needed here? */
|
|
if( _proc_cnt == Max_proc ) {
|
|
Wait_for_child( FALSE, -1 );
|
|
}
|
|
}
|
|
else {
|
|
/* empty the queue on abort. */
|
|
if( _abort_flg )
|
|
_procs[i].pr_recipe = NIL(RCP);
|
|
|
|
Handle_result(status,_procs[i].pr_ignore,_abort_flg,_procs[i].pr_target);
|
|
|
|
ABORT_REMAINDER_OF_RECIPE:
|
|
if( _procs[i].pr_last ) {
|
|
FREE(_procs[i].pr_dir ); _procs[i].pr_dir = NIL(char); /* Set in _add_child() */
|
|
|
|
if( !Doing_bang ) {
|
|
/* Update_time_stamp() triggers the deletion of intermediate
|
|
* targets. This starts a new process queue, so we have to
|
|
* clear the _use_i variable. */
|
|
int my_use_i = _use_i;
|
|
|
|
_use_i = -1;
|
|
Update_time_stamp( _procs[i].pr_target );
|
|
_use_i = my_use_i;
|
|
}
|
|
}
|
|
}
|
|
|
|
Set_dir(dir);
|
|
FREE(dir);
|
|
}
|
|
|
|
|
|
static int
|
|
_running( cp )/*
|
|
================
|
|
Check if target exists in process array AND is running. Return its
|
|
process array index if it is running, return -1 otherwise.
|
|
*/
|
|
CELLPTR cp;
|
|
{
|
|
register int i;
|
|
|
|
if( !_procs ) return( -1 );
|
|
|
|
for( i=0; i<Max_proc; i++ )
|
|
if( _procs[i].pr_valid &&
|
|
_procs[i].pr_target == cp )
|
|
break;
|
|
|
|
return( i == Max_proc ? -1 : i );
|
|
}
|
|
|
|
|
|
static void
|
|
_attach_cmd( cmd, group, cp, cmnd_attr, last )/*
|
|
================================================
|
|
Attach to an active process queue. Inherit wfc setting. */
|
|
char *cmd;
|
|
int group;
|
|
CELLPTR cp;
|
|
t_attr cmnd_attr;
|
|
int last;
|
|
{
|
|
register int i;
|
|
RCPPTR rp;
|
|
|
|
for( i=0; i<Max_proc; i++ )
|
|
if( _procs[i].pr_valid &&
|
|
_procs[i].pr_target == cp )
|
|
break;
|
|
|
|
TALLOC( rp, 1, RCP );
|
|
rp->prp_cmd = DmStrDup(cmd);
|
|
rp->prp_attr = cmnd_attr;
|
|
/* Inherit wfc from process queue. */
|
|
if( _procs[i].pr_wfc )
|
|
rp->prp_attr |= A_WFC;
|
|
rp->prp_group = group;
|
|
rp->prp_last = last;
|
|
|
|
if( _procs[i].pr_recipe == NIL(RCP) )
|
|
_procs[i].pr_recipe = _procs[i].pr_recipe_end = rp;
|
|
else {
|
|
_procs[i].pr_recipe_end->prp_next = rp;
|
|
_procs[i].pr_recipe_end = rp;
|
|
}
|
|
}
|