2021-10-11 18:37:13 -03:00

2029 lines
47 KiB
C

#ident "@(#)parallel.c 1.1 94/10/31 Copyright 1986,1987,1988 Sun Micro"
#ifdef PARALLEL
/*
* parallel.c
*
* Deal with the parallel processing
*/
/*
* Included files
*/
#include "defs.h"
#include <sys/file.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/dk.h>
#include <rpc/rpc.h>
#include <rpcsvc/rstat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
/*
* Defined macros
*/
#define MAXRULES 100
#define BUFSIZE 1024
#define SKIPSPACE(x) while (*x && \
(*x == (int) space_char || \
*x == (int) tab_char || \
*x == (int) comma_char)) { \
x++; \
}
#define SKIPWORD(x) while (*x && \
(*x != (int) space_char) && \
(*x != (int) tab_char) && \
(*x != (int) newline_char) && \
(*x != (int) comma_char) && \
(*x != (int) equal_char)) { \
x++; \
}
#define SKIPTOEND(x) while (*x && (*x != (int) newline_char)) { \
x++; \
}
/*
* typedefs & structs
*/
/*
* Static variables
*/
static Boolean first_time;
static Boolean local;
static char **machines;
static u_long *machine_addr;
static long *machine_boot;
static int machine_cnt;
static int *machine_failcnt;
static int *machine_limit;
static int *machine_load;
static int *machine_max;
static u_long myaddr;
static char myhost[33];
static int mymachine;
static struct timeval next_load_check;
static Boolean parent;
static int process_machine = -1;
static int process_running = -1;
static Running *running_tail = &running_list;
static Name subtree_conflict;
static Name subtree_conflict2;
/*
* File table of contents
*/
extern Doname execute_parallel();
extern void distribute_process();
extern void redirect_io();
extern Doname doname_parallel();
extern void doname_subtree();
extern void finish_running();
extern void process_next();
extern Property *set_conditionals();
extern void reset_conditionals();
extern Boolean dependency_conflict();
extern void await_parallel();
extern void update_machine_max();
extern void finish_children();
extern void dump_out_file();
extern void finish_doname();
extern void add_running();
extern void add_pending();
extern void add_serial();
extern void add_subtree();
extern void store_conditionals();
extern Boolean parallel_ok();
extern Boolean read_make_machines();
extern Boolean host_stat();
extern Boolean is_running();
/*
* execute_parallel(line)
*
* Spawns a parallel process to execute the command group.
*
* Return value:
* The result of the execution
*
* Parameters:
* line The command group to execute
*
* Global variables used:
* file_number Temporary file number
* parallel_process_cnt Number of processes currently running
* shell_name Shell to use for execution
* silent Silent flag
* stderr_file Temporary output file for stderr
* stdout_file Temporary output file for stdout
* stdout_stderr_same Flag set true if stderr and stdout the same
* vpath_defined True if vpath translation needed
*/
Doname
execute_parallel(line)
Property line;
{
Cmd_line rule;
Name target = line->body.line.target;
char *argv[MAXRULES + 5];
char **p;
char *cp;
Boolean silent_flag;
int ignore;
int argcnt;
char string[MAXPATHLEN];
/* Command invoked: makesh path shell -silent/nosilent command ... */
p = argv;
*p++ = "/usr/lib/makesh";
*p++ = get_current_path();
*p++ = getvar(shell_name)->string;
*p++ = silent ? "-silent" : "-nosilent";
argcnt = 0;
for (rule = line->body.line.command_used;
rule != NULL;
rule = rule->next) {
if (vpath_defined) {
rule->command_line =
vpath_translation(rule->command_line);
}
silent_flag = false;
ignore = 0;
if (rule->command_line->hash.length > 0) {
if (++argcnt == MAXRULES) {
/* Too many rules, run serially instead */
return build_serial;
}
if (rule->silent && !silent) {
silent_flag = true;
}
if (rule->ignore_error) {
ignore++;
}
if (silent_flag || ignore) {
*p = (char *) alloca(strlen(rule->
command_line->
string) +
(silent_flag ? 1 : 0) +
ignore + 1);
cp = *p++;
if (silent_flag) {
*cp++ = (int) at_char;
}
if (ignore) {
*cp++ = (int) hyphen_char;
}
(void) strcpy(cp, rule->command_line->string);
} else {
*p++ = rule->command_line->string;
}
}
}
if (argcnt == 0) {
return build_ok;
}
*p = NULL;
(void) sprintf(string,
"/tmp/make.stdout.%d.%d",
getpid(),
file_number++);
stdout_file = GETNAME(string, FIND_LENGTH);
stdout_file->stat.is_file = true;
if (!stdout_stderr_same) {
(void) sprintf(string,
"/tmp/make.stderr.%d.%d",
getpid(),
file_number++);
stderr_file = GETNAME(string, FIND_LENGTH);
stderr_file->stat.is_file = true;
} else {
stderr_file = NULL;
}
distribute_process(argv);
parallel_process_cnt++;
return build_running;
}
/*
* distribute_process(command)
*
* Finds a machine on which to run the given command.
*
* Parameters:
* command Argv vector of command to execute
*
* Static variables used:
* local True if builds must be done locally
* machines Array of string machine names to run commands
* machine_addr Array of integer host addresses for machines
* machine_cnt Number of machines available
* machine_load Array of load numbers for each machine
* machine_max Array of maximum processes per each machine
* myaddr The host address of this machine
* mymachine The index of this machine into the machine ary
* parent True if we are the top most make running
* process_running Set to the pid of the process set running
* process_machine Set to the index of the machine used
*
* Global variables used:
* nse dwight
* parallel_process_cnt Number of parallel process running
* remote_command_name Set to the name of the remote execution cmd
*/
static void
distribute_process(command)
char **command;
{
int i;
int *lp;
int min_load;
int *mm;
int available;
char **argv;
char **ap;
char *program;
Property remote_command;
u_long address;
Boolean warned = false;
min_load = 1000;
process_machine = -1;
while (process_machine == -1) {
update_machine_max();
/*
* If nothing is running, we're the parent make, and this
* machine is on the list, use it.
*/
if (parallel_process_cnt == 0 &&
parent &&
mymachine != -1) {
process_machine = mymachine;
break;
}
/*
* If we're building this target locally, wait for all
* the children to finish.
*/
if (local) {
if (parallel_process_cnt == 0) {
break;
} else {
await_parallel(false);
finish_children(true);
continue;
}
}
/* Look for the machine with the lowest load */
for (i = 0, lp = machine_load, mm = machine_max;
i < machine_cnt;
i++, lp++, mm++) {
if ((*lp < min_load) && (*lp < *mm)) {
process_machine = i;
min_load = *lp;
}
}
if (process_machine == -1) {
/*
* if no machine available and nothing running,
* give warning and sleep.
*/
if (parallel_process_cnt == 0) {
update_machine_max();
available = 0;
for (i = 0, mm = machine_max;
i < machine_cnt;
i++, mm++) {
if (*mm > 0) {
available += *mm;
}
}
if (available == 0) {
if (isatty(2)) {
warning("No machines available, waiting for load to come down");
warned = true;
}
sleep((int) update_delay);
}
} else {
await_parallel(false);
finish_children(true);
}
}
}
/* If warning previously given, retract it */
if (warned) {
warned = false;
warning("Machines available, continuing...");
}
if (local) {
address = myaddr;
} else {
address = *(machine_addr + process_machine);
*(machine_load + process_machine) += 1;
}
/*
* If we are building locally, just fork makesh; otherwise
* use the remote execution program to send the command.
*/
if (address == myaddr) {
argv = command;
program = "/usr/lib/makesh";
} else {
for (i = 0, argv = command; *argv; i++, argv++);
argv = (char **) alloca((int) ((i+4) * sizeof (char *)));
program = NULL;
ap = argv;
remote_command =
get_prop(remote_command_name->prop, macro_prop);
if (remote_command != NULL) {
program = remote_command->body.macro.value->string;
}
if (program != NULL && strlen(program) > 0) {
*ap++ = program;
} else {
program = nse ?
"/usr/nse/bin/nse_on" : "/usr/bin/on";
*ap++ = program;
*ap++ = "-i";
}
*ap++ = *(machines + process_machine);
while (*command) {
*ap++ = *command++;
}
*ap = NULL;
}
setvar_envvar();
if ((process_running = vfork()) == 0) {
enable_interrupt((void (*) ()) SIG_DFL);
redirect_io();
(void) execve(program, argv, environ);
(void) fprintf(stderr,
"make: Cannot load command `%s': %s\n",
program,
errmsg(errno));
(void) fflush(stderr);
_exit(1);
}
enable_interrupt(handle_interrupt);
}
/*
* redirect_io()
*
* Redirects stdin, stdout, and stderr for a parallel child.
*
* Parameters:
*
* Global variables used:
* stderr_file Temporary output file for stderr
* stdout_file Temporary output file for stdout
* stdout_stderr_same Flag set if stdout and stderr are the same
*/
static void
redirect_io()
{
(void) close(0);
(void) open("/dev/null", O_RDONLY);
(void) close(1);
(void) close(2);
if (open(stdout_file->string,
O_WRONLY | O_CREAT,
S_IREAD|S_IWRITE) < 0) {
fatal("Couldn't open standard out temp file `%s': %s",
stdout_file->string,
errmsg(errno));
}
if (stdout_stderr_same) {
(void) dup2(1, 2);
}
else if (open(stderr_file->string,
O_WRONLY | O_CREAT,
S_IREAD|S_IWRITE) < 0) {
fatal("Couldn't open standard error temp file `%s': %s",
stderr_file->string,
errmsg(errno));
}
}
/*
* doname_parallel(target, do_get, implicit)
*
* Processes the given target and finishes up any parallel
* processes left running.
*
* Return value:
* Result of target build
*
* Parameters:
* target Target to build
* do_get True if sccs get to be done
* implicit True if this is an implicit target
*
* Global variables used:
*/
Doname
doname_parallel(target, do_get, implicit)
Name target;
Boolean do_get;
Boolean implicit;
{
Doname result;
result = doname_check(target, do_get, implicit, false);
if (result == build_ok || result == build_failed) {
return result;
}
finish_running();
return target->state;
}
/*
* doname_subtree(target, do_get, implicit)
*
* Completely computes an object and its dependents for a
* serial subtree build.
*
* Parameters:
* target Target to build
* do_get True if sccs get to be done
* implicit True if this is an implicit target
*
* Static variables used:
* running_tail Tail of the list of running processes
*
* Global variables used:
* running_list The list of running processes
*/
static void
doname_subtree(target, do_get, implicit)
Name target;
Boolean do_get;
Boolean implicit;
{
Running save_running_list;
Running *save_running_tail;
save_running_list = running_list;
save_running_tail = running_tail;
running_list = NULL;
running_tail = &running_list;
target->state = build_subtree;
target->checking_subtree = true;
while(doname_check(target, do_get, implicit, false) == build_running) {
target->checking_subtree = false;
finish_running();
target->state = build_subtree;
}
target->checking_subtree = false;
running_list = save_running_list;
running_tail = save_running_tail;
}
/*
* finish_running()
*
* Keeps processing until the running_list is emptied out.
*
* Parameters:
*
* Global variables used:
* running_list The list of running processes
*/
void
finish_running()
{
while (running_list != NULL) {
await_parallel(true);
finish_children(true);
if (running_list != NULL) {
process_next();
}
}
}
/*
* process_next()
*
* Searches the running list for any targets which can
* start processing. This can be either a pending target, a serial target
* or a subtree target.
*
* Parameters:
*
* Static variables used:
* running_tail The end of the list of running procs
* subtree_conflict A target which conflicts with a subtree
* subtree_conflict2 The other target which conflicts
*
* Global variables used:
* commands_done True if commands executed
* debug_level Controls debug output
* parallel_process_cnt Number of parallel process running
* recursion_level Indentation for debug output
* running_list List of running processes
*/
static void
process_next()
{
Running rp;
Running *rp_prev;
Property line;
Chain target_group;
Dependency dep;
Boolean quiescent = true;
Running *subtree_target;
Boolean saved_commands_done;
Property *conditionals;
subtree_target = NULL;
subtree_conflict = NULL;
subtree_conflict2 = NULL;
/*
* If nothing currently running, build a serial target, if any.
*/
for (rp_prev = &running_list, rp = running_list;
rp != NULL && parallel_process_cnt == 0;
rp = rp->next) {
if (rp->state == build_serial) {
*rp_prev = rp->next;
if (rp->next == NULL) {
running_tail = rp_prev;
}
recursion_level = rp->recursion_level;
rp->target->state = build_pending;
conditionals =
set_conditionals(rp->conditional_cnt,
rp->conditional_targets);
(void) doname_check(rp->target,
rp->do_get,
rp->implicit,
false);
reset_conditionals(rp->conditional_cnt,
rp->conditional_targets,
conditionals);
quiescent = false;
break;
} else {
rp_prev = &rp->next;
}
}
/*
* Find a target to build. The target must be pending, have all
* its dependencies built, and not be in a target group with a target
* currently building.
*/
for (rp_prev = &running_list, rp = running_list;
rp != NULL;
rp = rp->next) {
if (!(rp->state == build_pending ||
rp->state == build_subtree)) {
quiescent = false;
rp_prev = &rp->next;
} else if (rp->state == build_pending) {
line = get_prop(rp->target->prop, line_prop);
for(dep = line->body.line.dependencies;
dep != NULL;
dep = dep->next) {
if (dep->name->state == build_running ||
dep->name->state == build_pending ||
dep->name->state == build_serial) {
break;
}
}
if (dep == NULL) {
for (target_group = line->body.
line.target_group;
target_group != NULL;
target_group = target_group->next) {
if (is_running(target_group->name)) {
break;
}
}
if (target_group == NULL) {
*rp_prev = rp->next;
if (rp->next == NULL) {
running_tail = rp_prev;
}
recursion_level = rp->recursion_level;
rp->target->state = rp->redo ?
build_dont_know : build_pending;
saved_commands_done = commands_done;
conditionals =
set_conditionals
(rp->conditional_cnt,
rp->conditional_targets);
if ((doname_check(rp->target,
rp->do_get,
rp->implicit,
false) !=
build_running) &&
!commands_done) {
commands_done =
saved_commands_done;
}
reset_conditionals
(rp->conditional_cnt,
rp->conditional_targets,
conditionals);
quiescent = false;
break;
} else {
rp_prev = &rp->next;
}
} else {
rp_prev = &rp->next;
}
} else {
rp_prev = &rp->next;
}
}
/*
* If nothing has been found to build and there exists a subtree
* target with no dependency conflicts, build it.
*/
if (quiescent) {
for(rp_prev = &running_list, rp = running_list;
rp != NULL;
rp = rp->next) {
if (rp->state == build_subtree) {
if (!dependency_conflict(rp->target)) {
*rp_prev = rp->next;
if (rp->next == NULL) {
running_tail = rp_prev;
}
recursion_level = rp->recursion_level;
conditionals =
set_conditionals
(rp->conditional_cnt,
rp->conditional_targets);
doname_subtree(rp->target,
rp->do_get,
rp->implicit);
reset_conditionals
(rp->conditional_cnt,
rp->conditional_targets,
conditionals);
quiescent = false;
break;
} else {
subtree_target = rp_prev;
rp_prev = &rp->next;
}
} else {
rp_prev = &rp->next;
}
}
}
/*
* If still nothing found to build, we either have a deadlock or
* a subtree with a dependency conflict with something waiting to
* build.
*/
if (quiescent) {
if (subtree_target == NULL) {
fatal("Internal error: deadlock detected in process_next");
} else {
rp = *subtree_target;
if (debug_level > 0) {
warning("Conditional macro conflict encountered for %s between %s and %s",
subtree_conflict2->string,
rp->target->string,
subtree_conflict->string);
}
*subtree_target = (*subtree_target)->next;
if (rp->next == NULL) {
running_tail = subtree_target;
}
recursion_level = rp->recursion_level;
conditionals =
set_conditionals(rp->conditional_cnt,
rp->conditional_targets);
doname_subtree(rp->target, rp->do_get, rp->implicit);
reset_conditionals(rp->conditional_cnt,
rp->conditional_targets,
conditionals);
}
}
}
/*
* set_conditionals(cnt, targets)
*
* Sets the conditional macros for the targets given in the array of
* targets. The old macro values are returned in an array of
* Properties for later resetting.
*
* Return value:
* Array of conditional macro settings
*
* Parameters:
* cnt Number of targets
* targets Array of targets
*/
static Property *
set_conditionals(cnt, targets)
int cnt;
Name *targets;
{
Property *locals, *lp;
Name *tp;
locals = (Property *) getmem(cnt * sizeof(Property));
for (lp=locals, tp=targets; cnt>0; cnt--, lp++, tp++) {
*lp = (Property) getmem((*tp)->conditional_cnt *
sizeof(struct Property));
set_locals(*tp, *lp);
}
return locals;
}
/*
* reset_conditionals(cnt, targets, locals)
*
* Resets the conditional macros as saved in the given array of
* Properties. The resets are done in reverse order. Afterwards the
* data structures are freed.
*
* Parameters:
* cnt Number of targets
* targets Array of targets
* locals Array of dependency macro settings
*
*/
static void
reset_conditionals(cnt, targets, locals)
int cnt;
Name *targets;
Property *locals;
{
Name *tp;
Property *lp;
for (tp=targets+(cnt-1), lp=locals+(cnt-1); cnt>0; cnt--, tp--, lp--) {
reset_locals(*tp, *lp,
get_prop((*tp)->prop, conditional_prop), 0);
retmem((char *) *lp);
}
retmem((char *) locals);
}
/*
* dependency_conflict(target)
*
* Returns true if there is an intersection between
* the subtree of the target and any dependents of the pending targets.
*
* Return value:
* True if conflict found
*
* Parameters:
* target Subtree target to check
*
* Static variables used:
* subtree_conflict Target conflict found
* subtree_conflict2 Second conflict found
*
* Global variables used:
* running_list List of running processes
* wait_name .WAIT, not a real dependency
*/
static Boolean
dependency_conflict(target)
Name target;
{
Property line;
Property pending_line;
Dependency dp;
Dependency pending_dp;
Running rp;
/* Return if we are already checking this target */
if (target->checking_subtree) {
return false;
}
target->checking_subtree = true;
line = get_prop(target->prop, line_prop);
if (line == NULL) {
target->checking_subtree = false;
return false;
}
/* Check each dependency of the target for conflicts */
for (dp = line->body.line.dependencies; dp != NULL; dp = dp->next) {
/* Ignore .WAIT dependency */
if (dp->name == wait_name) {
continue;
}
/*
* For each pending target, look for a dependency which
* is the same as a dependency of the subtree target. Since
* we can't build the subtree until all pending targets have
* finished which depend on the same dependency, this is
* a conflict.
*/
for (rp = running_list; rp != NULL; rp = rp->next) {
if (rp->state == build_pending) {
pending_line = get_prop(rp->target->prop,
line_prop);
if (pending_line == NULL) {
continue;
}
for(pending_dp = pending_line->
body.line.dependencies;
pending_dp != NULL;
pending_dp = pending_dp->next) {
if (dp->name == pending_dp->name) {
target->checking_subtree
= false;
subtree_conflict = rp->target;
subtree_conflict2 = dp->name;
return true;
}
}
}
}
if (dependency_conflict(dp->name)) {
target->checking_subtree = false;
return true;
}
}
target->checking_subtree = false;
return false;
}
/*
* await_parallel(waitflg)
*
* Waits for parallel children to exit and finishes their processing.
* If waitflg is false, the function returns after update_delay.
*
* Parameters:
* waitflg dwight
*
* Global variables used:
*/
void
await_parallel(waitflg)
Boolean waitflg;
{
int pid;
int waiterr;
Running rp;
union wait status;
Boolean nohang;
nohang = false;
for(;;) {
if (!nohang) {
(void) alarm((int) update_delay);
}
pid = wait3(&status,
nohang ? WNOHANG : 0,
(struct rusage *) NULL);
waiterr = errno;
if (!nohang) {
(void) alarm(0);
}
if (pid <= 0) {
if (waiterr == EINTR) {
if (waitflg) {
update_machine_max();
continue;
} else {
return;
}
} else {
return;
}
}
for (rp = running_list;
rp != NULL && pid != rp->pid;
rp = rp->next);
if (rp == NULL) {
fatal("Internal error: returned child pid not in running_list");
} else {
rp->state = status.w_status ? build_failed : build_ok;
}
nohang = true;
parallel_process_cnt--;
if (rp->host >= 0) {
*(machine_load + rp->host) -= 1;
}
}
}
/*
* update_machine_max()
*
* Checks the load every so often and adjusts the
* maximum allowed processes per processor.
*
* Parameters:
*
* Static variables used:
* first_time Set if this is the first time through
* local Set if we are only building locally
* machines Array of string machine names to run commands
* machine_addr Array of integer host addresses for machines
* machine_load Array of load numbers for each machine
* machine_max Array of maximum processes per each machine
* machine_boot Array of boot times for each machine
* machine_failcnt Array of stat fail count for each machine
* machine_limit Array of process limits for each machine
*
* Static variables used:
* myaddr The host address of this machine
*
* Global variables used:
* next_load_check Time for next check of load averages
* running_list List of running processes
*/
static void
update_machine_max()
{
struct timeval now;
int i;
int load;
int *lp;
int *ml;
int *mm;
int *fp;
int used;
int idle;
char **mp;
u_long *ap;
struct statsswtch stat;
Running rp;
long *bp;
if (first_time) {
/*
* If this is the first time, set up the load averages.
*/
for (mp = machines,
ap = machine_addr,
lp = machine_load,
ml = machine_limit,
mm = machine_max,
bp = machine_boot,
fp = machine_failcnt;
*mp;
mp++, ap++, lp++, ml++, mm++, bp++, fp++) {
if (*ap == myaddr) {
*mm = *ml;
} else {
if (!host_stat(*ap, &stat)) {
/* host may be dead */
if (*fp > 0) {
*fp = (int) max_fails;
}
*mm = -1;
*lp = 0;
continue;
}
if (*mm == -1) {
*mm = 0;
*lp = 0;
continue;
}
if (stat.boottime.tv_sec != *bp) {
*bp = stat.boottime.tv_sec;
*mm = 0;
*lp = 0;
continue;
}
for (i = 0, used = 0; i < CP_IDLE; i++) {
used += stat.cp_time[i];
}
used -= *lp;
idle = stat.cp_time[CP_IDLE] - *mm;
if (used == 0) {
load = 0;
} else if (idle > 0) {
load = (used * 100)/(used + idle);
} else {
load = 100;
}
if (load < 30) {
*mm = *ml;
} else if (load < 80) {
*mm = 1;
} else {
*mm = 0;
}
}
*lp = 0;
}
first_time = false;
(void) gettimeofday(&now, (struct timezone *) NULL);
next_load_check.tv_sec = now.tv_sec + (int) update_delay;
} else {
/*
* If it's time, update the load averages.
*/
(void) gettimeofday(&now, (struct timezone *) NULL);
if (now.tv_sec < next_load_check.tv_sec) {
return;
}
next_load_check.tv_sec = now.tv_sec + (int) update_delay - 1;
for (i = 0,
mp = machines,
ap = machine_addr,
ml = machine_limit,
mm = machine_max,
lp = machine_load,
bp = machine_boot,
fp = machine_failcnt;
*mp;
i++,
mp++,
ap++,
ml++,
mm++,
lp++,
bp++,
fp++) {
long timediff;
if (*ap == myaddr) {
continue;
}
if (!host_stat(*ap, &stat)) {
if (++(*fp) == (int) max_fails) {
warning("No answer from host %s", *mp);
/* Kill all of the process there */
for (rp = running_list;
rp != NULL;
rp = rp->next) {
if (rp->host == i) {
(void) kill(rp->pid,
SIGKILL);
}
}
}
*mm = -1;
continue;
}
if (*mm == -1) {
*mm = 0;
}
*fp = 0;
timediff = stat.boottime.tv_sec - *bp;
if (timediff > 10 || timediff < -10) {
if (*bp != 0) {
warning("Host %s rebooted", *mp);
for (rp = running_list;
rp != NULL;
rp = rp->next) {
if (rp->host == i) {
(void) kill(rp->pid,
SIGKILL);
}
}
}
}
*bp = stat.boottime.tv_sec;
load = (int) stat.avenrun[0];
/*
* If the load is less than 0.50, we assume the
* machine is unloaded and allocate the limit. If the
* load is less than the limit then we add one more.
* If the load is more than the current max, the load
* is too high and we back off one.
*/
if (load < (FSCALE/2)) {
*mm = *ml;
} else if (load < *ml * FSCALE) {
if (*mm < *ml) {
(*mm)++;
}
} else if (load > (*ml * FSCALE)) {
if (*mm > 0) {
(*mm)--;
}
}
}
}
/* Check to make sure at least one machine is usable */
for (mp = machines, mm = machine_max, fp = machine_failcnt;
*mp;
mp++, mm++, fp++) {
if (!(*mm == -1 && *fp >= (int) max_fails)) {
break;
}
}
if (*mp == NULL) {
if (!local) {
warning("No answer from any host on list, using local machine");
local = true;
}
} else if (local) {
warning("Host responded, not running local anymore");
local = false;
}
}
/*
* finish_children(docheck)
*
* Finishes the processing for all targets which were running
* and have now completed.
*
* Parameters:
* docheck Completely check the finished target
*
* Static variables used:
* machines Array of machine names
* myhost The name of this machine
* running_tail The tail of the running list
*
* Global variables used:
* continue_after_error -k flag
* fatal_in_progress True if we are finishing up after fatal err
* running_list List of running processes
*/
void
finish_children(docheck)
Boolean docheck;
{
Running rp;
Running *rp_prev;
Property line;
char *host;
for(rp_prev = &running_list, rp = running_list;
rp != NULL;
rp = rp->next) {
/*
* If the state is ok or failed then this target has finished
* building. Output the accumulated stdout and stderr,
* read the auto dependency stuff, handle a failed build,
* update the target, then finish the doname process for
* that target.
*/
if (rp->state == build_ok || rp->state == build_failed) {
*rp_prev = rp->next;
if (rp->next == NULL) {
running_tail = rp_prev;
}
if (rp->stdout_file != NULL) {
dump_out_file(rp->stdout_file->string, 1);
rp->stdout_file = NULL;
}
if (rp->stderr_file != NULL) {
dump_out_file(rp->stderr_file->string, 2);
rp->stderr_file = NULL;
}
check_state(rp->state, rp->temp_file);
rp->temp_file = NULL;
if (rp->state == build_failed) {
line = get_prop(rp->target->prop, line_prop);
if (line != NULL) {
line->body.line.command_used = NULL;
}
if (rp->host == -1) {
host = myhost;
} else {
host = *(machines + rp->host);
}
if (continue_after_error ||
fatal_in_progress ||
!docheck) {
warning("Command failed for target `%s' (on host %s)",
rp->target->string,
host);
build_failed_seen = true;
} else {
fatal("Command failed for target `%s' (on host %s)",
rp->target->string,
host);
}
}
if (!docheck) {
continue;
}
update_target(get_prop(rp->target->prop, line_prop),
rp->state);
finish_doname(rp);
} else {
rp_prev = &rp->next;
}
}
}
/*
* dump_out_file(filename, out_fd)
*
* Write the contents of the file to output then unlink the file.
*
* Parameters:
* filename Name of temp file containing output
* out_fd File descriptor to write to.
*
* Global variables used:
*/
static void
dump_out_file(filename, out_fd)
char *filename;
int out_fd;
{
int fd;
int cc;
char copybuf[BUFSIZE];
struct stat filestat;
if ((fd = open(filename, O_RDONLY)) < 0) {
fatal("Couldn't open output temporary file: %s",
errmsg(errno));
}
/* the on command puts a final, extra <cr><lf> at the end which */
/* we'll remove */
if (stat(filename, &filestat) < 0) {
fatal("Couldn't stat temporary file: %s", errmsg(errno));
}
if (filestat.st_size > 2) {
if (lseek(fd, (long) (filestat.st_size-3), L_SET) < 0) {
fatal("Failed lseek on temporary file: %s",
errmsg(errno));
}
if (read(fd, copybuf, 3) < 0) {
fatal("Failed read on temporary file: %s",
errmsg(errno));
}
if (copybuf[0] == 012 &&
copybuf[1] == 015 && copybuf[2] == 012) {
if (truncate(filename, filestat.st_size-2) < 0) {
fatal("Couldn't truncate temporary file: %s",
errmsg(errno));
}
}
if (lseek(fd, (long) 0, L_SET) < 0) {
fatal("Failed lseek on temporary file: %s",
errmsg(errno));
}
}
for (cc = read(fd, copybuf, BUFSIZE);
cc > 0;
cc = read(fd, copybuf, BUFSIZE)) {
/*
* read buffers from the source file until end or error.
*/
if (write(out_fd, copybuf, cc) < 0) {
fatal("Write failed to output: %s", errmsg(errno));
}
}
(void) close(fd);
(void) unlink(filename);
}
/*
* finish_doname(rp)
*
* Completes the processing for a target which was left running.
*
* Parameters:
* rp Running list entry for target
*
* Global variables used:
* debug_level Debug flag
* recursion_level Indentation for debug output
*/
static void
finish_doname(rp)
Running rp;
{
Doname result = rp->state;
Name target = rp->target;
int auto_count = rp->auto_count;
Name *automatics = rp->automatics;
Name true_target = rp->true_target;
recursion_level = rp->recursion_level;
if (result == build_ok) {
/* If all went OK set a nice timestamp */
if (true_target->stat.time == (int) file_doesnt_exist) {
true_target->stat.time = (int) file_max_time;
}
}
target->state = result;
if (target->is_member) {
Property member;
/* Propagate the timestamp from the member file to the member*/
if ((target->stat.time != (int) file_max_time) &&
((member = get_prop(target->prop, member_prop)) != NULL)) {
target->stat.time = exists(member->body.member.member);
}
}
/* Check if we found any new auto dependencies when we built */
/* the target */
if ((result == build_ok) && check_auto_dependencies(target,
auto_count,
automatics)) {
if (debug_level > 0) {
(void) printf("%*sTarget `%s' acquired new dependencies from build, checking those\n",
recursion_level,
"",
true_target->string);
}
target->state = build_running;
add_pending(target,
recursion_level,
rp->do_get,
rp->implicit,
true);
}
}
/*
* add_running(target, true_target, recursion_level, auto_count,
* automatics, do_get, implicit)
*
* Adds a record on the running list for this target, which
* was just spawned and is running.
*
* Parameters:
* target Target being built
* true_target True target for target
* recursion_level Debug indentation level
* auto_count Count of automatic dependencies
* automatics List of automatic dependencies
* do_get Sccs get flag
* implicit Implicit flag
*
* Static variables used:
* process_machine Index of machine building target
* running_tail Tail of running list
* process_running PID of process
*
* Global variables used:
* current_line Current line for target
* current_target Current target being built
* stderr_file Temporary file for stdout
* stdout_file Temporary file for stdout
* temp_file_name Temporary file for auto dependencies
*/
void
add_running(target, true_target, recursion_level, auto_count,
automatics, do_get, implicit)
Name target;
Name true_target;
int recursion_level;
int auto_count;
Name *automatics;
Boolean do_get;
Boolean implicit;
{
Running rp;
Name *p;
rp = ALLOC(Running);
rp->state = build_running;
rp->target = target;
rp->true_target = true_target;
rp->recursion_level = recursion_level;
rp->do_get = do_get;
rp->implicit = implicit;
rp->auto_count = auto_count;
if (auto_count > 0) {
rp->automatics = (Name *) getmem(auto_count * sizeof (Name));
for (p = rp->automatics; auto_count > 0; auto_count--) {
*p++ = *automatics++;
}
} else {
rp->automatics = NULL;
}
rp->pid = process_running;
rp->host = process_machine;
rp->stdout_file = stdout_file;
rp->stderr_file = stderr_file;
rp->temp_file = temp_file_name;
rp->redo = false;
rp->next = NULL;
store_conditionals(rp);
process_running = -1;
stdout_file = NULL;
stderr_file = NULL;
temp_file_name = NULL;
current_target = NULL;
current_line = NULL;
*running_tail = rp;
running_tail = &rp->next;
}
/*
* add_pending(target, recursion_level, do_get, implicit, redo)
*
* Adds a record on the running list for a pending target
* (waiting for its dependents to finish running).
*
* Parameters:
* target Target being built
* recursion_level Debug indentation level
* do_get Sccs get flag
* implicit Implicit flag
* redo True if this target is being redone
*
* Static variables used:
* running_tail Tail of running list
*/
void
add_pending(target, recursion_level, do_get, implicit, redo)
Name target;
int recursion_level;
Boolean do_get;
Boolean implicit;
Boolean redo;
{
Running rp;
rp = ALLOC(Running);
rp->state = build_pending;
rp->target = target;
rp->recursion_level = recursion_level;
rp->do_get = do_get;
rp->implicit = implicit;
rp->next = NULL;
rp->stdout_file = NULL;
rp->stderr_file = NULL;
rp->temp_file = NULL;
rp->redo = redo;
store_conditionals(rp);
*running_tail = rp;
running_tail = &rp->next;
}
/*
* add_serial(target, recursion_level, do_get, implicit)
*
* Adds a record on the running list for a target which must be
* executed in serial after others have finished.
*
* Parameters:
* target Target being built
* recursion_level Debug indentation level
* do_get Sccs get flag
* implicit Implicit flag
*
* Static variables used:
* running_tail Tail of running list
*/
void
add_serial(target, recursion_level, do_get, implicit)
Name target;
Boolean do_get;
Boolean implicit;
{
Running rp;
rp = ALLOC(Running);
rp->target = target;
rp->recursion_level = recursion_level;
rp->do_get = do_get;
rp->implicit = implicit;
rp->state = build_serial;
rp->next = NULL;
rp->stdout_file = NULL;
rp->stderr_file = NULL;
rp->temp_file = NULL;
rp->redo = false;
store_conditionals(rp);
*running_tail = rp;
running_tail = &rp->next;
}
/*
* add_subtree(target, recursion_level, do_get, implicit)
*
* Adds a record on the running list for a target which must be
* executed in isolation after others have finished.
*
* Parameters:
* target Target being built
* recursion_level Debug indentation level
* do_get Sccs get flag
* implicit Implicit flag
*
* Static variables used:
* running_tail Tail of running list
*/
void
add_subtree(target, recursion_level, do_get, implicit)
Name target;
Boolean do_get;
Boolean implicit;
{
Running rp;
rp = ALLOC(Running);
rp->target = target;
rp->recursion_level = recursion_level;
rp->do_get = do_get;
rp->implicit = implicit;
rp->state = build_subtree;
rp->next = NULL;
rp->stdout_file = NULL;
rp->stderr_file = NULL;
rp->temp_file = NULL;
rp->redo = false;
store_conditionals(rp);
*running_tail = rp;
running_tail = &rp->next;
}
/*
* store_conditionals(rp)
*
* Creates an array of the currently active targets with conditional
* macros (found in the chain conditional_targets) and puts that
* array in the Running struct.
*
* Parameters:
* rp Running struct for storing chain
*
* Global variables used:
* conditional_targets Chain of current dynamic conditionals
*/
static void
store_conditionals(rp)
Running rp;
{
int cnt;
Chain cond_name;
if (conditional_targets == NULL) {
rp->conditional_cnt = 0;
rp->conditional_targets = NULL;
return;
}
cnt = 0;
for (cond_name = conditional_targets;
cond_name != NULL;
cond_name = cond_name->next) {
cnt++;
}
rp->conditional_cnt = cnt;
rp->conditional_targets = (Name *)getmem(cnt * sizeof(Name));
for (cond_name = conditional_targets;
cond_name != NULL;
cond_name = cond_name->next) {
rp->conditional_targets[--cnt] = cond_name->name;
}
}
/*
* parallel_ok(target)
*
* Returns true if the target can be run in parallel
*
* Return value:
* True if can run in parallel
*
* Parameters:
* target Target being tested
*
* Global variables used:
* all_parallel True if all targets default to parallel
* only_parallel True if no targets default to parallel
*/
Boolean
parallel_ok(target)
Name target;
{
Boolean assign;
Boolean make_refd;
Property line;
Cmd_line rule;
assign = make_refd = false;
if ((line = get_prop(target->prop, line_prop)) == NULL) {
return false;
}
for (rule = line->body.line.command_used;
rule != NULL;
rule = rule->next) {
if (rule->assign) {
assign = true;
} else if (rule->make_refd) {
make_refd = true;
}
}
if (assign) {
return false;
} else if (target->parallel) {
return true;
} else if (target->no_parallel) {
return false;
} else if (all_parallel) {
return true;
} else if (only_parallel) {
return false;
} else if (make_refd) {
return false;
}
return true;
}
/*
* read_make_machines(make_machines)
*
* If this is a parallelizable run, read the MAKE_MACHINES environment
* variable or read the .make.machines file.
* Set up the array of remote machines and machine process loads
*
* Return value:
* True if information found
*
* Parameters:
* make_machines Name of .make.machines file
*
* Static variables used:
* first_time Set to indicate we are starting
* machines Array of machine names read from file
* machine_addr Array of address for the machines
* machine_boot Array of boot times for the machines
* machine_cnt Cound of machines in file
* machine_failcnt Array of fail count for the machines
* machine_limit Array of process limits for the machines
* machine_load Array of load averages for the machines
* machine_max Array of maximum processes for the machines
* myaddr Host address of this machine
* myhost Name of this host
* mymachine Index of this host in array of machines
* parent True if this is the top most make
*
* Global variables used:
* do_not_exec_rule -n flag
* no_parallel -R flag
* quest -q flag
* report_dependencies -P flag
* touch -t flag
*/
Boolean
read_make_machines(make_machines)
Name make_machines;
{
char machines_path[MAXPATHLEN];
Boolean default_make_machines;
char *machines_list;
Boolean parallel_flag = false;
struct stat machines_buf;
FILE *machines_file;
char *machine_string;
char *ms;
char **mp;
char *arg;
int i;
int *ml;
int *lp;
int *mm;
int *fp;
int limit;
Boolean foundeq;
Boolean foundend;
long *bp;
u_long *ap;
struct statsswtch stat;
struct hostent *hp;
Name MAKE_MACHINES;
MAKE_MACHINES = GETNAME("MAKE_MACHINES", FIND_LENGTH);
parent = true;
if (make_machines == NULL) {
(void) sprintf(machines_path,
"%s/.make.machines",
getenv("HOME"));
make_machines = GETNAME(machines_path, FIND_LENGTH);
default_make_machines = true;
} else {
default_make_machines = false;
}
if (no_parallel ||
touch ||
quest ||
do_not_exec_rule ||
report_dependencies_only) {
parallel_flag = false;
} else if ((machines_list =
getenv(MAKE_MACHINES->string)) != NULL) {
parallel_flag = true;
parent = false;
} else if ((machines_file =
fopen(make_machines->string, "r")) != NULL) {
if (fstat(fileno(machines_file), &machines_buf) < 0) {
fatal("fstat of %s failed: %s",
make_machines->string,
errmsg(errno));
}
machines_list = (char *) getmem((int) (machines_buf.st_size +
2 +
MAKE_MACHINES->
hash.length));
(void) sprintf(machines_list,
"%s=",
MAKE_MACHINES->string);
if (fread(machines_list + MAKE_MACHINES->hash.length + 1,
sizeof (char),
(int) machines_buf.st_size,
machines_file) != machines_buf.st_size) {
warning("Unable to read %s",
make_machines->string);
(void) fclose(machines_file);
} else {
(void) fclose(machines_file);
*(machines_list + MAKE_MACHINES->hash.length
+ 1 + machines_buf.st_size) = (int) nul_char;
if (putenv(machines_list) != 0) {
warning("Couldn't put %s in environment",
make_machines->string);
} else {
machines_list +=
MAKE_MACHINES->hash.length + 1;
parallel_flag = true;
}
}
} else if (!default_make_machines) {
fatal("Open of `%s' failed: %s",
make_machines->string,
errmsg(errno));
}
if (!parallel_flag) {
return false;
}
(void) gethostname(myhost, 32);
myhost[32] = (int) nul_char;
hp = gethostbyname(myhost);
bcopy(hp->h_addr, (char *) &myaddr, hp->h_length);
ms = machines_list;
while (*ms && isspace(*ms)) {
ms++;
}
ms = machine_string = strdup(ms);
while (*ms) {
machine_cnt++;
SKIPTOEND(ms);
if (*ms) {
ms++;
}
}
machines = (char **) getmem((machine_cnt + 1) * sizeof (char *));
machine_addr = (u_long *) getmem(machine_cnt * sizeof (u_long));
machine_load = (int *) getmem(machine_cnt * sizeof (int));
machine_limit = (int *) getmem(machine_cnt * sizeof (int));
machine_max = (int *) getmem(machine_cnt * sizeof (int));
machine_boot = (long *) getmem(machine_cnt * sizeof (long));
machine_failcnt = (int *) getmem(machine_cnt * sizeof (int));
for (ms = machine_string, mp = machines, ml = machine_limit,
ap = machine_addr, lp = machine_load, mm = machine_max,
bp = machine_boot, fp = machine_failcnt;
*ms != NULL;
) {
SKIPSPACE(ms);
*mp = ms;
SKIPWORD(ms);
if (*ms && *ms == (int) newline_char) {
foundend = true;
} else {
foundend = false;
}
if (*ms) {
*ms++ = (int) nul_char;
}
if (!foundend) {
SKIPSPACE(ms);
if (*ms && *ms == (int) newline_char) {
ms++;
foundend = true;
}
}
if ((**mp == (int) nul_char) ||
(hp = gethostbyname(*mp)) == NULL) {
if (**mp != (int) nul_char) {
warning("Ignoring unknown host `%s'", *mp);
}
machine_cnt--;
if (!foundend) {
SKIPTOEND(ms);
if (*ms) {
ms++;
}
}
} else {
limit = 2;
while (*ms && !foundend) {
foundeq = false;
arg = ms;
SKIPWORD(ms);
if (*ms && (*ms == (int) equal_char)) {
foundeq = true;
}
if (*ms && (*ms == (int) newline_char)) {
foundend = true;
}
if (*ms) {
*ms++ = (int) nul_char;
}
SKIPSPACE(ms);
if (IS_EQUAL(arg, "max")) {
if (*ms &&
!foundend &&
(*ms == (int) equal_char)) {
foundeq = true;
ms++;
SKIPSPACE(ms);
}
if (foundeq &&
!foundend) {
arg = ms;
SKIPWORD(ms);
if (*ms &&
(*ms == (int) newline_char)) {
foundend = true;
}
if (*ms) {
*ms++ = (int)nul_char;
}
limit =
(int) strtol(arg, &arg, 10);
if (*arg) {
warning("Expected number for max option for host %s",
*mp);
limit = 2;
} else if (limit < 1) {
warning("Max option value must be positive number for host %s",
*mp);
}
if (limit < 1) {
limit = 2;
}
}
}
/* else if ... for each option */
else {
warning("Unknown option: `%s'", arg);
if (*ms &&
!foundend &&
(*ms == (int) equal_char)) {
ms++;
SKIPWORD(ms);
SKIPSPACE(ms);
}
}
if (!foundend) {
SKIPSPACE(ms);
}
if (*ms && (*ms == (int) newline_char)) {
foundend = true;
ms++;
}
}
bcopy(hp->h_addr, (char *) ap, hp->h_length);
if (*ap != myaddr) {
if (!host_stat(*ap, &stat)) {
*fp = 1;
*mm = -1;
*bp = 0;
} else {
for (i = 0, *lp = 0;
i < CP_IDLE;
i++) {
*lp += stat.cp_time[i];
}
*mm = stat.cp_time[CP_IDLE];
*bp = stat.boottime.tv_sec;
*fp = 0;
}
}
*ml = limit;
mp++;
ml++;
ap++;
lp++;
mm++;
bp++;
fp++;
}
}
if (machine_cnt < 1) {
parallel_flag = false;
} else {
*mp = NULL;
mymachine = -1;
for (mp = machines, ap = machine_addr, i = 0;
*mp;
mp++, ap++, i++) {
if (*ap == myaddr) {
mymachine = i;
break;
}
}
first_time = true;
}
return true;
}
/*
* host_stat(addr, statsswtch)
*
* Does an rstat() with a timeout of 5 seconds. False is
* returned on failure.
*
* Return value:
* True if host_stat succeeded
*
* Parameters:
* addr Address of host to stat
* statsswtch Stat struct
*
* Global variables used:
* xdr_statsswtch dwight
*/
static Boolean
host_stat(addr, statsswtch)
u_long addr;
struct statsswtch *statsswtch;
{
CLIENT *client;
struct timeval timeout;
struct sockaddr_in serveradr;
int snum;
enum clnt_stat clnt_stat;
snum = RPC_ANYSOCK;
serveradr.sin_addr.s_addr = addr;
serveradr.sin_family = AF_INET;
serveradr.sin_port = 0;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if ((client = clntudp_bufcreate(&serveradr,
(unsigned long) RSTATPROG,
(unsigned long) RSTATVERS_SWTCH,
timeout,
&snum,
sizeof (struct rpc_msg),
sizeof (struct rpc_msg) +
sizeof (struct statsswtch))) == NULL) {
return false;
}
timeout.tv_sec = 5;
timeout.tv_usec = 0;
clnt_stat = clnt_call(client,
RSTATPROC_STATS,
xdr_void,
0,
xdr_statsswtch,
statsswtch,
timeout);
clnt_destroy(client);
(void) close(snum);
if (clnt_stat != RPC_SUCCESS) {
return false;
}
return true;
}
/*
* is_running(target)
*
* Returns true if the target is running.
*
* Return value:
* True if target is running
*
* Parameters:
* target Target to check
*
* Global variables used:
* running_list List of running processes
*/
Boolean
is_running(target)
Name target;
{
Running rp;
if (target->state != build_running) {
return false;
}
for (rp = running_list;
rp != NULL && target != rp->target;
rp = rp->next);
if (rp == NULL) {
return false;
} else {
return (rp->state == build_running) ? true : false;
}
}
#endif PARALLEL