master xplshn/aruu / shared / libutil / wexec.c
  1/* see LICENSE file for copyright and license details */
  2#include "../wexec.h"
  3#include "../config.h"
  4#include "../paths.h"
  5#include "../util.h"
  6
  7#include <errno.h>
  8#include <setjmp.h>
  9#include <signal.h>
 10#include <stdlib.h>
 11#include <string.h>
 12#include <sys/stat.h>
 13#include <sys/wait.h>
 14#include <unistd.h>
 15
 16#define MAX_BUILTINS 256
 17#define MAX_POPEN    32
 18
 19struct builtin {
 20  const char *name;
 21  wexec_fn    fn;
 22};
 23
 24static struct builtin builtins[MAX_BUILTINS];
 25static int            nbuiltins;
 26
 27struct popen_entry {
 28  FILE *fp;
 29  pid_t pid;
 30};
 31
 32static struct popen_entry popen_tab[MAX_POPEN];
 33static int                npopen;
 34
 35static char *(*wexec_env_lookup)(const char *name) = NULL;
 36
 37void
 38wexec_register_env_lookup(char *(*fn)(const char *name))
 39{
 40  wexec_env_lookup = fn;
 41}
 42
 43static const char *
 44wexec_getenv(const char *name)
 45{
 46  if (wexec_env_lookup)
 47    return wexec_env_lookup(name);
 48  return getenv(name);
 49}
 50
 51int
 52wpopen_track(FILE *fp, pid_t pid)
 53{
 54  if (npopen >= MAX_POPEN)
 55    return -1;
 56  popen_tab[npopen].fp  = fp;
 57  popen_tab[npopen].pid = pid;
 58  npopen++;
 59  return 0;
 60}
 61
 62pid_t
 63wpopen_untrack(FILE *fp)
 64{
 65  int   i;
 66  pid_t pid = -1;
 67  for (i = 0; i < npopen; i++) {
 68    if (popen_tab[i].fp == fp) {
 69      pid          = popen_tab[i].pid;
 70      popen_tab[i] = popen_tab[npopen - 1];
 71      npopen--;
 72      break;
 73    }
 74  }
 75  return pid;
 76}
 77
 78int
 79wexec_register(const char *name, wexec_fn fn)
 80{
 81  int i;
 82  for (i = 0; i < nbuiltins; i++) {
 83    if (strcmp(builtins[i].name, name) == 0)
 84      return -1;
 85  }
 86  if (nbuiltins >= MAX_BUILTINS)
 87    return -1;
 88  builtins[nbuiltins].name = name;
 89  builtins[nbuiltins].fn   = fn;
 90  nbuiltins++;
 91  return 0;
 92}
 93
 94int
 95wexec_is_builtin(const char *name)
 96{
 97  int i;
 98  for (i = 0; i < nbuiltins; i++) {
 99    if (strcmp(builtins[i].name, name) == 0)
100      return 1;
101  }
102  return 0;
103}
104
105int
106wexec_is_nofork(const char *name)
107{
108#include "nofork_list.h"
109  int i;
110  for (i = 0; nofork_list[i]; i++) {
111    if (strcmp(nofork_list[i], name) == 0)
112      return 1;
113  }
114  return 0;
115}
116
117#if FEATURE_NOEXEC
118static int noexec_enabled = 1;
119
120#if FEATURE_NOEXEC_OVERRIDE
121static void
122noexec_read_env(void)
123{
124  const char *e = wexec_getenv("ARUU_NOEXEC");
125  if (e && strcmp(e, "0") == 0)
126    noexec_enabled = 0;
127  else
128    noexec_enabled = 1;
129}
130#endif
131
132void
133wexec_set_noexec(int enabled)
134{
135  noexec_enabled = enabled;
136}
137
138int
139wexec_get_noexec(void)
140{
141#if FEATURE_NOEXEC_OVERRIDE
142  noexec_read_env();
143#endif
144  return noexec_enabled;
145}
146#endif
147
148#if FEATURE_NOFORK
149static int nofork_enabled = 1;
150
151#if FEATURE_NOFORK_OVERRIDE
152static void
153nofork_read_env(void)
154{
155  const char *e = wexec_getenv("ARUU_NOFORK");
156  if (e && strcmp(e, "0") == 0)
157    nofork_enabled = 0;
158  else
159    nofork_enabled = 1;
160}
161#endif
162
163void
164wexec_set_nofork(int enabled)
165{
166  nofork_enabled = enabled;
167}
168
169int
170wexec_get_nofork(void)
171{
172#if FEATURE_NOFORK_OVERRIDE
173  nofork_read_env();
174#endif
175  return nofork_enabled;
176}
177#endif
178
179char *
180wwhich(const char *name)
181{
182  const char *path, *p, *end;
183  char       *buf;
184  size_t      dlen, nlen;
185  struct stat st;
186
187#if FEATURE_NOEXEC
188  /* when builtin dispatch is active, registered builtins have no filesystem path */
189  if (wexec_get_noexec() && wexec_is_builtin(name))
190    return NULL;
191#endif
192
193  /* slash present: treat as a literal path */
194  if (strchr(name, '/') != NULL) {
195    if (stat(name, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & 0111))
196      return estrdup(name);
197    return NULL;
198  }
199
200  path = wexec_getenv("PATH");
201  if (path == NULL)
202    path = ARUU_PATH_DEFPATH;
203
204  nlen = strlen(name);
205  p    = path;
206  for (;;) {
207    end  = strchr(p, ':');
208    dlen = end ? (size_t)(end - p) : strlen(p);
209    /* skip empty components (current dir) for safety */
210    if (dlen > 0) {
211      buf = emalloc(dlen + 1 + nlen + 1);
212      memcpy(buf, p, dlen);
213      buf[dlen] = '/';
214      memcpy(buf + dlen + 1, name, nlen + 1);
215      if (stat(buf, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & 0111))
216        return buf;
217      free(buf);
218    }
219    if (!end)
220      break;
221    p = end + 1;
222  }
223  return NULL;
224}
225
226#if FEATURE_NOEXEC || FEATURE_NOFORK
227static sigjmp_buf            inproc_jmp;
228static volatile sig_atomic_t inproc_active      = 0;
229static volatile int          inproc_exit_status = 0;
230
231static void
232inproc_sig_handler(int signo)
233{
234  if (inproc_active) {
235    siglongjmp(inproc_jmp, signo);
236  }
237}
238
239int
240wexec_call_builtin(const char *name, char *const *argv)
241{
242  struct sigaction sa, osa_int, osa_quit, osa_pipe;
243  int              i, argc, ret, sig;
244  char           **p;
245
246  sig = sigsetjmp(inproc_jmp, 1);
247  if (sig != 0) {
248    inproc_active = 0;
249    sigaction(SIGINT, &osa_int, NULL);
250    sigaction(SIGQUIT, &osa_quit, NULL);
251    sigaction(SIGPIPE, &osa_pipe, NULL);
252    if (sig == 1)
253      return inproc_exit_status;
254    kill(getpid(), sig);
255    return 128 + sig;
256  }
257
258  sa.sa_handler = inproc_sig_handler;
259  sigemptyset(&sa.sa_mask);
260  sa.sa_flags = 0;
261  sigaction(SIGINT, &sa, &osa_int);
262  sigaction(SIGQUIT, &sa, &osa_quit);
263
264  sa.sa_handler = SIG_IGN;
265  sigaction(SIGPIPE, &sa, &osa_pipe);
266
267  inproc_active = 1;
268
269  ret  = -1;
270  argc = 0;
271  for (p = (char **)argv; *p; p++)
272    argc++;
273  for (i = 0; i < nbuiltins; i++) {
274    if (strcmp(builtins[i].name, name) == 0) {
275      fflush(stdout);
276      fflush(stderr);
277      ret = builtins[i].fn(argc, (char **)argv);
278      fflush(stdout);
279      fflush(stderr);
280      break;
281    }
282  }
283
284  inproc_active = 0;
285
286  sigaction(SIGINT, &osa_int, NULL);
287  sigaction(SIGQUIT, &osa_quit, NULL);
288  sigaction(SIGPIPE, &osa_pipe, NULL);
289
290  return ret;
291}
292#endif
293
294static int
295fork_exec(const char *path, char *const *argv, int use_path)
296{
297  pid_t pid;
298  int   st;
299
300  pid = fork();
301  if (pid < 0) {
302    weprintf("fork:");
303    return -1;
304  }
305  if (pid == 0) {
306    const char *base = strrchr(path, '/');
307    base             = base ? base + 1 : path;
308#if FEATURE_NOEXEC
309    if (wexec_get_noexec() && wexec_is_builtin(base)) {
310      struct sigaction sa;
311      int              i, ret;
312      for (i = 1; i < NSIG; i++) {
313        if (sigaction(i, NULL, &sa) == 0) {
314          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
315            sa.sa_handler = SIG_DFL;
316            sa.sa_flags   = 0;
317            sigemptyset(&sa.sa_mask);
318            sigaction(i, &sa, NULL);
319          }
320        }
321      }
322      ret = wexec_call_builtin(base, argv);
323      _exit(ret);
324    }
325#endif
326    if (use_path)
327      execvp(path, argv);
328    else
329      execv(path, argv);
330    weprintf(use_path ? "execvp %s:" : "execv %s:", path);
331    _exit((errno == ENOENT) ? 127 : 126);
332  }
333  if (waitpid(pid, &st, 0) < 0) {
334    weprintf("waitpid:");
335    return -1;
336  }
337  if (WIFEXITED(st))
338    return WEXITSTATUS(st);
339  if (WIFSIGNALED(st))
340    return 128 + WTERMSIG(st);
341  return -1;
342}
343
344int
345wexecvp(const char *name, char *const *argv)
346{
347#if FEATURE_NOFORK
348  if (wexec_get_nofork() && wexec_is_builtin(name) && wexec_is_nofork(name)) {
349    fflush(stdout);
350    fflush(stderr);
351    return wexec_call_builtin(name, argv);
352  }
353#endif
354  return fork_exec(name, argv, 1);
355}
356
357int
358wexecv(const char *path, char *const *argv)
359{
360  const char *base = strrchr(path, '/');
361  base             = base ? base + 1 : path;
362#if FEATURE_NOFORK
363  if (wexec_get_nofork() && wexec_is_builtin(base) && wexec_is_nofork(base)) {
364    fflush(stdout);
365    fflush(stderr);
366    return wexec_call_builtin(base, argv);
367  }
368#endif
369  return fork_exec(path, argv, 0);
370}
371
372FILE *
373wpopen(const char *name, char *const *argv, const char *mode)
374{
375  int   pfd[2];
376  pid_t pid;
377  int   parent_end, child_end;
378  char *sh_argv[4];
379#if FEATURE_NOEXEC
380  struct sigaction sa;
381  int              i;
382#endif
383
384  if (pipe(pfd) < 0) {
385    weprintf("pipe:");
386    return NULL;
387  }
388
389  if (*mode == 'r') {
390    parent_end = pfd[0];
391    child_end  = pfd[1];
392  } else {
393    parent_end = pfd[1];
394    child_end  = pfd[0];
395  }
396
397  pid = fork();
398  if (pid < 0) {
399    weprintf("fork:");
400    close(pfd[0]);
401    close(pfd[1]);
402    return NULL;
403  }
404
405  if (pid == 0) {
406    close(parent_end);
407    if (child_end != (*mode == 'r' ? STDOUT_FILENO : STDIN_FILENO)) {
408      dup2(child_end, *mode == 'r' ? STDOUT_FILENO : STDIN_FILENO);
409      close(child_end);
410    }
411    if (argv) {
412#if FEATURE_NOEXEC
413      if (wexec_get_noexec() && wexec_is_builtin(name)) {
414        int    argc;
415        char **p;
416
417        /* reset caught signal handlers to default for inproc command */
418        for (i = 1; i < NSIG; i++) {
419          if (sigaction(i, NULL, &sa) == 0) {
420            if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
421              sa.sa_handler = SIG_DFL;
422              sa.sa_flags   = 0;
423              sigemptyset(&sa.sa_mask);
424              sigaction(i, &sa, NULL);
425            }
426          }
427        }
428
429        argc = 0;
430        for (p = (char **)argv; *p; p++)
431          argc++;
432        wexec_call_builtin(name, argv);
433        fflush(stdout);
434        fflush(stderr);
435        _exit(0);
436      }
437#endif
438      execvp(name, argv);
439      weprintf("execvp %s:", name);
440      _exit((errno == ENOENT) ? 127 : 126);
441    } else {
442      sh_argv[0] = "sh";
443      sh_argv[1] = "-c";
444      sh_argv[2] = (char *)name;
445      sh_argv[3] = NULL;
446#if FEATURE_NOEXEC
447      if (wexec_get_noexec() && wexec_is_builtin("sh")) {
448        /* reset caught signal handlers to default for inproc command */
449        for (i = 1; i < NSIG; i++) {
450          if (sigaction(i, NULL, &sa) == 0) {
451            if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
452              sa.sa_handler = SIG_DFL;
453              sa.sa_flags   = 0;
454              sigemptyset(&sa.sa_mask);
455              sigaction(i, &sa, NULL);
456            }
457          }
458        }
459
460        wexec_call_builtin("sh", sh_argv);
461        fflush(stdout);
462        fflush(stderr);
463        _exit(0);
464      }
465#endif
466      execvp("sh", sh_argv);
467      weprintf("execvp sh:");
468      _exit((errno == ENOENT) ? 127 : 126);
469    }
470  }
471
472  close(child_end);
473  {
474    FILE *fp = fdopen(parent_end, mode);
475    if (!fp) {
476      weprintf("fdopen:");
477      close(parent_end);
478      kill(pid, SIGTERM);
479      waitpid(pid, NULL, 0);
480      return NULL;
481    }
482    if (wpopen_track(fp, pid) < 0) {
483      fclose(fp);
484      kill(pid, SIGTERM);
485      waitpid(pid, NULL, 0);
486      return NULL;
487    }
488    return fp;
489  }
490}
491
492int
493wpclose(FILE *fp)
494{
495  int   st;
496  pid_t pid;
497
498  pid = wpopen_untrack(fp);
499  if (fclose(fp) == EOF)
500    weprintf("fclose:");
501  if (pid < 0)
502    return -1;
503  if (waitpid(pid, &st, 0) < 0) {
504    weprintf("waitpid:");
505    return -1;
506  }
507  if (WIFEXITED(st))
508    return WEXITSTATUS(st);
509  if (WIFSIGNALED(st))
510    return 128 + WTERMSIG(st);
511  return -1;
512}
513
514int
515wsystem(const char *cmd)
516{
517#if FEATURE_NOEXEC
518  if (wexec_get_noexec() && wexec_is_builtin("sh")) {
519    pid_t pid;
520    int   st;
521
522    pid = fork();
523    if (pid < 0) {
524      weprintf("fork:");
525      return -1;
526    }
527    if (pid == 0) {
528      char            *sh_argv[4];
529      struct sigaction sa;
530      int              i;
531      int              ret;
532
533      /* reset caught signal handlers to default for inproc command */
534      for (i = 1; i < NSIG; i++) {
535        if (sigaction(i, NULL, &sa) == 0) {
536          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
537            sa.sa_handler = SIG_DFL;
538            sa.sa_flags   = 0;
539            sigemptyset(&sa.sa_mask);
540            sigaction(i, &sa, NULL);
541          }
542        }
543      }
544
545      sh_argv[0] = "sh";
546      sh_argv[1] = "-c";
547      sh_argv[2] = (char *)cmd;
548      sh_argv[3] = NULL;
549      ret        = wexec_call_builtin("sh", sh_argv);
550      _exit(ret);
551    }
552    if (waitpid(pid, &st, 0) < 0) {
553      weprintf("waitpid:");
554      return -1;
555    }
556    if (WIFEXITED(st))
557      return WEXITSTATUS(st);
558    if (WIFSIGNALED(st))
559      return 128 + WTERMSIG(st);
560    return -1;
561  }
562#endif
563  {
564    char *sh_argv[4];
565    int   ret;
566    sh_argv[0] = "sh";
567    sh_argv[1] = "-c";
568    sh_argv[2] = (char *)cmd;
569    sh_argv[3] = NULL;
570    ret        = fork_exec("sh", sh_argv, 1);
571    return ret;
572  }
573}
574
575void
576wexecvp_self(const char *name, char *const *argv)
577{
578#if FEATURE_NOEXEC
579  if (wexec_get_noexec() && wexec_is_builtin(name)) {
580    struct sigaction sa;
581    int              i;
582    int              ret;
583
584    /* reset caught signal handlers to default for inproc command */
585    for (i = 1; i < NSIG; i++) {
586      if (sigaction(i, NULL, &sa) == 0) {
587        if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
588          sa.sa_handler = SIG_DFL;
589          sa.sa_flags   = 0;
590          sigemptyset(&sa.sa_mask);
591          sigaction(i, &sa, NULL);
592        }
593      }
594    }
595
596    fflush(stdout);
597    fflush(stderr);
598    ret = wexec_call_builtin(name, argv);
599    fflush(stdout);
600    fflush(stderr);
601    exit(ret);
602  }
603#endif
604  execvp(name, argv);
605  weprintf("execvp %s:", name);
606  exit((errno == ENOENT) ? 127 : 126);
607}
608
609void
610wexecv_self(const char *path, char *const *argv)
611{
612#if FEATURE_NOEXEC
613  {
614    const char *base = strrchr(path, '/');
615    base             = base ? base + 1 : path;
616    if (wexec_get_noexec() && wexec_is_builtin(base)) {
617      struct sigaction sa;
618      int              i;
619      int              ret;
620
621      /* reset caught signal handlers to default for inproc command */
622      for (i = 1; i < NSIG; i++) {
623        if (sigaction(i, NULL, &sa) == 0) {
624          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
625            sa.sa_handler = SIG_DFL;
626            sa.sa_flags   = 0;
627            sigemptyset(&sa.sa_mask);
628            sigaction(i, &sa, NULL);
629          }
630        }
631      }
632
633      fflush(stdout);
634      fflush(stderr);
635      ret = wexec_call_builtin(base, argv);
636      fflush(stdout);
637      fflush(stderr);
638      exit(ret);
639    }
640  }
641#endif
642  execv(path, argv);
643  weprintf("execv %s:", path);
644  exit((errno == ENOENT) ? 127 : 126);
645}
646
647void(exit)(int);
648
649void
650wexec_exit(int code)
651{
652#if FEATURE_NOEXEC || FEATURE_NOFORK
653  if (inproc_active) {
654    inproc_exit_status = code;
655    siglongjmp(inproc_jmp, 1);
656  }
657#endif
658  (exit)(code);
659}