Category : C Source Code
Archive   : OHLMAKE.ZIP
Filename : JOB.C

 
Output of file : JOB.C contained in archive : OHLMAKE.ZIP
/* Job execution and handling for GNU Make.
Copyright (C) 1988, 1989, 1990 Free Software Foundation, Inc.
This file is part of GNU Make.

GNU Make is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

GNU Make is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Make; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

/*
* MS-DOS port (c) 1990 by Thorsten Ohl
*
* To this port, the same copying conditions apply as to the
* original release.
*
* IMPORTANT:
* This file is not identical to the original GNU release!
* You should have received this code as patch to the official
* GNU release.
*
* MORE IMPORTANT:
* This port comes with ABSOLUTELY NO WARRANTY.
*
* $Header: e:/gnu/make/RCS/job.c'v 3.58.0.6 90/07/22 14:31:11 tho Exp $
*/

#include "make.h"
#include "commands.h"
#include "job.h"
#include "file.h"
#include "variable.h"
#include

#ifdef MSDOS
#define PATHSEP ';'
#else
#define PATHSEP ':'
#endif

#ifndef MSDOS
extern int errno;
#endif /* not MSDOS */

#ifdef MSDOS

#include
#include
#include

int last_child_pid = 0; /* no child started yet ... */
int last_child_status = 0;

struct stringlist
{
char **list; /* Nil-terminated list of strings. */
unsigned int idx; /* Index into above. */
unsigned int max; /* Number of pointers allocated. */
};

extern struct stringlist *swapping_device;

extern int swap_and_spawnvpe (char *device, char *cmd, char **argv,
char **envvec);

#endif /* MSDOS */

#if defined(USG) && !defined(HAVE_VFORK)
#define vfork fork
#define VFORK_NAME "fork"
#else /* Have vfork or not USG. */
#define VFORK_NAME "vfork"
#endif /* USG and don't have vfork. */
extern int vfork ();

#if defined(HAVE_SYS_WAIT) || !defined(USG)
#include
#include
#include
#include
extern int wait3 ();
#endif

#if defined(WTERMSIG) || (defined(USG) && !defined(HAVE_SYS_WAIT))
#define WAIT_T int

#ifndef WTERMSIG
#define WTERMSIG(x) ((x) & 0x7f)
#endif
#ifndef WCOREDUMP
#define WCOREDUMP(x) ((x) & 0x80)
#endif
#ifndef WEXITSTATUS
#define WEXITSTATUS(x) (((x) >> 8) & 0xff)
#endif
#ifndef WIFSIGNALED
#define WIFSIGNALED(x) (WTERMSIG (x) != 0)
#endif
#ifndef WIFEXITED
#define WIFEXITED(x) (WTERMSIG (x) == 0)
#endif

#else /* WTERMSIG not defined and have or not USG. */

#define WAIT_T union wait
#define WTERMSIG(x) ((x).w_termsig)
#define WCOREDUMP(x) ((x).w_coredump)
#define WEXITSTATUS(x) ((x).w_retcode)
#ifndef WIFSIGNALED
#define WIFSIGNALED(x) (WTERMSIG(x) != 0)
#endif

#endif /* WTERMSIG defined or USG and don't have . */

#ifdef MSDOS
static char *search_path (char *file, char *path);
static void child_error (char *target_name, int exit_code, int exit_sig, int coredump, int ignored);
#endif /* MSDOS */

extern int dup2 ();
extern int fork (), wait (), execve ();
extern void _exit ();
extern int geteuid (), getegid ();

#if !defined(USG) && !defined(sigmask)
#define sigmask(sig) (1 << ((sig) - 1))
#endif

#ifdef MSDOS
#define getdtablesize() _NFILE
#else /* not MSDOS */
#ifndef USG
extern int getdtablesize ();
#else
#include
#define getdtablesize() NOFILE
#endif
#endif /* not MSDOS */

extern void wait_to_start_job ();
extern int start_remote_job_p ();
extern int start_remote_job (), remote_status ();


#if defined(USG) && !defined(HAVE_SIGLIST)
static char *sys_siglist[NSIG];
void init_siglist ();
#else /* Not USG and or HAVE_SIGLIST. */
extern char *sys_siglist[];
#endif /* USG and not HAVE_SIGLIST. */

#ifdef MSDOS
extern int child_handler (int sig);
static void start_job (struct child *child);
static void free_child (struct child *child);
#else /* not MSDOS */
int child_handler ();
static void free_child (), start_job ();
#endif /* not MSDOS */

/* Chain of all children. */

struct child *children = 0;

/* Number of children currently running. */

unsigned int job_slots_used = 0;

/* Nonzero if the `good' standard input is in use. */

static int good_stdin_used = 0;

/* Write an error message describing the exit status given in
EXIT_CODE, EXIT_SIG, and COREDUMP, for the target TARGET_NAME.
Append "(ignored)" if IGNORED is nonzero. */

static void
child_error (target_name, exit_code, exit_sig, coredump, ignored)
char *target_name;
int exit_code, exit_sig, coredump;
int ignored;
{
char *ignore_string = ignored ? " (ignored)" : "";

if (exit_sig == 0)
error ("*** [%s] Error %d%s", target_name, exit_code, ignore_string);
else
{
char *coredump_string = coredump ? " (core dumped)" : "";
if (exit_sig > 0 && exit_sig < NSIG)
error ("*** [%s] %s%s",
target_name, sys_siglist[exit_sig], coredump_string);
else
error ("*** [%s] Signal %d%s", target_name, exit_sig, coredump_string);
}
}

extern void block_remote_children (), unblock_remote_children ();

/* Block the child termination signal. */

void
block_children ()
{
#ifndef MSDOS
#ifdef USG
/* Ignoring SIGCLD makes wait always return -1.
Using the default action does the right thing. */
(void) signal (SIGCLD, SIG_DFL);
#else
(void) sigblock (sigmask (SIGCHLD));
#endif
#endif /* ! MSDOS */

block_remote_children ();
}

/* Unblock the child termination signal. */
void
unblock_children ()
{
#ifndef MSDOS
#ifdef USG
(void) signal (SIGCLD, child_handler);
#else
(void) sigsetmask (sigblock (0) & ~sigmask (SIGCHLD));
#endif
#endif /* ! MSDOS */

unblock_remote_children ();
}

extern int shell_function_pid, shell_function_completed;

/* Handle a child-termination signal (SIGCHLD, or SIGCLD for USG),
storing the returned status and the new command state (`cs_finished')
in the `file' member of the `struct child' for the dead child,
and removing the child from the chain.

If we were called as a signal handler, SIG should be SIGCHLD
(SIGCLD for USG). If instead it is zero, we were called explicitly
and should block waiting for running children.
If SIG is < 0, - SIG is the maximum number of children to bury (record
status of and remove from the chain). */

int
child_handler (sig)
int sig;
{
WAIT_T status;
unsigned int dead_children = 0;

if (sig > 0)
block_remote_children ();

while (1)
{
int remote = 0;
register int pid;
int exit_code, exit_sig, coredump;
register struct child *lastc, *c;
int child_failed;

/* First, check for remote children. */
pid = remote_status (&exit_code, &exit_sig, &coredump, 0);
if (pid < 0)
{
/* No remote children. Check for local children. */

#if !defined(USG) || defined(HAVE_SYS_WAIT)
if (sig > 0)
pid = wait3 (&status, WNOHANG, (struct rusage *) 0);
else
pid = wait (&status);
#else /* USG and don't HAVE_SYS_WAIT. */
/* System V cannot do non-blocking waits, so we have two
choices if called as a signal handler: handle only one
child (there may be more if the signal was blocked),
or block waiting for more. The latter option makes
parallelism useless, so we must choose the former. */
pid = wait (&status);
#endif /* HAVE_SYS_WAIT or not USG. */

if (pid <= 0)
/* No local children. */
break;
else
{
/* Chop the status word up. */
exit_code = WEXITSTATUS (status);
exit_sig = WIFSIGNALED (status) ? WTERMSIG (status) : 0;
coredump = WCOREDUMP (status);
}
}
else
/* We got a remote child. */
remote = 1;

/* Check if this is the child of the `shell' function. */
if (!remote && pid == shell_function_pid)
{
/* It is. Leave an indicator for the `shell' function. */
if (exit_sig == 0 && exit_code == 127)
shell_function_completed = -1;
else
shell_function_completed = 1;

/* Check if we have reached our quota of children. */
++dead_children;
if (sig < 0 && dead_children == -sig)
break;
#if defined(USG) && !defined(HAVE_SYS_WAIT)
else if (sig > 0)
break;
#endif
else
continue;
}

child_failed = exit_sig != 0 || exit_code != 0;

/* Search for a child matching the deceased one. */
lastc = 0;
for (c = children; c != 0; lastc = c, c = c->next)
if (c->remote == remote && c->pid == pid)
break;

if (c == 0)
{
/* An unknown child died. */
char buf[100];
sprintf (buf, "Unknown%s job %d", remote ? " remote" : "", pid);
if (child_failed)
child_error (buf, exit_code, exit_sig, coredump,
ignore_errors_flag);
else
error ("%s finished.", buf);
}
else
{
/* If this child had the good stdin, say it is now free. */
if (c->good_stdin)
good_stdin_used = 0;

if (child_failed && !c->noerror && !ignore_errors_flag)
{
/* The commands failed. Write an error message,
delete non-precious targets, and abort. */
child_error (c->file->name, exit_code, exit_sig, coredump, 0);
c->file->update_status = 1;
if (exit_sig != 0)
delete_child_targets (c);
}
else
{
if (child_failed)
{
/* The commands failed, but we don't care. */
child_error (c->file->name,
exit_code, exit_sig, coredump, 1);
child_failed = 0;
}

/* If there are more commands to run, try to start them. */
start_job (c);
switch (c->file->command_state)
{
case cs_running:
/* Successfully started. Loop to reap more children. */
continue;

case cs_finished:
if (c->file->update_status != 0)
{
/* We failed to start the commands. */
delete_child_targets (c);
}
break;

default:
error ("internal error: `%s' command_state \
%d in child_handler", c->file->name);
abort ();
break;
}
}

/* Set the state flag to say the commands have finished. */
c->file->command_state = cs_finished;
notice_finished_file (c->file);

/* Remove the child from the chain and free it. */
if (lastc == 0)
children = c->next;
else
lastc->next = c->next;
free_child (c);

/* There is now another slot open. */
--job_slots_used;

/* If the job failed, and the -k flag was not given, die. */
if (child_failed && !keep_going_flag)
die (1);

/* See if we have reached our quota for blocking. */
++dead_children;
if (sig < 0 && dead_children == -sig)
break;
#if defined(USG) && !defined(HAVE_SYS_WAIT)
else if (sig > 0)
break;
#endif
}
}

#ifdef USG
if (sig > 0)
(void) signal (sig, child_handler);
#endif

if (sig > 0)
unblock_remote_children ();

return 0;
}


/* Wait for N children, blocking if necessary.
If N is zero, wait until we run out of children.
If ERR is nonzero and we have any children to wait for,
print a message on stderr. */

void
wait_for_children (n, err)
unsigned int n;
int err;
{
block_children ();

#ifndef MSDOS
if (err && (children != 0 || shell_function_pid != 0))
{
fflush (stdout);
error ("*** Waiting for unfinished jobs....");
}
#endif /* not MSDOS */

/* Call child_handler to do the work. */
(void) child_handler (- (int) n);

unblock_children ();
}

/* Free the storage allocated for CHILD. */

static void
free_child (child)
register struct child *child;
{
if (child->commands != 0)
free (child->commands);
free ((char *) child);
}

/* Start a job to run the commands specified in CHILD.
CHILD is updated to reflect the commands and ID of the child process. */

static void
start_job (child)
register struct child *child;
{
static int bad_stdin = -1;
char *end;
register char *p;
int backslash;
char noprint = 0, recursive;
char **argv;

if (child->command_ptr == 0 || *child->command_ptr == '\0')
/* There are no more lines in the expansion of this line. */
if (child->file->cmds->command_lines[child->command_line] == 0)
{
/* There are no more lines to be expanded. */
child->command_ptr = 0;
child->file->command_state = cs_finished;
child->file->update_status = 0;
return;
}
else
{
/* Expand and run the next line. */
child->command_ptr = child->commands
#ifdef MSDOS
= allocated_var_exp_for_file
#else
= allocated_variable_expand_for_file
#endif /* MSDOS */
(child->file->cmds->command_lines[child->command_line++],
child->file);
}

/* Set RECURSIVE if the unexpanded line contains $(MAKE). */
recursive = child->file->cmds->lines_recurse[child->command_line - 1];

/* Find the end of this line. Backslash-newlines don't mean the end. */

end = child->command_ptr;
while (*end != '\0')
{
p = index (end, '\n');
if (p == 0)
{
end += strlen (end);
break;
}

end = p;
backslash = 0;
while (*--p == '\\')
backslash = !backslash;

if (backslash)
{
++end;
/* If there is a tab after a backslash-newline,
remove it, since it was most likely used to line
up the continued line with the previous one. */
if (*end == '\t')
strcpy (end, end + 1);
}
else
break;
}

p = child->command_ptr;

if (*end == '\0')
child->command_ptr = 0;
else
{
*end = '\0';
child->command_ptr = end + 1;
}

child->noerror = 0;
while (*p != '\0')
{
if (*p == '@')
noprint = 1;
else if (*p == '-')
child->noerror = 1;
else if (*p == '+')
recursive = 1;
else if (*p != ' ' && *p != '\t')
break;
++p;
}

/* If -q was given, just say that updating `failed'. */
if (question_flag && !recursive)
goto error;

/* There may be some preceding whitespace left if there
was nothing but a backslash on the first line. */
p = next_token (p);

if (*p == '\0')
/* There were no commands on this line. Go to the next. */
{
start_job (child);
return;
}

/* Print out the command. */

if (just_print_flag || (!noprint && !silent_flag))
puts (p);

/* If -n was given, recurse to get the next line in the sequence. */

if (just_print_flag && !recursive)
{
start_job (child);
return;
}

/* Collapse backslash-newlines in this line. */

collapse_continuations (p);

/* Figure out an argument list from this command line. */

argv = construct_command_argv (p, child->file);

/* Flush the output streams so they won't have things written twice. */

fflush (stdout);
fflush (stderr);

/* Set up a bad standard input that reads from a broken pipe. */

#ifndef MSDOS
if (bad_stdin == -1)
{
/* Make a file descriptor that is the read end of a broken pipe.
This will be used for some children's standard inputs. */
int pd[2];
if (pipe (pd) == 0)
{
/* Close the write side. */
(void) close (pd[1]);
/* Save the read side. */
bad_stdin = pd[0];
}
}
#endif /* ! MSDOS */

/* Decide whether to give this child the `good' standard input
(one that points to the terminal or whatever), or the `bad' one
that points to the read side of a broken pipe. */

child->good_stdin = !good_stdin_used;
if (child->good_stdin)
good_stdin_used = 1;

child->deleted = 0;

/* Set up the environment for the child. */
if (child->environment == 0)
child->environment = target_environment (child->file);

#ifdef MSDOS

child->remote = 0;
child->pid = abs ((int) clock()); /* reasonably random */

#if (0)
{
int ac = 0;
char **av = argv;
printf ( "start_job(): spawning process");
while (*av)
printf( " $%d=`%s'", ac++, *av++);
printf( "\n");
}
#endif /* NEVER */

/* LAST_CHILD_STATUS and LAST_CHILD_PID will be used by our
(faked) wait for MS-DOS to bury this 'child' */

last_child_status
= swap_and_spawnvpe (swapping_device ? swapping_device->list[0] : NULL,
argv[0], argv, child->environment);

last_child_pid = child->pid;

if (last_child_status == -1)
pfatal_with_name ("vm_spawnvp");

child->file->command_state = cs_running; /* in *some* sense ... */

#else /* not MSDOS */

if (start_remote_job_p ())
{
int is_remote, id, used_stdin;
if (start_remote_job (argv, child->good_stdin ? 0 : bad_stdin,
&is_remote, &id, &used_stdin))
goto error;
else
{
if (child->good_stdin && !used_stdin)
{
child->good_stdin = 0;
good_stdin_used = 0;
}
child->remote = is_remote;
child->pid = id;
}
}
else
{
if (child->command_line - 1 == 0)
/* Wait for the load to be low enough if this
is the first command in the sequence. */
wait_to_start_job ();

/* Fork the child process. */

child->remote = 0;
child->pid = vfork ();
if (child->pid == 0)
/* We are the child side. */
child_execute_job (child->good_stdin ? 0 : bad_stdin, 1,
child->file, argv, child->environment);
else if (child->pid < 0)
{
/* Fork failed! */
perror_with_name (VFORK_NAME, "");
goto error;
}
}

/* We are the parent side. Set the state to
say the commands are running and return. */

child->file->command_state = cs_running;

#endif /* not MSDOS */

return;

error:;
child->file->update_status = 1;
child->file->command_state = cs_finished;
}


/* Create a `struct child' for FILE and start its commands running. */

void
new_job (file)
register struct file *file;
{
extern unsigned int files_remade;
register struct commands *cmds = file->cmds;
register struct child *c;

/* Chop the commands up into lines if they aren't already. */
chop_commands (cmds);

if (job_slots > 0)
/* Wait for a job slot to be freed up. */
while (job_slots_used == job_slots)
wait_for_children (1, 0);

/* Start the command sequence, record it in a new
`struct child', and add that to the chain. */

block_children ();

c = (struct child *) xmalloc (sizeof (struct child));
c->file = file;
c->command_line = 0;
c->command_ptr = c->commands = 0;
c->environment = 0;
start_job (c);
switch (file->command_state)
{
case cs_running:
c->next = children;
children = c;
/* One more job slot is in use. */
++job_slots_used;
break;

case cs_finished:
free_child (c);
notice_finished_file (file);
break;

default:
error ("internal error: `%s' command_state == %d in new_job",
file->name, (int) file->command_state);
abort ();
break;
}

unblock_children ();

++files_remade;

if (job_slots == 1 && file->command_state == cs_running)
{
/* Since there is only one job slot, make things run linearly.
Wait for the child to finish, setting the state to `cs_finished'. */
while (file->command_state != cs_finished)
wait_for_children (1, 0);
}
}

#ifndef MSDOS
/* Replace the current process with one executing the command in ARGV.
STDIN_FD and STDOUT_FD are used as the process's stdin and stdout;
FILE, if not nil, is used as the variable-set to expand `PATH' and `SHELL';
ENVP is the environment of the new program. This function won't return. */

void
child_execute_job (stdin_fd, stdout_fd, file, argv, envp)
int stdin_fd, stdout_fd;
struct file *file;
char **argv, **envp;
{
if (stdin_fd != 0)
(void) dup2 (stdin_fd, 0);
if (stdout_fd != 1)
(void) dup2 (stdout_fd, 1);

/* Free up file descriptors. */
{
register int d;
int max = getdtablesize ();
for (d = 3; d < max; ++d)
(void) close (d);
}

/* Don't block children for our child. */
unblock_children ();

/* Run the command. */
exec_command (argv, envp,
allocated_variable_expand_for_file ("$(PATH)", file),
allocated_variable_expand_for_file ("$(SHELL)", file));
}
#endif /* not MSDOS */

/* Search PATH for FILE, returning a full pathname or nil.
The returned pathname is allocated via `malloc'. */

static char *
search_path (file, path)
char *file, *path;
{
if (*path == '\0' || index (file, '/') != 0)
return file;
else
{
char *program;
unsigned int len;

#ifndef USG
extern int getgroups ();
static int groups[NGROUPS];
static int ngroups = -1;
if (ngroups == -1)
ngroups = getgroups (NGROUPS, groups);
#endif /* Not USG. */

len = strlen (file) + 1;
program = (char *) xmalloc (strlen (path) + 1 + len);
do
{
struct stat st;
int perm;
char *p;

p = index (path, PATHSEP);
if (p == 0)
p = path + strlen (path);

if (p == path)
bcopy (file, program, len);
else
{
bcopy (path, program, p - path);
program[p - path] = '/';
bcopy (file, program + (p - path) + 1, len);
}

if (stat (program, &st) == 0
&& (st.st_mode & S_IFMT) == S_IFREG)
{
#ifdef MSDOS /* we always have permission */
return program;
#else /* not MSDOS */
if (st.st_uid == geteuid ())
perm = (st.st_mode & 0100);
else if (st.st_gid == getegid ())
perm = (st.st_mode & 0010);
else
{
#ifndef USG
register int i;
for (i = 0; i < ngroups; ++i)
if (groups[i] == st.st_gid)
break;
if (i < ngroups)
perm = (st.st_mode & 0010);
else
#endif /* Not USG. */
perm = (st.st_mode & 0001);
}

if (perm != 0)
return program;
#endif /* not MSDOS */
}

path = p + 1;
} while (*path != '\0');
}

return 0;
}

#ifndef MSDOS
/* Replace the current process with one running the command in ARGV,
with environment ENVP. The program named in ARGV[0] is searched
for in PATH. SHELL is the shell program to run for shell scripts.
This function does not return. */

void
exec_command (argv, envp, path, shell)
char **argv, **envp;
char *path, *shell;
{
char *program;

program = search_path (argv[0], path);
if (program == 0)
error ("%s: Command not found", argv[0]);
else
{
/* Run the program. */
execve (program, argv, envp);

if (errno == ENOEXEC)
{
char *shell_path = search_path (shell, path);
if (shell_path == 0)
error ("%s: Shell program not found", shell);
else
{
char **new_argv;
int argc;

argc = 1;
while (argv[argc] != 0)
++argc;

new_argv = (char **) alloca ((1 + argc + 1) * sizeof (char *));
new_argv[0] = shell_path;
new_argv[1] = program;
while (argc > 0)
{
new_argv[1 + argc] = argv[argc];
--argc;
}

execve (shell_path, new_argv, envp);
perror_with_name ("execve: ", shell_path);
}
}
else
perror_with_name ("execve: ", program);
}

_exit (127);
}
#endif /* not MSDOS */

/* Figure out the argument list necessary to run LINE as a command.
Try to avoid using a shell. This routine handles only ' quoting.
Starting quotes may be escaped with a backslash. If any of the
characters in sh_chars[] is seen, or any of the builtin commands
listed in sh_cmds[] is the first word of a line, the shell is used.

FILE is the target whose commands these are. It is used for
variable expansion for $(SHELL) and $(IFS). */

char **
construct_command_argv (line, file)
char *line;
struct file *file;
{
register int i;
register char *p;
register char *ap;
char *end;
int instring;
char **new_argv = 0;

#ifdef MSDOS

char *sh_chars;
char **sh_cmds;
char *base;
char *shell_command;
char switch_char = '/';

static char _sh_chars[]
= "#;\"*?[]&|<>(){}=$`";
static char *_sh_cmds[]
= { "cd", "echo", "eval", "exec", "exit", "login", "logout", "set",
"umask", "wait", "while", "for", "case", "if", ":", ".", "break",
"continue", "export", "read", "readonly", "shift", "times",
"trap", "switch", NULL };

static char _4dos_chars[]
= "<|>^&%";
static char *_4dos_cmds[]
= { "alias", "attrib", "beep", "cd", "cdd", "chdir", "copy", "del",
"dir", "echo", "erase", "except", "for", "global", "if", "iff",
"keystack", "md", "move", "popd", "pushd", "rd", "ren", "rename",
"set", "timer", "unalias", "unset", NULL };

static char _command_com_chars[]
= "<|>%";
static char *_command_com_cmds[]
= { "attrib", "cd", "chdir", "copy", "del", "dir", "echo", "erase",
"for", "if", "md", "quit", "rd", "ren", "rename", "set", "unset",
NULL };


/* See if it is safe to parse commands internally. */

/* $(SHELL) is always defined (see variable.c) */
p = base = shell_command
= strlwr (allocated_var_exp_for_file ("$(SHELL)", file));

while (*p)
{
if ((*p == '/' || *p == '\\' || *p == ':') && p[1])
base = p + 1;
p++;
}

if (!strncmp ("sh", base, 2))
{
sh_chars = _sh_chars;
sh_cmds = _sh_cmds;
switch_char = '/'; /* this is true for by Bourne shell */
}
else if (!strncmp ("4dos", base, 4))
{
sh_chars = _4dos_chars;
sh_cmds = _4dos_cmds;
}
else if (!strncmp ("command", base, 7))
{
sh_chars = _command_com_chars;
sh_cmds = _command_com_cmds;
}
else
{
free (shell_command);
goto slow;
}

#else /* not MSDOS */

static char sh_chars[] = "#;\"*?[]&|<>(){}=$`";
static char *sh_cmds[] = { "cd", "eval", "exec", "exit", "login",
"logout", "set", "umask", "wait", "while", "for",
"case", "if", ":", ".", "break", "continue",
"export", "read", "readonly", "shift", "times",
"trap", "switch", 0 };

/* See if it is safe to parse commands internally. */
p = variable_expand_for_file ("$(SHELL)", file);
if (strcmp (p, "/bin/sh"))
goto slow;

#endif /* not MSDOS */

p = variable_expand_for_file ("$(IFS)", file);
for (ap = p; *ap != '\0'; ++ap)
if (*ap != ' ' && *ap != '\t' && *ap != '\n')
goto slow;

i = strlen (line) + 1;

/* More than 1 arg per character is impossible. */
new_argv = (char **) xmalloc (i * sizeof (char *));

/* All the args can fit in a buffer as big as LINE is. */
ap = new_argv[0] = (char *) xmalloc (i);
end = ap + i;

/* I is how many complete arguments have been found. */
i = 0;
instring = 0;
for (p = line; *p != '\0'; ++p)
{
if (ap > end)
abort ();

if (instring)
{
/* Inside a string, just copy any char except a closing quote. */
if (*p == '\'')
instring = 0;
else
*ap++ = *p;
}
else if (index (sh_chars, *p) != 0)
/* Not inside a string, but it's a special char. */
goto slow;
else
/* Not a special char. */
switch (*p)
{
case '\\':
if (p[1] != '\0' && p[1] != '\n')
/* Copy and skip the following char. */
*ap++ = *++p;
break;

case '\'':
instring = 1;
break;

case '\n':
case ' ':
case '\t':
/* We have the end of an argument.
Terminate the text of the argument. */
*ap++ = '\0';
new_argv[++i] = ap;
/* If this argument is the command name,
see if it is a built-in shell command.
If so, have the shell handle it. */
if (i == 1)
{
register int j;
for (j = 0; sh_cmds[j] != 0; ++j)
if (streq (sh_cmds[j], new_argv[0]))
goto slow;
}

/* Ignore multiple whitespace chars. */
p = next_token (p);
/* Next iteration should examine the first nonwhite char. */
--p;
break;

default:
*ap++ = *p;
break;
}
}

/* Terminate the last argument and the argument list. */

*ap = '\0';
if (new_argv[i][0] != '\0')
++i;
new_argv[i] = 0;

if (new_argv[0] == 0)
/* Line was empty. */
return 0;
else
return new_argv;

slow:;
if (new_argv != 0)
free (new_argv);
new_argv = (char **) xmalloc (4 * sizeof (char *));
#ifdef MSDOS
new_argv[0] = allocated_var_exp_for_file ("$(SHELL)", file);
new_argv[1] = (switch_char == '-') ? "-c" : "/c";
#else /* not MSDOS */
new_argv[0] = allocated_variable_expand_for_file ("$(SHELL)", file);
new_argv[1] = "-c";
#endif /* not MSDOS */
new_argv[2] = line;
new_argv[3] = 0;

return new_argv;
}

#if defined(USG) && !defined(HAVE_SIGLIST)
/* Initialize sys_siglist. */

void
init_siglist ()
{
char buf[100];
register unsigned int i;

for (i = 0; i < NSIG; ++i)
switch (i)
{
default:
sprintf (buf, "Signal %u", i);
sys_siglist[i] = savestring (buf, strlen (buf));
break;
#ifndef MSDOS
case SIGHUP:
sys_siglist[i] = "Hangup";
break;
#endif /* ! MSDOS */
case SIGINT:
sys_siglist[i] = "Interrupt";
break;
#ifndef MSDOS
case SIGQUIT:
sys_siglist[i] = "Quit";
break;
case SIGILL:
sys_siglist[i] = "Illegal Instruction";
break;
case SIGTRAP:
sys_siglist[i] = "Trace Trap";
break;
case SIGIOT:
sys_siglist[i] = "IOT Trap";
break;
#endif /* ! MSDOS */
#ifdef SIGEMT
case SIGEMT:
sys_siglist[i] = "EMT Trap";
break;
#endif
#ifdef SIGDANGER
case SIGDANGER:
sys_siglist[i] = "Danger signal";
break;
#endif
case SIGFPE:
sys_siglist[i] = "Floating Point Exception";
break;
#ifndef MSDOS
case SIGKILL:
sys_siglist[i] = "Killed";
break;
case SIGBUS:
sys_siglist[i] = "Bus Error";
break;
case SIGSEGV:
sys_siglist[i] = "Segmentation fault";
break;
case SIGSYS:
sys_siglist[i] = "Bad Argument to System Call";
break;
case SIGPIPE:
sys_siglist[i] = "Broken Pipe";
break;
case SIGALRM:
sys_siglist[i] = "Alarm Clock";
break;
case SIGTERM:
sys_siglist[i] = "Terminated";
break;
case SIGUSR1:
sys_siglist[i] = "User-defined signal 1";
break;
case SIGUSR2:
sys_siglist[i] = "User-defined signal 2";
break;
#endif /* ! MSDOS */
#ifdef SIGCLD
case SIGCLD:
#endif
#if defined(SIGCHLD) && !defined(SIGCLD)
case SIGCHLD:
#endif
sys_siglist[i] = "Child Process Exited";
break;
#ifdef SIGPWR
case SIGPWR:
sys_siglist[i] = "Power Failure";
break;
#endif
#ifdef SIGVTALRM
case SIGVTALRM:
sys_siglist[i] = "Virtual Timer Alarm";
break;
#endif
#ifdef SIGPROF
case SIGPROF:
sys_siglist[i] = "Profiling Alarm Clock";
break;
#endif
#ifdef SIGIO
case SIGIO:
sys_siglist[i] = "I/O Possible";
break;
#endif
#ifdef SIGWINDOW
case SIGWINDOW:
sys_siglist[i] = "Window System Signal";
break;
#endif
#ifdef SIGSTOP
case SIGSTOP:
sys_siglist[i] = "Stopped (signal)";
break;
#endif
#ifdef SIGTSTP
case SIGTSTP:
sys_siglist[i] = "Stopped";
break;
#endif
#ifdef SIGCONT
case SIGCONT:
sys_siglist[i] = "Continued";
break;
#endif
#ifdef SIGTTIN
case SIGTTIN:
sys_siglist[i] = "Stopped (tty input)";
break;
#endif
#ifdef SIGTTOU
case SIGTTOU:
sys_siglist[i] = "Stopped (tty output)";
break;
#endif
#ifdef SIGURG
case SIGURG:
sys_siglist[i] = "Urgent Condition on Socket";
break;
#endif
#ifdef SIGXCPU
case SIGXCPU:
sys_siglist[i] = "CPU Limit Exceeded";
break;
#endif
#ifdef SIGXFSZ
case SIGXFSZ:
sys_siglist[i] = "File Size Limit Exceeded";
break;
#endif
}
}
#endif /* USG and not HAVE_SIGLIST. */

#if defined(USG) && !defined(USGr3) && !defined(HAVE_DUP2) && !defined (MSDOS)
int
dup2 (old, new)
int old, new;
{
int fd;

(void) close (new);
fd = dup (old);
if (fd != new)
{
(void) close (fd);
errno = EMFILE;
return -1;
}

return fd;
}
#endif /* USG and not USGr3 and not HAVE_DUP2 and not MSDOS. */

#ifdef MSDOS

/* fake a wait ... */

int
wait (WAIT_T *status)
{
int temp = last_child_pid; /* the last child we spawned */
*status = last_child_status << 8; /* it's return code (not a signal) */
last_child_pid = 0; /* the only child ... */
return temp;
}

#endif /* MSDOS */

/* Job execution and handling for GNU Make.
Copyright (C) 1988, 1989, 1990 Free Software Foundation, Inc.
This file is part of GNU Make.

GNU Make is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

GNU Make is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Make; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "make.h"
#include "commands.h"
#include "job.h"
#include "file.h"
#include "variable.h"
#include

extern int errno;

#if defined(USG) && !defined(HAVE_VFORK)
#define vfork fork
#define VFORK_NAME "fork"
#else /* Have vfork or not USG. */
#define VFORK_NAME "vfork"
#endif /* USG and don't have vfork. */
extern int vfork ();

#if defined(HAVE_SYS_WAIT) || !defined(USG)
#include
#include
#include
#include
extern int wait3 ();
#endif

#if defined(WTERMSIG) || (defined(USG) && !defined(HAVE_SYS_WAIT))
#define WAIT_T int

#ifndef WTERMSIG
#define WTERMSIG(x) ((x) & 0x7f)
#endif
#ifndef WCOREDUMP
#define WCOREDUMP(x) ((x) & 0x80)
#endif
#ifndef WEXITSTATUS
#define WEXITSTATUS(x) (((x) >> 8) & 0xff)
#endif
#ifndef WIFSIGNALED
#define WIFSIGNALED(x) (WTERMSIG (x) != 0)
#endif
#ifndef WIFEXITED
#define WIFEXITED(x) (WTERMSIG (x) == 0)
#endif

#else /* WTERMSIG not defined and have or not USG. */

#define WAIT_T union wait
#define WTERMSIG(x) ((x).w_termsig)
#define WCOREDUMP(x) ((x).w_coredump)
#define WEXITSTATUS(x) ((x).w_retcode)
#ifndef WIFSIGNALED
#define WIFSIGNALED(x) (WTERMSIG(x) != 0)
#endif

#endif /* WTERMSIG defined or USG and don't have . */


extern int dup2 ();
extern int fork (), wait (), execve ();
extern void _exit ();
extern int geteuid (), getegid ();

#if !defined(USG) && !defined(sigmask)
#define sigmask(sig) (1 << ((sig) - 1))
#endif

#ifndef USG
extern int getdtablesize ();
#else
#include
#define getdtablesize() NOFILE
#endif

extern void wait_to_start_job ();
extern int start_remote_job_p ();
extern int start_remote_job (), remote_status ();


#if defined(USG) && !defined(HAVE_SIGLIST)
static char *sys_siglist[NSIG];
void init_siglist ();
#else /* Not USG and or HAVE_SIGLIST. */
extern char *sys_siglist[];
#endif /* USG and not HAVE_SIGLIST. */

int child_handler ();
static void free_child (), start_job ();

/* Chain of all children. */

struct child *children = 0;

/* Number of children currently running. */

unsigned int job_slots_used = 0;

/* Nonzero if the `good' standard input is in use. */

static int good_stdin_used = 0;

/* Write an error message describing the exit status given in
EXIT_CODE, EXIT_SIG, and COREDUMP, for the target TARGET_NAME.
Append "(ignored)" if IGNORED is nonzero. */

static void
child_error (target_name, exit_code, exit_sig, coredump, ignored)
char *target_name;
int exit_code, exit_sig, coredump;
int ignored;
{
char *ignore_string = ignored ? " (ignored)" : "";

if (exit_sig == 0)
error ("*** [%s] Error %d%s", target_name, exit_code, ignore_string);
else
{
char *coredump_string = coredump ? " (core dumped)" : "";
if (exit_sig > 0 && exit_sig < NSIG)
error ("*** [%s] %s%s",
target_name, sys_siglist[exit_sig], coredump_string);
else
error ("*** [%s] Signal %d%s", target_name, exit_sig, coredump_string);
}
}

extern void block_remote_children (), unblock_remote_children ();

/* Block the child termination signal. */

void
block_children ()
{
#ifdef USG
/* Ignoring SIGCLD makes wait always return -1.
Using the default action does the right thing. */
(void) signal (SIGCLD, SIG_DFL);
#else
(void) sigblock (sigmask (SIGCHLD));
#endif

block_remote_children ();
}

/* Unblock the child termination signal. */
void
unblock_children ()
{
#ifdef USG
(void) signal (SIGCLD, child_handler);
#else
(void) sigsetmask (sigblock (0) & ~sigmask (SIGCHLD));
#endif

unblock_remote_children ();
}

extern int shell_function_pid, shell_function_completed;

/* Handle a child-termination signal (SIGCHLD, or SIGCLD for USG),
storing the returned status and the new command state (`cs_finished')
in the `file' member of the `struct child' for the dead child,
and removing the child from the chain.

If we were called as a signal handler, SIG should be SIGCHLD
(SIGCLD for USG). If instead it is zero, we were called explicitly
and should block waiting for running children.
If SIG is < 0, - SIG is the maximum number of children to bury (record
status of and remove from the chain). */

int
child_handler (sig)
int sig;
{
WAIT_T status;
unsigned int dead_children = 0;

if (sig > 0)
block_remote_children ();

while (1)
{
int remote = 0;
register int pid;
int exit_code, exit_sig, coredump;
register struct child *lastc, *c;
int child_failed;

/* First, check for remote children. */
pid = remote_status (&exit_code, &exit_sig, &coredump, 0);
if (pid < 0)
{
/* No remote children. Check for local children. */

#if !defined(USG) || defined(HAVE_SYS_WAIT)
if (sig > 0)
pid = wait3 (&status, WNOHANG, (struct rusage *) 0);
else
pid = wait (&status);
#else /* USG and don't HAVE_SYS_WAIT. */
/* System V cannot do non-blocking waits, so we have two
choices if called as a signal handler: handle only one
child (there may be more if the signal was blocked),
or block waiting for more. The latter option makes
parallelism useless, so we must choose the former. */
pid = wait (&status);
#endif /* HAVE_SYS_WAIT or not USG. */

if (pid <= 0)
/* No local children. */
break;
else
{
/* Chop the status word up. */
exit_code = WEXITSTATUS (status);
exit_sig = WIFSIGNALED (status) ? WTERMSIG (status) : 0;
coredump = WCOREDUMP (status);
}
}
else
/* We got a remote child. */
remote = 1;

/* Check if this is the child of the `shell' function. */
if (!remote && pid == shell_function_pid)
{
/* It is. Leave an indicator for the `shell' function. */
if (exit_sig == 0 && exit_code == 127)
shell_function_completed = -1;
else
shell_function_completed = 1;

/* Check if we have reached our quota of children. */
++dead_children;
if (sig < 0 && dead_children == -sig)
break;
#if defined(USG) && !defined(HAVE_SYS_WAIT)
else if (sig > 0)
break;
#endif
else
continue;
}

child_failed = exit_sig != 0 || exit_code != 0;

/* Search for a child matching the deceased one. */
lastc = 0;
for (c = children; c != 0; lastc = c, c = c->next)
if (c->remote == remote && c->pid == pid)
break;

if (c == 0)
{
/* An unknown child died. */
char buf[100];
sprintf (buf, "Unknown%s job %d", remote ? " remote" : "", pid);
if (child_failed)
child_error (buf, exit_code, exit_sig, coredump,
ignore_errors_flag);
else
error ("%s finished.", buf);
}
else
{
/* If this child had the good stdin, say it is now free. */
if (c->good_stdin)
good_stdin_used = 0;

if (child_failed && !c->noerror && !ignore_errors_flag)
{
/* The commands failed. Write an error message,
delete non-precious targets, and abort. */
child_error (c->file->name, exit_code, exit_sig, coredump, 0);
c->file->update_status = 1;
if (exit_sig != 0)
delete_child_targets (c);
}
else
{
if (child_failed)
{
/* The commands failed, but we don't care. */
child_error (c->file->name,
exit_code, exit_sig, coredump, 1);
child_failed = 0;
}

/* If there are more commands to run, try to start them. */
start_job (c);
switch (c->file->command_state)
{
case cs_running:
/* Successfully started. Loop to reap more children. */
continue;

case cs_finished:
if (c->file->update_status != 0)
{
/* We failed to start the commands. */
delete_child_targets (c);
}
break;

default:
error ("internal error: `%s' command_state \
%d in child_handler", c->file->name);
abort ();
break;
}
}

/* Set the state flag to say the commands have finished. */
c->file->command_state = cs_finished;
notice_finished_file (c->file);

/* Remove the child from the chain and free it. */
if (lastc == 0)
children = c->next;
else
lastc->next = c->next;
free_child (c);

/* There is now another slot open. */
--job_slots_used;

/* If the job failed, and the -k flag was not given, die. */
if (child_failed && !keep_going_flag)
die (1);

/* See if we have reached our quota for blocking. */
++dead_children;
if (sig < 0 && dead_children == -sig)
break;
#if defined(USG) && !defined(HAVE_SYS_WAIT)
else if (sig > 0)
break;
#endif
}
}

#ifdef USG
if (sig > 0)
(void) signal (sig, child_handler);
#endif

if (sig > 0)
unblock_remote_children ();

return 0;
}


/* Wait for N children, blocking if necessary.
If N is zero, wait until we run out of children.
If ERR is nonzero and we have any children to wait for,
print a message on stderr. */

void
wait_for_children (n, err)
unsigned int n;
int err;
{
block_children ();

if (err && (children != 0 || shell_function_pid != 0))
{
fflush (stdout);
error ("*** Waiting for unfinished jobs....");
}

/* Call child_handler to do the work. */
(void) child_handler (- (int) n);

unblock_children ();
}

/* Free the storage allocated for CHILD. */

static void
free_child (child)
register struct child *child;
{
if (child->commands != 0)
free (child->commands);
free ((char *) child);
}

/* Start a job to run the commands specified in CHILD.
CHILD is updated to reflect the commands and ID of the child process. */

static void
start_job (child)
register struct child *child;
{
static int bad_stdin = -1;
char *end;
register char *p;
int backslash;
char noprint = 0, recursive;
char **argv;

if (child->command_ptr == 0 || *child->command_ptr == '\0')
/* There are no more lines in the expansion of this line. */
if (child->file->cmds->command_lines[child->command_line] == 0)
{
/* There are no more lines to be expanded. */
child->command_ptr = 0;
child->file->command_state = cs_finished;
child->file->update_status = 0;
return;
}
else
{
/* Expand and run the next line. */
child->command_ptr = child->commands
= allocated_variable_expand_for_file
(child->file->cmds->command_lines[child->command_line++],
child->file);
}

/* Set RECURSIVE if the unexpanded line contains $(MAKE). */
recursive = child->file->cmds->lines_recurse[child->command_line - 1];

/* Find the end of this line. Backslash-newlines don't mean the end. */

end = child->command_ptr;
while (*end != '\0')
{
p = index (end, '\n');
if (p == 0)
{
end += strlen (end);
break;
}

end = p;
backslash = 0;
while (*--p == '\\')
backslash = !backslash;

if (backslash)
{
++end;
/* If there is a tab after a backslash-newline,
remove it, since it was most likely used to line
up the continued line with the previous one. */
if (*end == '\t')
strcpy (end, end + 1);
}
else
break;
}

p = child->command_ptr;

if (*end == '\0')
child->command_ptr = 0;
else
{
*end = '\0';
child->command_ptr = end + 1;
}

child->noerror = 0;
while (*p != '\0')
{
if (*p == '@')
noprint = 1;
else if (*p == '-')
child->noerror = 1;
else if (*p == '+')
recursive = 1;
else if (*p != ' ' && *p != '\t')
break;
++p;
}

/* If -q was given, just say that updating `failed'. */
if (question_flag && !recursive)
goto error;

/* There may be some preceding whitespace left if there
was nothing but a backslash on the first line. */
p = next_token (p);

if (*p == '\0')
/* There were no commands on this line. Go to the next. */
{
start_job (child);
return;
}

/* Print out the command. */

if (just_print_flag || (!noprint && !silent_flag))
puts (p);

/* If -n was given, recurse to get the next line in the sequence. */

if (just_print_flag && !recursive)
{
start_job (child);
return;
}

/* Collapse backslash-newlines in this line. */

collapse_continuations (p);

/* Figure out an argument list from this command line. */

argv = construct_command_argv (p, child->file);

/* Flush the output streams so they won't have things written twice. */

fflush (stdout);
fflush (stderr);

/* Set up a bad standard input that reads from a broken pipe. */

if (bad_stdin == -1)
{
/* Make a file descriptor that is the read end of a broken pipe.
This will be used for some children's standard inputs. */
int pd[2];
if (pipe (pd) == 0)
{
/* Close the write side. */
(void) close (pd[1]);
/* Save the read side. */
bad_stdin = pd[0];
}
}

/* Decide whether to give this child the `good' standard input
(one that points to the terminal or whatever), or the `bad' one
that points to the read side of a broken pipe. */

child->good_stdin = !good_stdin_used;
if (child->good_stdin)
good_stdin_used = 1;

child->deleted = 0;

/* Set up the environment for the child. */
if (child->environment == 0)
child->environment = target_environment (child->file);

if (start_remote_job_p ())
{
int is_remote, id, used_stdin;
if (start_remote_job (argv, child->good_stdin ? 0 : bad_stdin,
&is_remote, &id, &used_stdin))
goto error;
else
{
if (child->good_stdin && !used_stdin)
{
child->good_stdin = 0;
good_stdin_used = 0;
}
child->remote = is_remote;
child->pid = id;
}
}
else
{
if (child->command_line - 1 == 0)
/* Wait for the load to be low enough if this
is the first command in the sequence. */
wait_to_start_job ();

/* Fork the child process. */

child->remote = 0;
child->pid = vfork ();
if (child->pid == 0)
/* We are the child side. */
child_execute_job (child->good_stdin ? 0 : bad_stdin, 1,
child->file, argv, child->environment);
else if (child->pid < 0)
{
/* Fork failed! */
perror_with_name (VFORK_NAME, "");
goto error;
}
}

/* We are the parent side. Set the state to
say the commands are running and return. */

child->file->command_state = cs_running;
return;

error:;
child->file->update_status = 1;
child->file->command_state = cs_finished;
}


/* Create a `struct child' for FILE and start its commands running. */

void
new_job (file)
register struct file *file;
{
extern unsigned int files_remade;
register struct commands *cmds = file->cmds;
register struct child *c;

/* Chop the commands up into lines if they aren't already. */
chop_commands (cmds);

if (job_slots > 0)
/* Wait for a job slot to be freed up. */
while (job_slots_used == job_slots)
wait_for_children (1, 0);

/* Start the command sequence, record it in a new
`struct child', and add that to the chain. */

block_children ();

c = (struct child *) xmalloc (sizeof (struct child));
c->file = file;
c->command_line = 0;
c->command_ptr = c->commands = 0;
c->environment = 0;
start_job (c);
switch (file->command_state)
{
case cs_running:
c->next = children;
children = c;
/* One more job slot is in use. */
++job_slots_used;
break;

case cs_finished:
free_child (c);
notice_finished_file (file);
break;

default:
error ("internal error: `%s' command_state == %d in new_job",
file->name, (int) file->command_state);
abort ();
break;
}

unblock_children ();

++files_remade;

if (job_slots == 1 && file->command_state == cs_running)
{
/* Since there is only one job slot, make things run linearly.
Wait for the child to finish, setting the state to `cs_finished'. */
while (file->command_state != cs_finished)
wait_for_children (1, 0);
}
}

/* Replace the current process with one executing the command in ARGV.
STDIN_FD and STDOUT_FD are used as the process's stdin and stdout;
FILE, if not nil, is used as the variable-set to expand `PATH' and `SHELL';
ENVP is the environment of the new program. This function won't return. */

void
child_execute_job (stdin_fd, stdout_fd, file, argv, envp)
int stdin_fd, stdout_fd;
struct file *file;
char **argv, **envp;
{
if (stdin_fd != 0)
(void) dup2 (stdin_fd, 0);
if (stdout_fd != 1)
(void) dup2 (stdout_fd, 1);

/* Free up file descriptors. */
{
register int d;
int max = getdtablesize ();
for (d = 3; d < max; ++d)
(void) close (d);
}

/* Don't block children for our child. */
unblock_children ();

/* Run the command. */
exec_command (argv, envp,
allocated_variable_expand_for_file ("$(PATH)", file),
allocated_variable_expand_for_file ("$(SHELL)", file));
}

/* Search PATH for FILE, returning a full pathname or nil.
The returned pathname is allocated via `malloc'. */

static char *
search_path (file, path)
char *file, *path;
{
if (*path == '\0' || index (file, '/') != 0)
return file;
else
{
char *program;
unsigned int len;

#ifndef USG
extern int getgroups ();
static int groups[NGROUPS];
static int ngroups = -1;
if (ngroups == -1)
ngroups = getgroups (NGROUPS, groups);
#endif /* Not USG. */

len = strlen (file) + 1;
program = (char *) xmalloc (strlen (path) + 1 + len);
do
{
struct stat st;
int perm;
char *p;

p = index (path, ':');
if (p == 0)
p = path + strlen (path);

if (p == path)
bcopy (file, program, len);
else
{
bcopy (path, program, p - path);
program[p - path] = '/';
bcopy (file, program + (p - path) + 1, len);
}

if (stat (program, &st) == 0
&& (st.st_mode & S_IFMT) == S_IFREG)
{
if (st.st_uid == geteuid ())
perm = (st.st_mode & 0100);
else if (st.st_gid == getegid ())
perm = (st.st_mode & 0010);
else
{
#ifndef USG
register int i;
for (i = 0; i < ngroups; ++i)
if (groups[i] == st.st_gid)
break;
if (i < ngroups)
perm = (st.st_mode & 0010);
else
#endif /* Not USG. */
perm = (st.st_mode & 0001);
}

if (perm != 0)
return program;
}

path = p + 1;
} while (*path != '\0');
}

return 0;
}

/* Replace the current process with one running the command in ARGV,
with environment ENVP. The program named in ARGV[0] is searched
for in PATH. SHELL is the shell program to run for shell scripts.
This function does not return. */

void
exec_command (argv, envp, path, shell)
char **argv, **envp;
char *path, *shell;
{
char *program;

program = search_path (argv[0], path);
if (program == 0)
error ("%s: Command not found", argv[0]);
else
{
/* Run the program. */
execve (program, argv, envp);

if (errno == ENOEXEC)
{
char *shell_path = search_path (shell, path);
if (shell_path == 0)
error ("%s: Shell program not found", shell);
else
{
char **new_argv;
int argc;

argc = 1;
while (argv[argc] != 0)
++argc;

new_argv = (char **) alloca ((1 + argc + 1) * sizeof (char *));
new_argv[0] = shell_path;
new_argv[1] = program;
while (argc > 0)
{
new_argv[1 + argc] = argv[argc];
--argc;
}

execve (shell_path, new_argv, envp);
perror_with_name ("execve: ", shell_path);
}
}
else
perror_with_name ("execve: ", program);
}

_exit (127);
}

/* Figure out the argument list necessary to run LINE as a command.
Try to avoid using a shell. This routine handles only ' quoting.
Starting quotes may be escaped with a backslash. If any of the
characters in sh_chars[] is seen, or any of the builtin commands
listed in sh_cmds[] is the first word of a line, the shell is used.

FILE is the target whose commands these are. It is used for
variable expansion for $(SHELL) and $(IFS). */

char **
construct_command_argv (line, file)
char *line;
struct file *file;
{
static char sh_chars[] = "#;\"*?[]&|<>(){}=$`";
static char *sh_cmds[] = { "cd", "eval", "exec", "exit", "login",
"logout", "set", "umask", "wait", "while", "for",
"case", "if", ":", ".", "break", "continue",
"export", "read", "readonly", "shift", "times",
"trap", "switch", 0 };
register int i;
register char *p;
register char *ap;
char *end;
int instring;
char **new_argv = 0;

/* See if it is safe to parse commands internally. */
p = variable_expand_for_file ("$(SHELL)", file);
if (strcmp (p, "/bin/sh"))
goto slow;
p = variable_expand_for_file ("$(IFS)", file);
for (ap = p; *ap != '\0'; ++ap)
if (*ap != ' ' && *ap != '\t' && *ap != '\n')
goto slow;

i = strlen (line) + 1;

/* More than 1 arg per character is impossible. */
new_argv = (char **) xmalloc (i * sizeof (char *));

/* All the args can fit in a buffer as big as LINE is. */
ap = new_argv[0] = (char *) xmalloc (i);
end = ap + i;

/* I is how many complete arguments have been found. */
i = 0;
instring = 0;
for (p = line; *p != '\0'; ++p)
{
if (ap > end)
abort ();

if (instring)
{
/* Inside a string, just copy any char except a closing quote. */
if (*p == '\'')
instring = 0;
else
*ap++ = *p;
}
else if (index (sh_chars, *p) != 0)
/* Not inside a string, but it's a special char. */
goto slow;
else
/* Not a special char. */
switch (*p)
{
case '\\':
if (p[1] != '\0' && p[1] != '\n')
/* Copy and skip the following char. */
*ap++ = *++p;
break;

case '\'':
instring = 1;
break;

case '\n':
case ' ':
case '\t':
/* We have the end of an argument.
Terminate the text of the argument. */
*ap++ = '\0';
new_argv[++i] = ap;
/* If this argument is the command name,
see if it is a built-in shell command.
If so, have the shell handle it. */
if (i == 1)
{
register int j;
for (j = 0; sh_cmds[j] != 0; ++j)
if (streq (sh_cmds[j], new_argv[0]))
goto slow;
}

/* Ignore multiple whitespace chars. */
p = next_token (p);
/* Next iteration should examine the first nonwhite char. */
--p;
break;

default:
*ap++ = *p;
break;
}
}

/* Terminate the last argument and the argument list. */

*ap = '\0';
if (new_argv[i][0] != '\0')
++i;
new_argv[i] = 0;

if (new_argv[0] == 0)
/* Line was empty. */
return 0;
else
return new_argv;

slow:;
if (new_argv != 0)
free (new_argv);
new_argv = (char **) xmalloc (4 * sizeof (char *));
new_argv[0] = allocated_variable_expand_for_file ("$(SHELL)", file);
new_argv[1] = "-c";
new_argv[2] = line;
new_argv[3] = 0;

return new_argv;
}

#if defined(USG) && !defined(HAVE_SIGLIST)
/* Initialize sys_siglist. */

void
init_siglist ()
{
char buf[100];
register unsigned int i;

for (i = 0; i < NSIG; ++i)
switch (i)
{
default:
sprintf (buf, "Signal %u", i);
sys_siglist[i] = savestring (buf, strlen (buf));
break;
case SIGHUP:
sys_siglist[i] = "Hangup";
break;
case SIGINT:
sys_siglist[i] = "Interrupt";
break;
case SIGQUIT:
sys_siglist[i] = "Quit";
break;
case SIGILL:
sys_siglist[i] = "Illegal Instruction";
break;
case SIGTRAP:
sys_siglist[i] = "Trace Trap";
break;
case SIGIOT:
sys_siglist[i] = "IOT Trap";
break;
#ifdef SIGEMT
case SIGEMT:
sys_siglist[i] = "EMT Trap";
break;
#endif
#ifdef SIGDANGER
case SIGDANGER:
sys_siglist[i] = "Danger signal";
break;
#endif
case SIGFPE:
sys_siglist[i] = "Floating Point Exception";
break;
case SIGKILL:
sys_siglist[i] = "Killed";
break;
case SIGBUS:
sys_siglist[i] = "Bus Error";
break;
case SIGSEGV:
sys_siglist[i] = "Segmentation fault";
break;
case SIGSYS:
sys_siglist[i] = "Bad Argument to System Call";
break;
case SIGPIPE:
sys_siglist[i] = "Broken Pipe";
break;
case SIGALRM:
sys_siglist[i] = "Alarm Clock";
break;
case SIGTERM:
sys_siglist[i] = "Terminated";
break;
case SIGUSR1:
sys_siglist[i] = "User-defined signal 1";
break;
case SIGUSR2:
sys_siglist[i] = "User-defined signal 2";
break;
#ifdef SIGCLD
case SIGCLD:
#endif
#if defined(SIGCHLD) && !defined(SIGCLD)
case SIGCHLD:
#endif
sys_siglist[i] = "Child Process Exited";
break;
#ifdef SIGPWR
case SIGPWR:
sys_siglist[i] = "Power Failure";
break;
#endif
#ifdef SIGVTALRM
case SIGVTALRM:
sys_siglist[i] = "Virtual Timer Alarm";
break;
#endif
#ifdef SIGPROF
case SIGPROF:
sys_siglist[i] = "Profiling Alarm Clock";
break;
#endif
#ifdef SIGIO
case SIGIO:
sys_siglist[i] = "I/O Possible";
break;
#endif
#ifdef SIGWINDOW
case SIGWINDOW:
sys_siglist[i] = "Window System Signal";
break;
#endif
#ifdef SIGSTOP
case SIGSTOP:
sys_siglist[i] = "Stopped (signal)";
break;
#endif
#ifdef SIGTSTP
case SIGTSTP:
sys_siglist[i] = "Stopped";
break;
#endif
#ifdef SIGCONT
case SIGCONT:
sys_siglist[i] = "Continued";
break;
#endif
#ifdef SIGTTIN
case SIGTTIN:
sys_siglist[i] = "Stopped (tty input)";
break;
#endif
#ifdef SIGTTOU
case SIGTTOU:
sys_siglist[i] = "Stopped (tty output)";
break;
#endif
#ifdef SIGURG
case SIGURG:
sys_siglist[i] = "Urgent Condition on Socket";
break;
#endif
#ifdef SIGXCPU
case SIGXCPU:
sys_siglist[i] = "CPU Limit Exceeded";
break;
#endif
#ifdef SIGXFSZ
case SIGXFSZ:
sys_siglist[i] = "File Size Limit Exceeded";
break;
#endif
}
}
#endif /* USG and not HAVE_SIGLIST. */

#if defined(USG) && !defined(USGr3) && !defined(HAVE_DUP2)
int
dup2 (old, new)
int old, new;
{
int fd;

(void) close (new);
fd = dup (old);
if (fd != new)
{
(void) close (fd);
errno = EMFILE;
return -1;
}

return fd;
}
#endif /* USG and not USGr3 and not HAVE_DUP2. */


  3 Responses to “Category : C Source Code
Archive   : OHLMAKE.ZIP
Filename : JOB.C

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/