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 = "/usr/local/bin:/usr/bin:/bin";
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    _exit(127);
331  }
332  if (waitpid(pid, &st, 0) < 0) {
333    weprintf("waitpid:");
334    return -1;
335  }
336  if (WIFEXITED(st))
337    return WEXITSTATUS(st);
338  if (WIFSIGNALED(st))
339    return 128 + WTERMSIG(st);
340  return -1;
341}
342
343int
344wexecvp(const char *name, char *const *argv)
345{
346#if FEATURE_NOFORK
347  if (wexec_get_nofork() && wexec_is_builtin(name) && wexec_is_nofork(name)) {
348    fflush(stdout);
349    fflush(stderr);
350    return wexec_call_builtin(name, argv);
351  }
352#endif
353  return fork_exec(name, argv, 1);
354}
355
356int
357wexecv(const char *path, char *const *argv)
358{
359  const char *base = strrchr(path, '/');
360  base             = base ? base + 1 : path;
361#if FEATURE_NOFORK
362  if (wexec_get_nofork() && wexec_is_builtin(base) && wexec_is_nofork(base)) {
363    fflush(stdout);
364    fflush(stderr);
365    return wexec_call_builtin(base, argv);
366  }
367#endif
368  return fork_exec(path, argv, 0);
369}
370
371FILE *
372wpopen(const char *name, char *const *argv, const char *mode)
373{
374  int   pfd[2];
375  pid_t pid;
376  int   parent_end, child_end;
377  char *sh_argv[4];
378#if FEATURE_NOEXEC
379  struct sigaction sa;
380  int              i;
381#endif
382
383  if (pipe(pfd) < 0) {
384    weprintf("pipe:");
385    return NULL;
386  }
387
388  if (*mode == 'r') {
389    parent_end = pfd[0];
390    child_end  = pfd[1];
391  } else {
392    parent_end = pfd[1];
393    child_end  = pfd[0];
394  }
395
396  pid = fork();
397  if (pid < 0) {
398    weprintf("fork:");
399    close(pfd[0]);
400    close(pfd[1]);
401    return NULL;
402  }
403
404  if (pid == 0) {
405    close(parent_end);
406    if (child_end != (*mode == 'r' ? STDOUT_FILENO : STDIN_FILENO)) {
407      dup2(child_end, *mode == 'r' ? STDOUT_FILENO : STDIN_FILENO);
408      close(child_end);
409    }
410    if (argv) {
411#if FEATURE_NOEXEC
412      if (wexec_get_noexec() && wexec_is_builtin(name)) {
413        int    argc;
414        char **p;
415
416        /* reset caught signal handlers to default for inproc command */
417        for (i = 1; i < NSIG; i++) {
418          if (sigaction(i, NULL, &sa) == 0) {
419            if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
420              sa.sa_handler = SIG_DFL;
421              sa.sa_flags   = 0;
422              sigemptyset(&sa.sa_mask);
423              sigaction(i, &sa, NULL);
424            }
425          }
426        }
427
428        argc = 0;
429        for (p = (char **)argv; *p; p++)
430          argc++;
431        wexec_call_builtin(name, argv);
432        fflush(stdout);
433        fflush(stderr);
434        _exit(0);
435      }
436#endif
437      execvp(name, argv);
438      _exit(127);
439    } else {
440      sh_argv[0] = "sh";
441      sh_argv[1] = "-c";
442      sh_argv[2] = (char *)name;
443      sh_argv[3] = NULL;
444#if FEATURE_NOEXEC
445      if (wexec_get_noexec() && wexec_is_builtin("sh")) {
446        /* reset caught signal handlers to default for inproc command */
447        for (i = 1; i < NSIG; i++) {
448          if (sigaction(i, NULL, &sa) == 0) {
449            if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
450              sa.sa_handler = SIG_DFL;
451              sa.sa_flags   = 0;
452              sigemptyset(&sa.sa_mask);
453              sigaction(i, &sa, NULL);
454            }
455          }
456        }
457
458        wexec_call_builtin("sh", sh_argv);
459        fflush(stdout);
460        fflush(stderr);
461        _exit(0);
462      }
463#endif
464      execvp("sh", sh_argv);
465      _exit(127);
466    }
467  }
468
469  close(child_end);
470  {
471    FILE *fp = fdopen(parent_end, mode);
472    if (!fp) {
473      weprintf("fdopen:");
474      close(parent_end);
475      kill(pid, SIGTERM);
476      waitpid(pid, NULL, 0);
477      return NULL;
478    }
479    if (wpopen_track(fp, pid) < 0) {
480      fclose(fp);
481      kill(pid, SIGTERM);
482      waitpid(pid, NULL, 0);
483      return NULL;
484    }
485    return fp;
486  }
487}
488
489int
490wpclose(FILE *fp)
491{
492  int   st;
493  pid_t pid;
494
495  pid = wpopen_untrack(fp);
496  if (fclose(fp) == EOF)
497    weprintf("fclose:");
498  if (pid < 0)
499    return -1;
500  if (waitpid(pid, &st, 0) < 0) {
501    weprintf("waitpid:");
502    return -1;
503  }
504  if (WIFEXITED(st))
505    return WEXITSTATUS(st);
506  if (WIFSIGNALED(st))
507    return 128 + WTERMSIG(st);
508  return -1;
509}
510
511int
512wsystem(const char *cmd)
513{
514#if FEATURE_NOEXEC
515  if (wexec_get_noexec() && wexec_is_builtin("sh")) {
516    pid_t pid;
517    int   st;
518
519    pid = fork();
520    if (pid < 0) {
521      weprintf("fork:");
522      return -1;
523    }
524    if (pid == 0) {
525      char            *sh_argv[4];
526      struct sigaction sa;
527      int              i;
528      int              ret;
529
530      /* reset caught signal handlers to default for inproc command */
531      for (i = 1; i < NSIG; i++) {
532        if (sigaction(i, NULL, &sa) == 0) {
533          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
534            sa.sa_handler = SIG_DFL;
535            sa.sa_flags   = 0;
536            sigemptyset(&sa.sa_mask);
537            sigaction(i, &sa, NULL);
538          }
539        }
540      }
541
542      sh_argv[0] = "sh";
543      sh_argv[1] = "-c";
544      sh_argv[2] = (char *)cmd;
545      sh_argv[3] = NULL;
546      ret        = wexec_call_builtin("sh", sh_argv);
547      _exit(ret);
548    }
549    if (waitpid(pid, &st, 0) < 0) {
550      weprintf("waitpid:");
551      return -1;
552    }
553    if (WIFEXITED(st))
554      return WEXITSTATUS(st);
555    if (WIFSIGNALED(st))
556      return 128 + WTERMSIG(st);
557    return -1;
558  }
559#endif
560  {
561    char *sh_argv[4];
562    int   ret;
563    sh_argv[0] = "sh";
564    sh_argv[1] = "-c";
565    sh_argv[2] = (char *)cmd;
566    sh_argv[3] = NULL;
567    ret        = fork_exec("sh", sh_argv, 1);
568    return ret;
569  }
570}
571
572void
573wexecvp_self(const char *name, char *const *argv)
574{
575#if FEATURE_NOEXEC
576  if (wexec_get_noexec() && wexec_is_builtin(name)) {
577    struct sigaction sa;
578    int              i;
579    int              ret;
580
581    /* reset caught signal handlers to default for inproc command */
582    for (i = 1; i < NSIG; i++) {
583      if (sigaction(i, NULL, &sa) == 0) {
584        if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
585          sa.sa_handler = SIG_DFL;
586          sa.sa_flags   = 0;
587          sigemptyset(&sa.sa_mask);
588          sigaction(i, &sa, NULL);
589        }
590      }
591    }
592
593    fflush(stdout);
594    fflush(stderr);
595    ret = wexec_call_builtin(name, argv);
596    fflush(stdout);
597    fflush(stderr);
598    exit(ret);
599  }
600#endif
601  execvp(name, argv);
602  weprintf("execvp %s:", name);
603  exit((errno == ENOENT) ? 127 : 126);
604}
605
606void
607wexecv_self(const char *path, char *const *argv)
608{
609#if FEATURE_NOEXEC
610  {
611    const char *base = strrchr(path, '/');
612    base             = base ? base + 1 : path;
613    if (wexec_get_noexec() && wexec_is_builtin(base)) {
614      struct sigaction sa;
615      int              i;
616      int              ret;
617
618      /* reset caught signal handlers to default for inproc command */
619      for (i = 1; i < NSIG; i++) {
620        if (sigaction(i, NULL, &sa) == 0) {
621          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
622            sa.sa_handler = SIG_DFL;
623            sa.sa_flags   = 0;
624            sigemptyset(&sa.sa_mask);
625            sigaction(i, &sa, NULL);
626          }
627        }
628      }
629
630      fflush(stdout);
631      fflush(stderr);
632      ret = wexec_call_builtin(base, argv);
633      fflush(stdout);
634      fflush(stderr);
635      exit(ret);
636    }
637  }
638#endif
639  execv(path, argv);
640  weprintf("execv %s:", path);
641  exit((errno == ENOENT) ? 127 : 126);
642}
643
644void(exit)(int);
645
646void
647wexec_exit(int code)
648{
649#if FEATURE_NOEXEC || FEATURE_NOFORK
650  if (inproc_active) {
651    inproc_exit_status = code;
652    siglongjmp(inproc_jmp, 1);
653  }
654#endif
655  (exit)(code);
656}