File: misc.c

package info (click to toggle)
charliecloud 0.43-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,116 kB
  • sloc: python: 6,021; sh: 4,284; ansic: 3,863; makefile: 598
file content (126 lines) | stat: -rw-r--r-- 4,025 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/* Copyright © Triad National Security, LLC, and others. */

#define _GNU_SOURCE
#include "config.h"
#include "misc.h"

#include <pwd.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#include "all.h"


/** External variables **/

/* Path to host temporary directory. Set during command line processing. */
char *host_tmp = NULL;

/* Username of invoking user. Set during early initialization. */
char *username = NULL;


/** Functions **/

/** Run a command, wait for it to exit, then return. If the command fails,
    exit with a fatal error. Similar to Python’s @c subprocess.run().

    @param cmd    Command to run. If a path contains a slash, run that file
                  directly; otherwise, search in @c $PATH; i.e. the same
                  behavior as @c execlp(3).

    @param pathx  Colon-separated string suitable for @c $PATH to append to
                  the existing @c $PATH (e.g. “@c /foo:/bar”)., or @c NULL if
                  none. Neither the child nor the current process” @c $PATH is
                  modified. May not begin/end with colon.

    @param args2  Null-terminated array of arguments for the command, like
                  @c execv(3).

    @param args1  Null-terminated variadic arguments for the command, like
                  @c execl(3). If not used, caller must provide @c NULL.

    @warning
    Both @p args1 and @p args2 can be non-NULL simultaneously. In this case,
    @p args1 precedes @p args2 on the @p cmd command line, which is the
    opposite order from the function call.

    Design decisions:

    #. Manual @c fork(2) / @c exec(2) rather than @c posix_spawn(3) for better
       error reporting.

    #. @c fork(2) rather than @c vfork(2) or @c clone(2) because I didn’t feel
       like thinking hard enough to figure out whether one of the latter two
       would be better.

    #. @c fork(2) rather than @c fork_ch() because we don’t want garbage
       collection overhead. */
void run_wait(char *cmd, char *pathx, char **args2, ...)
{
   pid_t child;

   T__ (pathx && pathx[0] != '\0');
   T__ (pathx[0] != ':' && pathx[strlen(pathx)-1] != ':');

#undef fork
   Tfe (0 <= (child = fork()), "can't fork: %s", cmd);
#define fork FN_BLOCKED
   if (child) {  // parent
      int ws;
      DEBUG("run_wait: waiting ...");
      T_e (-1 != waitpid(child, &ws, 0));
      if (WIFEXITED(ws)) {
         Zf_ (WEXITSTATUS(ws), "%s: failed with %d", cmd, WEXITSTATUS(ws));
      } else {
         // no WUNTRACED so assume WIFSIGNALED
         Tf_ (0, "%s: killed by signal %d", cmd, WTERMSIG(ws));
      }
   } else {      // child
      char **args, **env_saved;
      char *arg, *path;
      va_list ap;

      // append pathx for execvpe(3)
      env_saved = list_copy(environ, sizeof(environ[0]));
      path = cats(3, getenv("PATH"), (pathx == NULL ? "" : ":"), pathx);
      setenv("PATH", path, true);
      DEBUG("run_wait: PATH=%s", path);

      // prepare command arguments
      args = NULL;
      list_append((void **)&args, &cmd, sizeof(cmd));
      va_start(ap, args2);
      while ((arg = va_arg(ap, char *)))
         list_append((void **)&args, &arg, sizeof(arg));
      va_end(ap);
      list_cat((void **)&args, args2, sizeof(char *));

      // replace self with command
      T__ (streq(cmd, args[0]));
      DEBUG("run_wait: $ %s", argv_to_string(args));
      execvpe(cmd, args, env_saved);  // only returns on error
      ERROR (errno, "can't exec: %s", cmd);
      exit(EXIT_ERR_CMD);
   }
}

/* Set the username global by looking up EUID in the password database. Logic
   must match ch.user(). Formerly, we used $USER, but that’s not reliably set.
   See #1162. This approach does require that EUID *have* a corresponding
   username. */
void username_set(void)
{
   struct passwd *pw;

   errno = 0;
   Tfe (pw = getpwuid(geteuid()), "can't get username for EUID %d", geteuid());
   username = pw->pw_name;
}

/* Report the version number. */
void version(void)
{
   fprintf(stderr, "%s\n", VERSION);
}