usage: Print metavariables in SHOUTY letters.
[sw-tools] / src / sw_env.c
1 /* -*-c-*-
2 *
3 * $Id: sw_env.c,v 1.3 2004/04/08 01:52:19 mdw Exp $
4 *
5 * Mangling of environment variables
6 *
7 * (c) 1999 EBI
8 */
9
10 /*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of sw-tools.
13 *
14 * sw-tools is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * sw-tools is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with sw-tools; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29 /*----- Header files ------------------------------------------------------*/
30
31 #include "config.h"
32
33 #include <ctype.h>
34 #include <errno.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include <unistd.h>
40 #include <sys/wait.h>
41
42 #ifndef DECL_ENVIRON
43 extern char **environ;
44 #endif
45
46 #include <mLib/alloc.h>
47 #include <mLib/dstr.h>
48 #include <mLib/env.h>
49 #include <mLib/report.h>
50 #include <mLib/sym.h>
51
52 #include "sw_env.h"
53
54 /*----- Main code ---------------------------------------------------------*/
55
56 /* --- @env_error@ --- *
57 *
58 * Arguments: @int e@ = error code
59 *
60 * Returns: String representation of error.
61 *
62 * Use: Transforms an error into something a user can understand.
63 */
64
65 const char *env_error(int e)
66 {
67 const char *tab[] = {
68 "Everything is fine",
69 "Unexpected end-of-file",
70 "Bad character in variable name",
71 "Bad parameter substitution",
72 "Mismatched quote",
73 "Missing or spurious `}'",
74 "<see errno>",
75 "Internal error"
76 };
77 if (e == ENV_SYSTEM)
78 return (strerror(errno));
79 else
80 return (tab[e]);
81 }
82
83 /* --- @peek@ --- *
84 *
85 * Arguments: @FILE *fp@ = stream to read from
86 *
87 * Returns: Next nonwhitespace character.
88 *
89 * Use: Advances the file position past whitespace characters, and
90 * returns the following nonwhitespace character. The character
91 * is not `read'.
92 */
93
94 static int peek(FILE *fp)
95 {
96 int ch;
97
98 do {
99 ch = getc(fp);
100 if (ch == EOF)
101 return (EOF);
102 } while (isspace((unsigned char)ch));
103 ungetc(ch, fp);
104 return (ch);
105 }
106
107 /* --- @env_var@ --- *
108 *
109 * Arguments: @sym_table *t@ = pointer to symbol table
110 * @FILE *fp@ = pointer to stream to read from
111 * @dstr *d@ = pointer to output variable
112 *
113 * Returns: One of the @ENV_@ constants.
114 *
115 * Use: Scans a variable name from the input stream.
116 */
117
118 int env_var(sym_table *t, FILE *fp, dstr *d)
119 {
120 int ch = getc(fp);
121
122 if (ch == EOF)
123 return (ENV_EOF);
124 if (ch != '_' && !isalpha((unsigned char)ch))
125 return (ENV_VCHAR);
126 for (;;) {
127 DPUTC(d, ch);
128 ch = getc(fp);
129 if (ch == EOF || (ch != '_' && !isalnum((unsigned char)ch)))
130 break;
131 }
132 ungetc(ch, fp);
133 DPUTZ(d);
134 return (ENV_OK);
135 }
136
137 /* --- @cmdsubst@ --- *
138 *
139 * Arguments: @sym_table *t@ = pointer to symbol table
140 * @FILE *fp@ = pointer to stream to read from
141 * @dstr *d@ = pointer to output variable
142 * @unsigned f@ = interesting flags
143 *
144 * Returns: An @ENV_@ magic code.
145 *
146 * Use: Rips a command line out of the input stream and writes the
147 * output of the command to the variable. The parsing has some
148 * bizarre artifacts, but it's fairly serviceable.
149 */
150
151 static int cmdsubst(sym_table *t, FILE *fp, dstr *d, unsigned f)
152 {
153 int term = (f & EVF_BACKTICK) ? '`' : ')';
154 int argc = 1;
155 size_t l = d->len;
156 pid_t kid;
157 int fd[2];
158 int e;
159 char **argv;
160
161 /* --- Snarfle the arguments --- */
162
163 f &= ~EVF_INCSPC;
164 while (peek(fp) != term) {
165 if ((e = env_value(t, fp, d, f)) != ENV_OK)
166 return (e);
167 DPUTC(d, 0);
168 argc++;
169 }
170 getc(fp);
171 if (argc == 1) {
172 d->len = l;
173 return (ENV_OK);
174 }
175
176 /* --- Make the @argv@ array --- */
177
178 {
179 char *p = d->buf + l;
180 char *lim = d->buf + d->len;
181 char **v;
182
183 v = argv = xmalloc(argc * sizeof(char *));
184 while (p < lim) {
185 *v++ = p;
186 while (*p) {
187 p++;
188 if (p >= lim)
189 goto done;
190 }
191 p++;
192 }
193 done:;
194 *v++ = 0;
195 }
196
197 /* --- Do the fork/exec thing --- */
198
199 if (pipe(fd))
200 goto fail_0;
201 if ((kid = fork()) < 0)
202 goto fail_1;
203
204 if (kid == 0) {
205 close(fd[0]);
206 if (fd[1] != 1) {
207 dup2(fd[1], 1);
208 close(fd[1]);
209 }
210 environ = env_export(t);
211 execvp(argv[0], argv);
212 _exit(127);
213 }
214
215 d->len = l;
216 close(fd[1]);
217 for (;;) {
218 char buf[4096];
219 ssize_t n = read(fd[0], buf, sizeof(buf));
220 if (n <= 0)
221 break;
222 DPUTM(d, buf, n);
223 }
224 close(fd[0]);
225 waitpid(kid, 0, 0);
226 l = d->len;
227 while (l > 0 && d->buf[l - 1] == '\n')
228 l--;
229 d->len = l;
230 free(argv);
231 return (ENV_OK);
232
233 fail_1:
234 close(fd[0]);
235 close(fd[1]);
236 fail_0:
237 free(argv);
238 return (ENV_SYSTEM);
239 }
240
241 /* --- @env_value@ --- *
242 *
243 * Arguments: @sym_table *t@ = pointer to symbol table
244 * @FILE *fp@ = pointer to stream to read from
245 * @dstr *d@ = pointer to output variable
246 * @unsigned f@ = various interesting flags
247 *
248 * Returns: 0 if OK, @EOF@ if end-of-file encountered, or >0 on error.
249 *
250 * Use: Scans a value from the input stream. The value read may be
251 * quoted in a Bourne-shell sort of a way, and contain Bourne-
252 * shell-like parameter substitutions. Some substitutions
253 * aren't available because they're too awkward to implement.
254 */
255
256 int env_value(sym_table *t, FILE *fp, dstr *d, unsigned f)
257 {
258 enum { Q_NONE, Q_SINGLE, Q_DOUBLE, Q_BACK } qt = Q_NONE;
259 int ch;
260
261 do {
262 ch = getc(fp);
263 if (ch == EOF)
264 return (ENV_EOF);
265 } while ((f & EVF_INITSPC) && isspace((unsigned char)ch));
266
267 for (;; ch = getc(fp)) {
268
269 /* --- Sort out the current character --- */
270
271 if (ch == EOF) break;
272
273 /* --- A backslash escapes the next character --- */
274
275 if (ch == '\\') {
276 if ((ch = getc(fp)) == EOF) break;
277 else if (ch != '\n')
278 DPUTC(d, ch);
279 continue;
280 }
281
282 /* --- A single quote starts single-quoting, unless quoted --- *
283 *
284 * Do the single-quoted snarf here rather than fiddling with anything
285 * else.
286 */
287
288 if (ch == '\'' && qt == Q_NONE) {
289 qt = Q_SINGLE;
290 for (;;) {
291 if ((ch = getc(fp)) == EOF) goto done;
292 if (ch == '\'') break;
293 DPUTC(d, ch);
294 }
295 qt = Q_NONE;
296 continue;
297 }
298
299 /* --- A backtick does the obvious thing --- */
300
301 if (ch == '`' && !(f & EVF_BACKTICK)) {
302 int e;
303 if ((e = cmdsubst(t, fp, d, f | EVF_BACKTICK)) != ENV_OK)
304 return (e);
305 continue;
306 }
307
308 /* --- Handle double-quoted text --- */
309
310 if (ch == '\"') {
311 if (qt == Q_DOUBLE)
312 qt = Q_NONE;
313 else if (qt == Q_NONE)
314 qt = Q_DOUBLE;
315 else
316 return (ENV_INTERNAL);
317 continue;
318 }
319
320 /* --- Handle variable references and similar magic --- */
321
322 if (ch == '$') {
323 size_t l = d->len;
324 int e;
325 char *v;
326 char *vn;
327
328 /* --- Read one character ahead --- */
329
330 if ((ch = getc(fp)) == EOF) goto done;
331
332 /* --- An alphabetic means this is a direct reference --- */
333
334 if (ch == '_' || isalpha(ch)) {
335 ungetc(ch, fp);
336 if ((e = env_var(t, fp, d)) != ENV_OK) return (e);
337 d->len = l;
338 if ((v = env_get(t, d->buf + l)) != 0)
339 DPUTS(d, v);
340 }
341
342 /* --- A brace means this is a more complex substitution --- */
343
344 else if (ch == '{') {
345 if ((e = env_var(t, fp, d)) != ENV_OK)
346 return (e);
347 d->len = l;
348 v = env_get(t, d->buf + l);
349
350 again:
351 ch = getc(fp);
352 switch (ch) {
353
354 case EOF:
355 goto done;
356
357 case '}':
358 if (v)
359 DPUTS(d, v);
360 ungetc(ch, fp);
361 break;
362
363 case ':':
364 if (v && !*v)
365 v = 0;
366 goto again; /* `::' and `:}' should be errors */
367
368 case '+':
369 if (v)
370 v = 0;
371 else
372 v = "";
373 /* Drop through hackily */
374
375 case '-':
376 if (v) {
377 DPUTS(d, v);
378 l = d->len;
379 }
380 if ((e = env_value(t, fp, d, EVF_INCSPC)) != ENV_OK)
381 return (e);
382 if (v)
383 d->len = l;
384 break;
385
386 case '=':
387 if (v) {
388 DPUTS(d, v);
389 l = d->len;
390 if ((e = env_value(t, fp, d, EVF_INCSPC)) != ENV_OK)
391 return (e);
392 d->len = l;
393 } else {
394 vn = xstrdup(d->buf + l);
395 if ((e = env_value(t, fp, d, EVF_INCSPC)) != ENV_OK)
396 return (e);
397 if (!(f & EVF_SKIP))
398 env_put(t, vn, d->buf + l);
399 free(vn);
400 }
401 break;
402
403 default:
404 return (ENV_SUBST);
405 }
406 if (getc(fp) != '}')
407 return (ENV_BRACE);
408 }
409
410 /* --- Handle `$(...)'-style command substitution --- */
411
412 else if (ch == '(') {
413 if ((e = cmdsubst(t, fp, d, f & ~EVF_BACKTICK)) != ENV_OK)
414 return (e);
415 }
416
417 /* --- No other `$...' tricks implemented yet --- *
418 *
419 * Other ideas: `$((...))' arithmetic.
420 */
421
422 else
423 return (ENV_SUBST);
424 continue;
425 }
426
427 /* --- At this point, anything else double-quoted is munched --- */
428
429 if (qt == Q_DOUBLE) {
430 DPUTC(d, ch);
431 continue;
432 }
433
434 /* --- Some characters just aren't allowed unquoted --- *
435 *
436 * They're not an error; they just mean I should stop parsing. They're
437 * probably interesting to the next level up.
438 */
439
440 switch (ch) {
441 case '(': case ')':
442 case '{': case '}':
443 case ';':
444 ungetc(ch, fp);
445 goto done;
446 case '`':
447 if (f & EVF_BACKTICK) {
448 ungetc(ch, fp);
449 goto done;
450 }
451 break;
452 }
453
454 /* --- Whitespace characters --- *
455 *
456 * I might snarf them myself anyway, according to flags. Or I might
457 * stop, and skip any following whitespace
458 */
459
460 if (isspace((unsigned char)ch) && (f & EVF_INCSPC) == 0) {
461 do
462 ch = getc(fp);
463 while (ch != EOF && isspace((unsigned char)ch));
464 ungetc(ch, fp);
465 break;
466 }
467
468 /* --- Get a new character and go around again --- */
469
470 DPUTC(d, ch);
471 }
472
473 /* --- Tidying --- */
474
475 done:
476 DPUTZ(d);
477 return (qt == Q_NONE ? ENV_OK : ENV_QUOTE);
478 }
479
480 /* --- @env_read@ --- *
481 *
482 * Arguments: @sym_table *t@ = pointer to symbol table
483 * @FILE *fp@ = file handle to read
484 * @unsigned f@ = various flags
485 *
486 * Returns: Zero if OK, @EOF@ for end-of-file, or error code.
487 *
488 * Use: Reads the environment assignment statements in the file.
489 */
490
491 int env_read(sym_table *t, FILE *fp, unsigned f)
492 {
493 dstr n = DSTR_INIT, v = DSTR_INIT;
494 int e = ENV_OK, ch;
495
496 for (;;) {
497 ch = peek(fp);
498
499 if (ch == ':') {
500 getc(fp);
501 peek(fp);
502 if ((e = env_value(t, fp, &v, f)) != ENV_OK)
503 goto done;
504 }
505
506 else if (ch == '#')
507 do ch = getc(fp); while (ch != EOF && ch != '\n');
508
509 else if (peek(fp) == '}' ||
510 (e = env_var(t, fp, &n)) != ENV_OK)
511 goto done;
512
513 else if (strcmp(n.buf, "include") == 0) {
514 peek(fp);
515 if ((e = env_value(t, fp, &v, f)) != ENV_OK)
516 goto done;
517 if (!(f & EVF_SKIP))
518 env_file(t, v.buf);
519 }
520
521 else if (strcmp(n.buf, "arch") == 0) {
522 peek(fp);
523 if ((e = env_value(t, fp, &v, f)) != ENV_OK)
524 goto done;
525 if (peek(fp) != '{') {
526 e = ENV_BRACE;
527 goto done;
528 }
529 getc(fp);
530 e = env_read(t, fp, strcmp(v.buf, ARCH) ? f | EVF_SKIP : f);
531 if (e != ENV_OK)
532 goto done;
533 if (getc(fp) != '}') {
534 e = ENV_BRACE;
535 goto done;
536 }
537 }
538
539 else if (strcmp(n.buf, "unset") == 0) {
540 peek(fp);
541 if ((e = env_var(t, fp, &v)) != ENV_OK)
542 goto done;
543 env_put(t, v.buf, 0);
544 }
545
546 else {
547 if (strcmp(n.buf, "set") == 0) {
548 DRESET(&n);
549 peek(fp);
550 if ((e = env_var(t, fp, &n)) != ENV_OK)
551 goto done;
552 }
553 if (peek(fp) == '=') {
554 getc(fp);
555 peek(fp);
556 }
557 if ((e = env_value(t, fp, &v, f)) != ENV_OK)
558 goto done;
559
560 if (!(f & EVF_SKIP))
561 env_put(t, n.buf, v.buf);
562 }
563
564 if (peek(fp) == ';')
565 getc(fp);
566 DRESET(&n);
567 DRESET(&v);
568 }
569
570 done:
571 dstr_destroy(&n);
572 dstr_destroy(&v);
573 return (e);
574 }
575
576 /* --- @env_file@ --- *
577 *
578 * Arguments: @sym_table *t@ = pointer to symbol table
579 * @const char *name@ = pointer to filename
580 *
581 * Returns: Zero if OK, or an error code.
582 *
583 * Use: Reads a named file of environment assignments.
584 */
585
586 int env_file(sym_table *t, const char *name)
587 {
588 FILE *fp;
589 int e;
590
591 if ((fp = fopen(name, "r")) == 0)
592 return (ENV_SYSTEM);
593 e = env_read(t, fp, 0);
594 fclose(fp);
595 if (e == ENV_EOF)
596 e = ENV_OK;
597 else if (e == ENV_OK)
598 e = ENV_BRACE;
599 return (e);
600 }
601
602 /*----- That's all, folks -------------------------------------------------*/