master xplshn/aruu / cmd / dev / ninqu / exec.c
  1#include "ninqu.h"
  2#include "wexec.h"
  3
  4#include <errno.h>
  5#include <libgen.h>
  6#include <limits.h>
  7#include <stdio.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <sys/wait.h>
 11#include <unistd.h>
 12
 13int jobs_n = 1;
 14int summary_mode;
 15int failed_any;
 16
 17/* child _exit on error so parent never sees a stale pid */
 18pid_t
 19spawn_inst(struct Inst *inst, struct StrList *argv)
 20{
 21  pid_t pid = fork();
 22  if (pid < 0)
 23    eprintf("fork:");
 24  if (pid != 0)
 25    return pid;
 26
 27  if (inst->workdir[0] && chdir(inst->workdir) != 0)
 28    eprintf("chdir %s:", inst->workdir);
 29  if (inst->redirect) {
 30    FILE *f = fopen(inst->out, "w");
 31    if (!f)
 32      eprintf("open %s:", inst->out);
 33    if (dup2(fileno(f), STDOUT_FILENO) < 0)
 34      _exit(127);
 35    fclose(f);
 36  }
 37  child_execvp(sl_argv(argv));
 38}
 39
 40/* parent waits on supervisor so pipeline has one exit status */
 41pid_t
 42spawn_pipe(struct Inst *inst)
 43{
 44  pid_t pid = fork();
 45  if (pid < 0)
 46    eprintf("fork:");
 47  if (pid != 0)
 48    return pid;
 49
 50  if (inst->workdir[0] && chdir(inst->workdir) != 0)
 51    eprintf("chdir %s:", inst->workdir);
 52
 53  {
 54    int k, prev_read = -1;
 55    for (k = 0; k < inst->cmd.nstages; k++) {
 56      int   p[2] = {-1, -1};
 57      pid_t sp;
 58      if (k < inst->cmd.nstages - 1) {
 59        if (pipe(p) != 0)
 60          _exit(127);
 61      }
 62      sp = fork();
 63      if (sp < 0)
 64        _exit(127);
 65      if (sp == 0) {
 66        if (prev_read >= 0) {
 67          if (dup2(prev_read, STDIN_FILENO) < 0)
 68            _exit(127);
 69          close(prev_read);
 70        }
 71        if (k < inst->cmd.nstages - 1) {
 72          close(p[0]);
 73          if (dup2(p[1], STDOUT_FILENO) < 0)
 74            _exit(127);
 75          close(p[1]);
 76        }
 77        child_execvp(sl_argv(inst->cmd.stages[k]));
 78      }
 79      if (prev_read >= 0)
 80        close(prev_read);
 81      if (k < inst->cmd.nstages - 1) {
 82        close(p[1]);
 83        prev_read = p[0];
 84      }
 85    }
 86    for (k = 0; k < inst->cmd.nstages; k++)
 87      wait(NULL);
 88    _exit(0);
 89  }
 90}
 91
 92/* mkdir -p the directory an instance writes into, so a rule (out) can point at a path that does not
 93 * exist yet */
 94void
 95mk_out_dir(const char *out)
 96{
 97  char  tmp[PATH_MAX];
 98  char *dir;
 99
100  if (!out || !out[0])
101    return;
102  estrlcpy(tmp, out, sizeof tmp);
103  dir = dirname(tmp);
104  if (strcmp(dir, ".") == 0 || strcmp(dir, "/") == 0)
105    return;
106  mkdirp(dir, 0777, 0777);
107}
108
109/* flush before child runs so announcement appears before its output */
110void
111announce_inst(struct Inst *inst)
112{
113  mk_out_dir(inst->out);
114  printf("  %-10s %s\n", rules[inst->rule_idx].name, inst->out[0] ? inst->out : "(phony)");
115  fflush(stdout);
116}
117
118/* exits on failure since a missing producer is unrecoverable */
119void
120run_inst_sync(int idx)
121{
122  struct Inst *inst = &insts[idx];
123  pid_t        pid;
124  int          status;
125
126  if (!inst_stale(inst))
127    return;
128  announce_inst(inst);
129  pid = inst->cmd.is_pipe ? spawn_pipe(inst) : spawn_inst(inst, &inst->cmd.argv);
130  if (waitpid(pid, &status, 0) < 0)
131    eprintf("waitpid:");
132  if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0))
133    eprintf("manifest: producer failed (rule %s)\n", rules[inst->rule_idx].name);
134}
135
136/* all instances in the batch are at the same kahn level */
137void
138run_batch(int *idxs, int n)
139{
140  int    next = 0, running = 0;
141  pid_t *pids = emalloc((size_t)(n > 0 ? n : 1) * sizeof *pids);
142  int   *slot = emalloc((size_t)(n > 0 ? n : 1) * sizeof *slot);
143
144  while (next < n || running > 0) {
145    while (running < jobs_n && next < n) {
146      struct Inst *inst = &insts[idxs[next]];
147      pid_t        pid;
148
149      if (!inst_stale(inst)) {
150        next++;
151        continue;
152      }
153      announce_inst(inst);
154
155      pid           = inst->cmd.is_pipe ? spawn_pipe(inst) : spawn_inst(inst, &inst->cmd.argv);
156      pids[running] = pid;
157      slot[running] = idxs[next];
158      running++;
159      next++;
160    }
161    if (running > 0) {
162      int   status;
163      pid_t done = wait(&status);
164      int   i;
165      if (done < 0)
166        eprintf("wait:");
167      for (i = 0; i < running; i++) {
168        if (pids[i] == done) {
169          if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
170            char *cmd = sl_join(&insts[slot[i]].cmd.argv);
171            weprintf("rule %s failed: %s\n", rules[insts[slot[i]].rule_idx].name, cmd);
172            free(cmd);
173            failed_any = 1;
174          }
175          pids[i] = pids[running - 1];
176          slot[i] = slot[running - 1];
177          running--;
178          break;
179        }
180      }
181    }
182  }
183  free(pids);
184  free(slot);
185}
186
187/* run transitive deps in order so a producer does not archive uncompiled files */
188void
189run_inst_with_deps(int idx)
190{
191  struct Inst *inst = &insts[idx];
192  int          i;
193
194  for (i = 0; i < inst->dep_rule_names.n; i++) {
195    struct Rule *dep = rule_find(inst->dep_rule_names.v[i]);
196    int          k;
197    if (!dep)
198      continue;
199    for (k = 0; k < dep->n_inst; k++)
200      dep_push(inst, dep->inst_idx[k]);
201  }
202  for (i = 0; i < inst->n_dep; i++)
203    run_inst_with_deps(inst->dep_inst[i]);
204  run_inst_sync(idx);
205}
206
207/* run producer for path only if path does not exist */
208void
209materialize_if_missing(const char *path)
210{
211  struct Rule *r;
212
213  if (file_exists(path))
214    return;
215  r = rule_find_output(path);
216  if (!r)
217    r = rule_find_produces(base_of(path));
218  if (!r)
219    return;
220  expand_rule(r->name);
221  if (r->n_inst > 0) {
222    int k;
223    for (k = 0; k < r->n_inst; k++)
224      run_inst_with_deps(r->inst_idx[k]);
225  }
226}
227
228/* used by (set NAME (capture (exec ...))) at parse time */
229char *
230capture_argv(struct StrList *argv)
231{
232  int    fds[2];
233  pid_t  pid;
234  char  *buf;
235  size_t cap, len;
236  int    status;
237
238  if (argv->n == 0)
239    eprintf("capture: empty argv\n");
240  if (pipe(fds) != 0)
241    eprintf("capture: pipe:");
242
243  pid = fork();
244  if (pid < 0)
245    eprintf("capture: fork:");
246  if (pid == 0) {
247    close(fds[0]);
248    if (dup2(fds[1], STDOUT_FILENO) < 0)
249      _exit(127);
250    close(fds[1]);
251    child_execvp(sl_argv(argv));
252  }
253  close(fds[1]);
254
255  cap = 4096;
256  len = 0;
257  buf = emalloc(cap);
258  for (;;) {
259    ssize_t n;
260    if (len + 4096 > cap) {
261      cap *= 2;
262      buf = erealloc(buf, cap);
263    }
264    n = read(fds[0], buf + len, cap - len - 1);
265    if (n < 0) {
266      if (errno == EINTR)
267        continue;
268      eprintf("capture: read:");
269    }
270    if (n == 0)
271      break;
272    len += (size_t)n;
273  }
274  buf[len] = '\0';
275  close(fds[0]);
276
277  while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
278    buf[--len] = '\0';
279
280  if (waitpid(pid, &status, 0) < 0)
281    eprintf("capture: waitpid:");
282  if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0))
283    eprintf("capture: command failed\n");
284  return buf;
285}