summaryrefslogtreecommitdiff
path: root/dmake/unix/runargv.c
diff options
context:
space:
mode:
Diffstat (limited to 'dmake/unix/runargv.c')
-rw-r--r--dmake/unix/runargv.c1101
1 files changed, 1101 insertions, 0 deletions
diff --git a/dmake/unix/runargv.c b/dmake/unix/runargv.c
new file mode 100644
index 000000000000..4be342bbb273
--- /dev/null
+++ b/dmake/unix/runargv.c
@@ -0,0 +1,1101 @@
+/* $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 ) {
+ fprintf(stderr, "%s: Internal Error: wait() failed: %d - %s\n",
+ Pname, errno, strerror(errno) );
+ if(errno != 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 ) {
+ fprintf(stderr, "%s: Internal Error: waitpid() failed: %d - %s\n",
+ Pname, errno, strerror(errno) );
+ if(errno != 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(__OpenBSD__)
+ 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() ). */
+ if( (i = _use_i) == -1 ) {
+ for( i=0; i<Max_proc; i++ )
+ if( !_procs[i].pr_valid )
+ break;
+ }
+ else {
+ /* Re-use the process queue number given by _use_i.
+ * Free the pointer before using it again below. */
+ FREE( _procs[i].pr_dir );
+ }
+
+ 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;
+ /* Freed above and after the last recipe in _finished child(). */
+ 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 ); /* 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;
+ }
+}