/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * parallel.cc * * Deal with the parallel processing */ /* * Included files */ #include /* errno */ #include #include #include /* redirect_io() */ #include /* expand_value() */ #include /* getmem() */ #include #include #include #include #include #include #include #include #include #include /* * Defined macros */ #define MAXRULES 100 /* * This const should be in avo_dms/include/AvoDmakeCommand.h */ const int local_host_mask = 0x20; /* * typedefs & structs */ /* * Static variables */ static Boolean just_did_subtree = false; static char local_host[MAXNAMELEN] = ""; static char user_name[MAXNAMELEN] = ""; static int pmake_max_jobs = 0; static pid_t process_running = -1; static Running *running_tail = &running_list; static Name subtree_conflict; static Name subtree_conflict2; /* * File table of contents */ static void delete_running_struct(Running rp); static Boolean dependency_conflict(Name target); static Doname distribute_process(char **commands, Property line); static void doname_subtree(Name target, Boolean do_get, Boolean implicit); static void dump_out_file(char *filename, Boolean err); static void finish_doname(Running rp); static void maybe_reread_make_state(void); static void process_next(void); static void reset_conditionals(int cnt, Name *targets, Property *locals); static pid_t run_rule_commands(char *host, char **commands); static Property *set_conditionals(int cnt, Name *targets); static void store_conditionals(Running rp); /* * execute_parallel(line, waitflg) * * DMake 2.x: * parallel mode: spawns a parallel process to execute the command group. * * Return value: * The result of the execution * * Parameters: * line The command group to execute */ Doname execute_parallel(Property line, Boolean waitflg, Boolean local) { int argcnt; int cmd_options = 0; char *commands[MAXRULES + 5]; char *cp; Name dmake_name; Name dmake_value; int ignore; Name make_machines_name; char **p; Property prop; Doname result = build_ok; Cmd_line rule; Boolean silent_flag; Name target = line->body.line.target; Boolean wrote_state_file = false; if ((pmake_max_jobs == 0) && (dmake_mode_type == parallel_mode)) { if (local_host[0] == '\0') { (void) gethostname(local_host, MAXNAMELEN); } MBSTOWCS(wcs_buffer, "DMAKE_MAX_JOBS"); dmake_name = GETNAME(wcs_buffer, FIND_LENGTH); if (((prop = get_prop(dmake_name->prop, macro_prop)) != NULL) && ((dmake_value = prop->body.macro.value) != NULL)) { pmake_max_jobs = atoi(dmake_value->string_mb); if (pmake_max_jobs <= 0) { warning(gettext("DMAKE_MAX_JOBS cannot be less than or equal to zero.")); warning(gettext("setting DMAKE_MAX_JOBS to %d."), PMAKE_DEF_MAX_JOBS); pmake_max_jobs = PMAKE_DEF_MAX_JOBS; } } else { /* * For backwards compatibility w/ PMake 1.x, when * DMake 2.x is being run in parallel mode, DMake * should parse the PMake startup file * $(HOME)/.make.machines to get the pmake_max_jobs. */ MBSTOWCS(wcs_buffer, "PMAKE_MACHINESFILE"); dmake_name = GETNAME(wcs_buffer, FIND_LENGTH); if (((prop = get_prop(dmake_name->prop, macro_prop)) != NULL) && ((dmake_value = prop->body.macro.value) != NULL)) { make_machines_name = dmake_value; } else { make_machines_name = NULL; } if ((pmake_max_jobs = read_make_machines(make_machines_name)) <= 0) { pmake_max_jobs = PMAKE_DEF_MAX_JOBS; } } } if ((dmake_mode_type == serial_mode) || ((dmake_mode_type == parallel_mode) && (waitflg))) { return (execute_serial(line)); } { p = commands; } argcnt = 0; for (rule = line->body.line.command_used; rule != NULL; rule = rule->next) { if (posix && (touch || quest) && !rule->always_exec) { continue; } 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) { return build_serial; } { if (rule->silent && !silent) { silent_flag = true; } if (rule->ignore_error) { ignore++; } /* XXX - need to add support for + prefix */ if (silent_flag || ignore) { *p = getmem((silent_flag ? 1 : 0) + ignore + (strlen(rule-> command_line-> string_mb)) + 1); cp = *p++; if (silent_flag) { *cp++ = (int) at_char; } if (ignore) { *cp++ = (int) hyphen_char; } (void) strcpy(cp, rule->command_line->string_mb); } else { *p++ = rule->command_line->string_mb; } } } } if ((argcnt == 0) || (report_dependencies_level > 0)) { return build_ok; } { *p = NULL; Doname res = distribute_process(commands, line); if (res == build_running) { parallel_process_cnt++; } /* * Return only those memory that were specially allocated * for part of commands. */ for (int i = 0; commands[i] != NULL; i++) { if ((commands[i][0] == (int) at_char) || (commands[i][0] == (int) hyphen_char)) { retmem_mb(commands[i]); } } return res; } } #include /* sysconf(_SC_NPROCESSORS_ONLN) */ #include /* ftok() */ #include /* shmget(), shmat(), shmdt(), shmctl() */ #include /* sem_init(), sem_trywait(), sem_post(), sem_destroy() */ #ifdef __sun #include /* getloadavg() */ #else #include // getloadavg() #define LOADAVG_1MIN 0 #endif /* * adjust_pmake_max_jobs (int pmake_max_jobs) * * Parameters: * pmake_max_jobs - max jobs limit set by user * * External functions used: * sysconf() * getloadavg() */ static int adjust_pmake_max_jobs (int pmake_max_jobs) { static int ncpu = 0; double loadavg[3]; int adjustment; int adjusted_max_jobs; if (ncpu <= 0) { if ((ncpu = sysconf(_SC_NPROCESSORS_ONLN)) <= 0) { ncpu = 1; } } if (getloadavg(loadavg, 3) != 3) return(pmake_max_jobs); adjustment = ((int)loadavg[LOADAVG_1MIN]); if (adjustment < 2) return(pmake_max_jobs); if (ncpu > 1) { adjustment = adjustment / ncpu; } adjusted_max_jobs = pmake_max_jobs - adjustment; if (adjusted_max_jobs < 1) adjusted_max_jobs = 1; return(adjusted_max_jobs); } /* * M2 adjust mode data and functions * * m2_init() - initializes M2 shared semaphore * m2_acquire_job() - decrements M2 semaphore counter * m2_release_job() - increments M2 semaphore counter * m2_fini() - destroys M2 semaphore and shared memory* * * Environment variables: * __DMAKE_M2_FILE__ * * External functions: * ftok(), shmget(), shmat(), shmdt(), shmctl() * sem_init(), sem_trywait(), sem_post(), sem_destroy() * creat(), close(), unlink() * getenv(), putenv() * * Static variables: * m2_file - tmp file name to create ipc key for shared memory * m2_shm_id - shared memory id * m2_shm_sem - shared memory semaphore */ static char m2_file[MAXPATHLEN]; static int m2_shm_id = -1; static sem_t* m2_shm_sem = 0; static int m2_init() { char *var; key_t key; if ((var = getenv("__DMAKE_M2_FILE__")) == 0) { /* compose tmp file name */ sprintf(m2_file, "%s/dmake.m2.%d.XXXXXX", tmpdir, getpid()); /* create tmp file */ int fd = mkstemp(m2_file); if (fd < 0) { return -1; } else { close(fd); } } else { /* using existing semaphore */ strcpy(m2_file, var); } /* combine IPC key */ if ((key = ftok(m2_file, 38)) == (key_t) -1) { return -1; } /* create shared memory */ if ((m2_shm_id = shmget(key, sizeof(*m2_shm_sem), 0666 | (var ? 0 : IPC_CREAT|IPC_EXCL))) == -1) { return -1; } /* attach shared memory */ if ((m2_shm_sem = (sem_t*) shmat(m2_shm_id, 0, 0666)) == (sem_t*)-1) { return -1; } /* root process */ if (var == 0) { /* initialize semaphore */ if (sem_init(m2_shm_sem, 1, pmake_max_jobs)) { return -1; } /* alloc memory for env variable */ if ((var = (char*) malloc(MAXPATHLEN)) == 0) { return -1; } /* put key to env */ sprintf(var, "__DMAKE_M2_FILE__=%s", m2_file); if (putenv(var)) { return -1; } } return 0; } static void m2_fini() { if (m2_shm_id >= 0) { struct shmid_ds stat; /* determine the number of attached processes */ if (shmctl(m2_shm_id, IPC_STAT, &stat) == 0) { if (stat.shm_nattch <= 1) { /* destroy semaphore */ if (m2_shm_sem != 0) { (void) sem_destroy(m2_shm_sem); } /* destroy shared memory */ (void) shmctl(m2_shm_id, IPC_RMID, &stat); /* remove tmp file created for the key */ (void) unlink(m2_file); } else { /* detach shared memory */ if (m2_shm_sem != 0) { (void) shmdt((char*) m2_shm_sem); } } } m2_shm_id = -1; m2_shm_sem = 0; } } static int m2_acquire_job() { if ((m2_shm_id >= 0) && (m2_shm_sem != 0)) { if (sem_trywait(m2_shm_sem) == 0) { return 1; } if (errno == EAGAIN) { return 0; } } return -1; } static int m2_release_job() { if ((m2_shm_id >= 0) && (m2_shm_sem != 0)) { if (sem_post(m2_shm_sem) == 0) { return 0; } } return -1; } /* * job adjust mode * * Possible values: * ADJUST_M1 - adjustment by system load (default) * ADJUST_M2 - fixed limit of jobs for the group of nested dmakes * ADJUST_NONE - no adjustment - fixed limit of jobs for the current dmake */ static enum { ADJUST_UNKNOWN, ADJUST_M1, ADJUST_M2, ADJUST_NONE } job_adjust_mode = ADJUST_UNKNOWN; /* * void job_adjust_fini() * * Description: * Cleans up job adjust data. * * Static variables: * job_adjust_mode Current job adjust mode */ void job_adjust_fini() { if (job_adjust_mode == ADJUST_M2) { m2_fini(); } } /* * void job_adjust_error() * * Description: * Prints warning message, cleans up job adjust data, and disables job adjustment * * Environment: * DMAKE_ADJUST_MAX_JOBS * * External functions: * putenv() * * Static variables: * job_adjust_mode Current job adjust mode */ static void job_adjust_error() { if (job_adjust_mode != ADJUST_NONE) { /* cleanup internals */ job_adjust_fini(); /* warning message for the user */ warning(gettext("Encountered max jobs auto adjustment error - disabling auto adjustment.")); /* switch off job adjustment for the children */ putenv(strdup("DMAKE_ADJUST_MAX_JOBS=NO")); /* and for this dmake */ job_adjust_mode = ADJUST_NONE; } } /* * void job_adjust_init() * * Description: * Parses DMAKE_ADJUST_MAX_JOBS env variable * and performs appropriate initializations. * * Environment: * DMAKE_ADJUST_MAX_JOBS * DMAKE_ADJUST_MAX_JOBS == "NO" - no adjustment * DMAKE_ADJUST_MAX_JOBS == "M2" - M2 adjust mode * other - M1 adjust mode * * External functions: * getenv() * * Static variables: * job_adjust_mode Current job adjust mode */ static void job_adjust_init() { if (job_adjust_mode == ADJUST_UNKNOWN) { /* default mode */ job_adjust_mode = ADJUST_M1; /* determine adjust mode */ if (char *var = getenv("DMAKE_ADJUST_MAX_JOBS")) { if (strcasecmp(var, "NO") == 0) { job_adjust_mode = ADJUST_NONE; } else if (strcasecmp(var, "M2") == 0) { job_adjust_mode = ADJUST_M2; } } /* M2 specific initialization */ if (job_adjust_mode == ADJUST_M2) { if (m2_init()) { job_adjust_error(); } } } } /* * distribute_process(char **commands, Property line) * * Parameters: * commands argv vector of commands to execute * * Return value: * The result of the execution * * Static variables used: * process_running Set to the pid of the process set running * job_adjust_mode Current job adjust mode */ static Doname distribute_process(char **commands, Property line) { static unsigned file_number = 0; wchar_t string[MAXPATHLEN]; char mbstring[MAXPATHLEN]; int filed; int res; int tmp_index; char *tmp_index_str_ptr; /* initialize adjust mode, if not initialized */ if (job_adjust_mode == ADJUST_UNKNOWN) { job_adjust_init(); } /* actions depend on adjust mode */ switch (job_adjust_mode) { case ADJUST_M1: while (parallel_process_cnt >= adjust_pmake_max_jobs (pmake_max_jobs)) { await_parallel(false); finish_children(true); } break; case ADJUST_M2: if ((res = m2_acquire_job()) == 0) { if (parallel_process_cnt > 0) { await_parallel(false); finish_children(true); if ((res = m2_acquire_job()) == 0) { return build_serial; } } else { return build_serial; } } if (res < 0) { /* job adjustment error */ job_adjust_error(); /* no adjustment */ while (parallel_process_cnt >= pmake_max_jobs) { await_parallel(false); finish_children(true); } } break; default: while (parallel_process_cnt >= pmake_max_jobs) { await_parallel(false); finish_children(true); } } setvar_envvar(); /* * Tell the user what DMake is doing. */ if (!silent && output_mode != txt2_mode) { /* * Print local_host --> x job(s). */ (void) fprintf(stdout, gettext("%s --> %d %s\n"), local_host, parallel_process_cnt + 1, (parallel_process_cnt == 0) ? gettext("job") : gettext("jobs")); /* Print command line(s). */ tmp_index = 0; while (commands[tmp_index] != NULL) { /* No @ char. */ /* XXX - need to add [2] when + prefix is added */ if ((commands[tmp_index][0] != (int) at_char) && (commands[tmp_index][1] != (int) at_char)) { tmp_index_str_ptr = commands[tmp_index]; if (*tmp_index_str_ptr == (int) hyphen_char) { tmp_index_str_ptr++; } (void) fprintf(stdout, "%s\n", tmp_index_str_ptr); } tmp_index++; } (void) fflush(stdout); } (void) sprintf(mbstring, "%s/dmake.stdout.%d.%d.XXXXXX", tmpdir, getpid(), file_number++); mktemp(mbstring); stdout_file = strdup(mbstring); stderr_file = NULL; if (!out_err_same) { (void) sprintf(mbstring, "%s/dmake.stderr.%d.%d.XXXXXX", tmpdir, getpid(), file_number++); mktemp(mbstring); stderr_file = strdup(mbstring); } process_running = run_rule_commands(local_host, commands); return build_running; } /* * 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 */ Doname doname_parallel(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 (Doname) 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(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(void) { while (running_list != NULL) { { await_parallel(false); 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 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(void) { 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. */ start_loop_1: 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; (void) doname_check(rp->target, rp->do_get, rp->implicit, false); quiescent = false; delete_running_struct(rp); goto start_loop_1; } 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. */ start_loop_2: 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); rp->target->dont_activate_cond_values = true; if ((doname_check(rp->target, rp->do_get, rp->implicit, rp->target->has_target_prop ? true : false) != build_running) && !commands_done) { commands_done = saved_commands_done; } rp->target->dont_activate_cond_values = false; reset_conditionals (rp->conditional_cnt, rp->conditional_targets, conditionals); quiescent = false; delete_running_struct(rp); goto start_loop_2; } 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) { start_loop_3: 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; doname_subtree(rp->target, rp->do_get, rp->implicit); quiescent = false; delete_running_struct(rp); goto start_loop_3; } 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(gettext("Internal error: deadlock detected in process_next")); } else { rp = *subtree_target; if (debug_level > 0) { warning(gettext("Conditional macro conflict encountered for %s between %s and %s"), subtree_conflict2->string_mb, rp->target->string_mb, subtree_conflict->string_mb); } *subtree_target = (*subtree_target)->next; if (rp->next == NULL) { running_tail = subtree_target; } recursion_level = rp->recursion_level; doname_subtree(rp->target, rp->do_get, rp->implicit); delete_running_struct(rp); } } } /* * 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(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(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_mb((caddr_t) *lp); } retmem_mb((caddr_t) 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(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 */ void await_parallel(Boolean waitflg) { Boolean nohang; pid_t pid; int status; Running rp; int waiterr; nohang = false; for ( ; ; ) { if (!nohang) { (void) alarm((int) update_delay); } pid = waitpid((pid_t)-1, &status, nohang ? WNOHANG : 0); waiterr = errno; if (!nohang) { (void) alarm(0); } if (pid <= 0) { if (waiterr == EINTR) { if (waitflg) { continue; } else { return; } } else { return; } } for (rp = running_list; (rp != NULL) && (rp->pid != pid); rp = rp->next) { ; } if (rp == NULL) { fatal(gettext("Internal error: returned child pid not in running_list")); } else { rp->state = (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? build_ok : build_failed; } nohang = true; parallel_process_cnt--; if (job_adjust_mode == ADJUST_M2) { if (m2_release_job()) { job_adjust_error(); } } } } /* * 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: * 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(Boolean docheck) { int cmds_length; Property line; Property line2; struct stat out_buf; Running rp; Running *rp_prev; Cmd_line rule; Boolean silent_flag; for (rp_prev = &running_list, rp = running_list; rp != NULL; rp = rp->next) { bypass_for_loop_inc_4: /* * If the state is ok or failed, then this target has * finished building. * In parallel_mode, output the accumulated stdout/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 ((line2 = rp->command) == NULL) { line2 = get_prop(rp->target->prop, line_prop); } /* * Check if there were any job output * from the parallel build. */ if (rp->stdout_file != NULL) { if (stat(rp->stdout_file, &out_buf) < 0) { fatal(gettext("stat of %s failed: %s"), rp->stdout_file, errmsg(errno)); } if ((line2 != NULL) && (out_buf.st_size > 0)) { cmds_length = 0; for (rule = line2->body.line.command_used, silent_flag = silent; rule != NULL; rule = rule->next) { cmds_length += rule->command_line->hash.length + 1; silent_flag = BOOLEAN(silent_flag || rule->silent); } if (out_buf.st_size != cmds_length || silent_flag || output_mode == txt2_mode) { dump_out_file(rp->stdout_file, false); } } (void) unlink(rp->stdout_file); retmem_mb(rp->stdout_file); rp->stdout_file = NULL; } if (!out_err_same && (rp->stderr_file != NULL)) { if (stat(rp->stderr_file, &out_buf) < 0) { fatal(gettext("stat of %s failed: %s"), rp->stderr_file, errmsg(errno)); } if ((line2 != NULL) && (out_buf.st_size > 0)) { dump_out_file(rp->stderr_file, true); } (void) unlink(rp->stderr_file); retmem_mb(rp->stderr_file); rp->stderr_file = NULL; } check_state(rp->temp_file); if (rp->temp_file != NULL) { free_name(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 (continue_after_error || fatal_in_progress || !docheck) { warning(gettext("Command failed for target `%s'"), rp->command ? line2->body.line.target->string_mb : rp->target->string_mb); build_failed_seen = true; } else { /* * XXX??? - DMake needs to exit(), * but shouldn't call fatal(). */ #ifdef PRINT_EXIT_STATUS warning("I'm in finish_children. rp->state == build_failed."); #endif fatal(gettext("Command failed for target `%s'"), rp->command ? line2->body.line.target->string_mb : rp->target->string_mb); } } if (!docheck) { delete_running_struct(rp); rp = *rp_prev; if (rp == NULL) { break; } else { goto bypass_for_loop_inc_4; } } update_target(get_prop(rp->target->prop, line_prop), rp->state); finish_doname(rp); delete_running_struct(rp); rp = *rp_prev; if (rp == NULL) { break; } else { goto bypass_for_loop_inc_4; } } else { rp_prev = &rp->next; } } } /* * dump_out_file(filename, err) * * Write the contents of the file to stdout, then unlink the file. * * Parameters: * filename Name of temp file containing output * * Global variables used: */ static void dump_out_file(char *filename, Boolean err) { int chars_read; char copybuf[BUFSIZ]; int fd; int out_fd = (err ? 2 : 1); if ((fd = open(filename, O_RDONLY)) < 0) { fatal(gettext("open failed for output file %s: %s"), filename, errmsg(errno)); } if (!silent && output_mode != txt2_mode) { (void) fprintf(err ? stderr : stdout, err ? gettext("%s --> Job errors\n") : gettext("%s --> Job output\n"), local_host); (void) fflush(err ? stderr : stdout); } for (chars_read = read(fd, copybuf, BUFSIZ); chars_read > 0; chars_read = read(fd, copybuf, BUFSIZ)) { /* * Read buffers from the source file until end or error. */ if (write(out_fd, copybuf, chars_read) < 0) { fatal(gettext("write failed for output file %s: %s"), filename, 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(Running rp) { int auto_count = rp->auto_count; Name *automatics = rp->automatics; Doname result = rp->state; Name target = rp->target; Name true_target = rp->true_target; Property *conditionals; recursion_level = rp->recursion_level; if (result == build_ok) { if (true_target == NULL) { (void) printf("Target = %s\n", target->string_mb); (void) printf(" State = %d\n", result); fatal("Internal error: NULL true_target in finish_doname"); } /* If all went OK, set a nice timestamp */ if (true_target->stat.time == file_doesnt_exist) { true_target->stat.time = 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 != file_max_time) && ((member = get_prop(target->prop, member_prop)) != NULL) && (exists(member->body.member.member) > file_doesnt_exist)) { target->stat.time = /* exists(member->body.member.member); */ member->body.member.member->stat.time; } } /* * 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(gettext("%*sTarget `%s' acquired new dependencies from build, checking all dependencies\n"), recursion_level, "", true_target->string_mb); } target->rechecking_target = true; target->state = build_running; /* [tolik, Tue Mar 25 1997] * Fix for bug 4038824: * command line options set by conditional macros get dropped * rp->conditional_cnt and rp->conditional_targets must be copied * to new 'rp' during add_pending(). Set_conditionals() stores * rp->conditional_targets to the global variable 'conditional_targets' * Add_pending() will use this variable to set up 'rp'. */ conditionals = set_conditionals(rp->conditional_cnt, rp->conditional_targets); add_pending(target, recursion_level, rp->do_get, rp->implicit, true); reset_conditionals(rp->conditional_cnt, rp->conditional_targets, conditionals); } } /* * new_running_struct() * * Constructor for Running struct. Creates a structure and initializes * its fields. * */ static Running new_running_struct() { Running rp; rp = ALLOC(Running); rp->target = NULL; rp->true_target = NULL; rp->command = NULL; rp->sprodep_value = NULL; rp->sprodep_env = NULL; rp->auto_count = 0; rp->automatics = NULL; rp->pid = -1; rp->job_msg_id = -1; rp->stdout_file = NULL; rp->stderr_file = NULL; rp->temp_file = NULL; rp->next = NULL; return rp; } /* * add_running(target, true_target, command, 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 * command Running command. * 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: * 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(Name target, Name true_target, Property command, int recursion_level, int auto_count, Name *automatics, Boolean do_get, Boolean implicit) { Running rp; Name *p; rp = new_running_struct(); rp->state = build_running; rp->target = target; rp->true_target = true_target; rp->command = command; 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; process_running = -1; childPid = -1; } rp->job_msg_id = job_msg_id; 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); 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(Name target, int recursion_level, Boolean do_get, Boolean implicit, Boolean redo) { Running rp; rp = new_running_struct(); rp->state = build_pending; rp->target = target; rp->recursion_level = recursion_level; rp->do_get = do_get; rp->implicit = implicit; 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(Name target, int recursion_level, Boolean do_get, Boolean implicit) { Running rp; rp = new_running_struct(); rp->target = target; rp->recursion_level = recursion_level; rp->do_get = do_get; rp->implicit = implicit; rp->state = build_serial; 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(Name target, int recursion_level, Boolean do_get, Boolean implicit) { Running rp; rp = new_running_struct(); rp->target = target; rp->recursion_level = recursion_level; rp->do_get = do_get; rp->implicit = implicit; rp->state = build_subtree; 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(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, line_prop_must_exists) * * 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(Name target, Boolean line_prop_must_exists) { Boolean assign; Boolean make_refd; Property line; Cmd_line rule; assign = make_refd = false; if (((line = get_prop(target->prop, line_prop)) == NULL) && line_prop_must_exists) { return false; } if (line != NULL) { 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; } else { 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(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; } } /* * This function replaces the makesh binary. */ static pid_t run_rule_commands(char *host, char **commands) { Boolean always_exec; Name command; Boolean ignore; int length; Doname result; Boolean silent_flag; wchar_t *tmp_wcs_buffer; childPid = fork(); switch (childPid) { case -1: /* Error */ fatal(gettext("Could not fork child process for dmake job: %s"), errmsg(errno)); break; case 0: /* Child */ /* To control the processed targets list is not the child's business */ running_list = NULL; if(out_err_same) { redirect_io(stdout_file, (char*)NULL); } else { redirect_io(stdout_file, stderr_file); } for (commands = commands; (*commands != (char *)NULL); commands++) { silent_flag = silent; ignore = false; always_exec = false; while ((**commands == (int) at_char) || (**commands == (int) hyphen_char) || (**commands == (int) plus_char)) { if (**commands == (int) at_char) { silent_flag = true; } if (**commands == (int) hyphen_char) { ignore = true; } if (**commands == (int) plus_char) { always_exec = true; } (*commands)++; } if ((length = strlen(*commands)) >= MAXPATHLEN) { tmp_wcs_buffer = ALLOC_WC(length + 1); (void) mbstowcs(tmp_wcs_buffer, *commands, length + 1); command = GETNAME(tmp_wcs_buffer, FIND_LENGTH); retmem(tmp_wcs_buffer); } else { MBSTOWCS(wcs_buffer, *commands); command = GETNAME(wcs_buffer, FIND_LENGTH); } if ((command->hash.length > 0) && !silent_flag) { (void) printf("%s\n", command->string_mb); } result = dosys(command, ignore, false, false, /* bugs #4085164 & #4990057 */ /* BOOLEAN(silent_flag && ignore), */ always_exec, (Name) NULL); if (result == build_failed) { if (silent_flag) { (void) printf(gettext("The following command caused the error:\n%s\n"), command->string_mb); } if (!ignore) { _exit(1); } } } _exit(0); break; default: break; } return childPid; } static void maybe_reread_make_state(void) { /* Copying dosys()... */ if (report_dependencies_level == 0) { make_state->stat.time = file_no_time; (void) exists(make_state); if (make_state_before == make_state->stat.time) { return; } makefile_type = reading_statefile; if (read_trace_level > 1) { trace_reader = true; } temp_file_number++; (void) read_simple_file(make_state, false, false, false, false, false, true); trace_reader = false; } } static void delete_running_struct(Running rp) { if ((rp->conditional_cnt > 0) && (rp->conditional_targets != NULL)) { retmem_mb((char *) rp->conditional_targets); } /**/ if ((rp->auto_count > 0) && (rp->automatics != NULL)) { retmem_mb((char *) rp->automatics); } /**/ if(rp->sprodep_value) { free_name(rp->sprodep_value); } if(rp->sprodep_env) { retmem_mb(rp->sprodep_env); } retmem_mb((char *) rp); }