File: env.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 (408 lines) | stat: -rw-r--r-- 12,933 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
/* Copyright © Triad National Security, LLC, and others. */

#define _GNU_SOURCE
#include "config.h"

#include <fnmatch.h>
#include <string.h>
#include <unistd.h>

#include "all.h"


/** Macros **/


/** Function prototypes (private) **/

char *environ_get(char **env, const char *name, int *index);
char *environ_expand(char **env, const char *value);


/** Functions **/

/** Get a container environment variable.

      @param  c     Container configuration whose environment to search.

      @param  name  Name of variable to look up.

      @returns String value of environment variable @p name in @p c, or @c
               NULL if not set. This is a pointer to the container’s actual
               environment buffer and thus should not be modified. */
char *cenv_get(struct container *c, const char *name)
{
   return environ_get(c->environ, name, NULL);
}

/** Set variable in container environment.

      @param c      Container configuration to change.

      @param name   Name of variable.

      @param value  New value for variable.

    If @p name already exists in the container environment, its value is replaced. Otherwise, it is added to the environment.

    If @p c->env_expand, then further expand variables in @p value marked with
    @c $, as described in man page. */
void cenv_set(struct container *c, const char *name, const char *value)
{
   int i;
   char *line;

   T__(strchr(name, '=') == NULL);
   line = cats(3, name, "=",
               c->env_expand ? environ_expand(c->environ, value) : value);

   DEBUG("environment: %s", line);
   (void)environ_get(c->environ, name, &i);
   if (i != -1)
      c->environ[i] = line;
   else
      list_append((void **)&(c->environ), &line, sizeof(line));
}

/** Set multiple variables in a container environment.

      @param c     Container to modify.

      @param vars  List of variables to set. */
void cenvs_set(struct container *c, const struct env_var *vars)
{
   for (size_t i = 0; vars[i].name != NULL; i++)
      cenv_set(c, vars[i].name, vars[i].value);
}

/** Unset variable in container environment.

      @param name  Name of variable to unset.

    If @p name is not in the container environment, do nothing. */
void cenv_unset(struct container *c, const char *name)
{
   int victim_i;
   int last_i = list_count(c->environ, sizeof(c->environ[0])) - 1;
   T__ (name != NULL);

   (void)environ_get(c->environ, name, &victim_i);
   if (victim_i == -1)
      DEBUG("environment: unset: not set, ignoring: %s", name);
   else {
      DEBUG("environment: unset %s", name);
      // swap with last non-null line to avoid gap
      c->environ[victim_i] = c->environ[last_i];
      c->environ[last_i] = NULL;
   }
}

/** Remove variables matching a glob from the container environment.

      @param c    Container to modify.

      @param glob  All variables whose names match this glob will be removed
                   from the container environment. */
void cenvs_unset(struct container *c, const char *glob)
{
   char **new_environ;

   T__ (c->environ != NULL);
   new_environ = list_new(list_count(c->environ, sizeof(c->environ[0])),
                          sizeof(c->environ[0]));

   // Copy into new environment the variables whose names *don’t* match.
   for (int i_old = 0, i_new = 0; c->environ[i_old] != NULL; i_old++) {
      char *name, *value;
      int matchp;
      split(&name, &value, c->environ[i_old], '=');
      T__ (value != NULL);                         // entries must have equals
      matchp = fnmatch(glob, name, FNM_EXTMATCH);  // extglobs if available
      if (matchp == 0) {
         DEBUG("environment: unset %s", name);
      } else {
         T__ (matchp == FNM_NOMATCH);
         *(value - 1) = '=';  // rejoin line
         new_environ[i_new++] = c->environ[i_old];
      }
   }

   // Note: It is legitimate to reassign the environ(7) global, if we ever
   // want to do that. See: http://man7.org/linux/man-pages/man3/exec.3p.html
   c->environ = new_environ;
}

/* Read the file listing environment variables at path, with records separated
   by delim, and return a corresponding list of struct env_var. Reads the
   entire file one time without seeking. If there is a problem reading the
   file, or with any individual variable, exit with error.

   The purpose of delim is to allow both newline- and zero-delimited files. We
   did consider using a heuristic to choose the file’s delimiter, but there
   seemed to be two problems. First, every heuristic we considered had flaws.
   Second, use of a heuristic would require reading the file twice or seeking.
   We don’t want to demand non-seekable files (e.g., pipes), and if we read
   the file into a buffer before parsing, we’d need our own getdelim(3). See
   issue #1124 for further discussion. */
struct env_var *env_file_read(const char *path, int delim)
{
   struct env_var *vars;
   FILE *fp;

   Tfe (fp = fopen(path, "r"), "can't open: %s", path);

   vars = list_new(0, sizeof(struct env_var));
   for (size_t line_no = 1; true; line_no++)
   {
      struct env_var var;
      char *line;
      errno = 0;
      line = getdelim_ch(fp, delim);
      if (line == NULL) // EOF
         break;
      if (line[0] == '\0') // skip blank lines
         continue;
      var = env_parse(line, path, line_no);
      list_append((void **)&vars, &var, sizeof(var));
   }

   Zfe (fclose(fp), "can't close: %s", path);
   return vars;
}

/* Parse the environment variable in line and return it as a struct env_var.
   Exit with error on syntax error; if path is non-NULL, attribute the problem
   to that path at line_no. Note: Trailing whitespace such as newline is
   *included* in the value. */
struct env_var env_parse(const char *line, const char *path, size_t lineno)
{
   char *name, *value, *where;

   if (path == NULL)
      where = strdup_ch(line);
   else
      where = asprintf_ch("%s:%zu", path, lineno);

   // Split line into variable name and value.
   split(&name, &value, line, '=');
   Tf_ (value != NULL, "can't parse variable: no delimiter: %s", where);
   Tf_ (name[0] != 0, "can't parse variable: empty name: %s", where);

   // Strip leading and trailing single quotes from value, if both present.
   if (   strlen(value) >= 2
       && value[0] == '\''
       && value[strlen(value) - 1] == '\'') {
      value[strlen(value) - 1] = 0;
      value++;
   }

   return (struct env_var){ name, value };
}

/** Get the value of a variable in an @c environ(7)-style array.

      @param  env[in]     @c NULL-terminated array of strings having the form
                          @c name=value, i.e. variable name as a sequence of
                          one or more bytes, equals character (@c 0x3d),
                          variable value as a sequence of zero or more bytes,
                          terminating zero byte.

      @param  name[in]    Name of variable to search for.

      @param  index[out]  Array index of variable @p name, or -1 if not found.
                          Ignored if a null pointer.

    @returns the value of variable @p name, or @c NULL if not found. This is a
             pointer into @p env and thus should not be modified. */
char *environ_get(char **env, const char *name, int *index)
{
   size_t name_len = strlen(name);

   for (int i = 0; env[i] != NULL; i++) {
      char *v;

      T__ (v = strchr(env[i], '='));        // address of first ‘=’
      v++;                                  // skip ‘=’

      if (name_len != (v - env[i] - 1))     // lengths different ⇒ no match
         continue;

      if (streqn(env[i], name, name_len)) {  // found
         if (index != NULL)
            *index = i;
         return v;
      }
   }

   // not found
   if (index != NULL)
      *index = -1;
   return NULL;
}

/** Expand shell-style variable notation in a string.

      @param env    List of environment variable names and values in the same
                    format at @c environ(7). This is the variables used for
                    expansion.

      @param value  String to expand.

    @returns the expanded string.

    See @c ch-run(1) for the details of how expansion works. Notably,
    expansions within colon-separated lists (e.g. @c $PATH) have special
    treatment. */
char *environ_expand(char **env, const char *value)
{
   char *vwk;                   // modifiable copy of value
   char *vwk_cur;               // current location in vwk
   char *vout = strdup_ch("");  // output (expanded) string
   bool first_out = false;      // true after 1st output element written
   vwk = strdup_ch(value);
   vwk_cur = vwk;

   while (true) {                          // loop executes ≥ once
      char *elem = strsep(&vwk_cur, ":");  // NULL ⇒ no more elements
      if (elem == NULL)
         break;
      if (elem[0] == '$' && elem[1] != 0) {  // looks like $VARIABLE
         elem = environ_get(env, elem + 1, NULL);
         if (elem != NULL && elem[0] == '\0')  // if set but empty ...
            elem = NULL;                       // ... convert to unset
      }
      if (elem != NULL) {  // empty -> omit from output list
         vout = cats(3, vout, first_out ? ":" : "", elem);
         first_out = true;
      }
   }

   return vout;
}

/** Pack an @c environ(7) style environment into a buffer.

      @param[in]  env      Null-terminated list of equals-separate environment
                           variable strings.

      @param[out] byte_ct  Size of the returned buffer in bytes.

    @returns a newly-allocated buffer containing the variable strings in @p
    env separated by zero bytes (@c '\0'). This is the same format as @c
    /proc/<pid>/environ. */
char *environ_serialize(char **env, size_t *byte_ct)
{
   char *buf = NULL;

   *byte_ct = 0;
   for (size_t i = 0; env[i] != NULL; i++) {
      size_t line_byte_ct = strlen(env[i]) + 1;
      buf = realloc_ch(buf, *byte_ct + line_byte_ct, false);
      memcpy(buf + *byte_ct, env[i], line_byte_ct);
      *byte_ct += line_byte_ct;
   }

   return buf;
}

/** Unpack a buffer of environment variables.

      @param buf      Buffer containing a zero-byte (@c '\0') delimited
                      sequence of equals-separated variables (same format as
                      @c /proc/<pid>/environ).

      @param byte_ct  Length of buffer in bytes.

   @returns a @c environ(7) style null-terminated array of strings. Both the
   array of pointers and the strings they point to are newly allocated (i.e.,
   it’s a deep copy); thus, @p buf need not remain valid after the call.

   @note The last byte in @p env must be @c '\0', terminating the last
   variable string. */
char **environ_unserialize(char *buf, size_t byte_ct)
{
   char **env = NULL;
   char *new = malloc_ch(byte_ct, false);
   char *next;
   int var_ct;

   memcpy(new, buf, byte_ct);

   // Allocate env to the right size.
   var_ct = 0;
   for (int i = 0; i < byte_ct; i++)
      var_ct += (new[i] == '\0');
   env = list_new(var_ct, sizeof(char *));

   next = new;
   for (int i = 0; i < var_ct; i++) {
      env[i] = next;
      next += strlen(next) + 1;
   }
   T__ (new[byte_ct - 1] == '\0');
   T__ (next == new + byte_ct);

   return env;
}

/* Set the environment variables listed in d. */
void envs_hook_set(struct container *c, void *d)
{
   struct env_var *vars = d;
   cenvs_set(c, vars);
}

/* Set the environment variables specified in file d. */
void envs_hook_set_file(struct container *c, void *d)
{
   struct env_file *ef = d;
   cenvs_set(c, env_file_read(ef->path, ef->delim));
}

/* Unset the environment variables matching glob d. */
void envs_hook_unset(struct container *c, void *d)
{
   cenvs_unset(c, (char *)d);
}

/** @c getenv(3) with a default value.

      @param name  Name of environment variable to fetch.

      @param df    Value to return if @p name is not set.

    @returns the value of environment variable @p name if set, otherwise @p
    default. Note: if @p name is set to the empty string, that is the return
    value, i.e. the distinction between unset and set but empty matters. */
char *getenv_df(const char *name, char *df)
{
   char *ret = getenv(name);
   if (!ret)
      ret = df;
   return ret;
}

/** Get the first environment variable in a prioritized list that is set.

      @param names[in]   Zero-terminated array of variable names in descending
                         priority order.

      @param name[out]   Name of the first variable in @p names that is set,
                         or @c NULL if none were.

      @param value[out]  Value of the first set variable, or @c NULL.

    @returns true if one of the variables was set, false otherwise. */
bool getenv_first(char **array, char **name, char **value)
{
   for (int i = 0; array[i] != NULL; i++) {
      *name = array[i];
      *value = getenv(*name);
      if (*value != NULL)
         return true;
   }

   *name = NULL;
   *value = NULL;
   return false;
}