Initial revision
[sw-tools] / src / sw_env.c
1 /* -*-c-*-
2 *
3 * $Id: sw_env.c,v 1.1 1999/06/02 16:53:35 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 /*----- Revision history --------------------------------------------------*
30 *
31 * $Log: sw_env.c,v $
32 * Revision 1.1 1999/06/02 16:53:35 mdw
33 * Initial revision
34 *
35 */
36
37 /*----- Header files ------------------------------------------------------*/
38
39 #include "config.h"
40
41 #include <ctype.h>
42 #include <errno.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46
47 #include <unistd.h>
48 #include <sys/wait.h>
49
50 #ifndef DECL_ENVIRON
51 extern char **environ;
52 #endif
53
54 #include <mLib/alloc.h>
55 #include <mLib/dstr.h>
56 #include <mLib/report.h>
57 #include <mLib/sym.h>
58
59 #include "sw_env.h"
60
61 /*----- Data structures ---------------------------------------------------*/
62
63 typedef struct var {
64 sym_base _base;
65 char *v;
66 } var;
67
68 /*----- Main code ---------------------------------------------------------*/
69
70 /* --- @env_get@ --- *
71 *
72 * Arguments: @sym_table *t@ = pointer to a symbol table
73 * @const char *name@ = pointer to variable name to look up
74 *
75 * Returns: Pointer to corresponding value string, or null.
76 *
77 * Use: Looks up an environment variable in the table and returns its
78 * value. If the variable can't be found, a null pointer is
79 * returned.
80 */
81
82 char *env_get(sym_table *t, const char *name)
83 {
84 var *e = sym_find(t, name, -1, 0, 0);
85 return (e ? e->v : 0);
86 }
87
88 /* --- @env_put@ --- *
89 *
90 * Arguments: @sym_table *t@ = pointer to a symbol table
91 * @const char *name@ = pointer to variable name to set
92 * @const char *value@ = pointer to value string to assign
93 *
94 * Returns: ---
95 *
96 * Use: Assigns a value to a variable. If the @name@ contains an
97 * equals character, then it's assumed to be of the form
98 * `VAR=VALUE' and @value@ argument is ignored. Otherwise, if
99 * @value@ is null, the variable is deleted. Finally, the
100 * normal case: @name@ is a plain name, and @value@ is a normal
101 * string causes the variable to be assigned the value in the
102 * way you'd expect.
103 */
104
105 void env_put(sym_table *t, const char *name, const char *value)
106 {
107 char *q = 0;
108
109 /* --- Sort out the mess with `NAME=VALUE' forms --- */
110
111 {
112 size_t eq = strcspn(name, "=");
113 if (name[eq] == '=') {
114 q = xmalloc(eq + 1);
115 memcpy(q, name, eq);
116 q[eq] = 0;
117 value = name + eq + 1;
118 name = q;
119 }
120 }
121
122 /* --- Read the current value --- */
123
124 if (!value) {
125 var *v;
126 if ((v = sym_find(t, name, -1, 0, 0)) != 0) {
127 free(v->v);
128 sym_remove(t, v);
129 }
130 } else {
131 unsigned found;
132 var *v = sym_find(t, name, -1, sizeof(*v), &found);
133 if (found)
134 free(v->v);
135 v->v = xstrdup(value);
136 }
137
138 /* --- Tidying --- */
139
140 if (q)
141 free(q);
142 }
143
144 /* --- @env_import@ --- *
145 *
146 * Arguments: @sym_table *t@ = pointer to a symbol table
147 * @char **env@ = pointer to an environment list
148 *
149 * Returns: ---
150 *
151 * Use: Inserts all of the environment variables listed into a symbol
152 * table for rapid access. Equivalent to a lot of calls to
153 * @env_put@.
154 */
155
156 void env_import(sym_table *t, char **env)
157 {
158 while (*env) {
159 env_put(t, *env, 0);
160 env++;
161 }
162 }
163
164 /* --- @env_export@ --- *
165 *
166 * Arguments: @sym_table *t@ = pointer to a symbol table
167 *
168 * Returns: A big environment list.
169 *
170 * Use: Extracts an environment table from a symbol table
171 * representation of an environment. The table and all of the
172 * strings are in one big block allocated from the heap.
173 */
174
175 char **env_export(sym_table *t)
176 {
177 size_t n = 1;
178 size_t sz = 0;
179 sym_iter i;
180 var *v;
181 char **env;
182 char *p, **pp;
183
184 /* --- Work out sizes for everything --- */
185
186 for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; ) {
187 n++;
188 sz += strlen(SYM_NAME(v)) + strlen(v->v) + 2;
189 }
190
191 /* --- Allocate the big chunk of memory --- */
192
193 env = pp = xmalloc(n * sizeof(char *) + sz);
194 p = (char *)(env + n);
195
196 /* --- Dump the output in the big chunk of memory --- */
197
198 for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; ) {
199 const char *name = SYM_NAME(v);
200 size_t nlen = strlen(name), vlen = strlen(v->v);
201 *pp++ = p;
202 memcpy(p, name, nlen); p += nlen;
203 *p++ = '=';
204 memcpy(p, v->v, vlen); p += vlen;
205 *p++ = 0;
206 }
207 *pp++ = 0;
208 return (env);
209 }
210
211 /* --- @env_destroy@ --- *
212 *
213 * Arguments: @sym_table *t@ = pointer to symbol table
214 *
215 * Returns: ---
216 *
217 * Use: Destroys all the variables in the symbol table.
218 */
219
220 void env_destroy(sym_table *t)
221 {
222 sym_iter i;
223 var *v;
224
225 for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; )
226 free(v->v);
227 sym_destroy(t);
228 }
229
230 /* --- @env_error@ --- *
231 *
232 * Arguments: @int e@ = error code
233 *
234 * Returns: String representation of error.
235 *
236 * Use: Transforms an error into something a user can understand.
237 */
238
239 const char *env_error(int e)
240 {
241 const char *tab[] = {
242 "Everything is fine",
243 "Unexpected end-of-file",
244 "Bad character in variable name",
245 "Bad parameter substitution",
246 "Mismatched quote",
247 "Missing or spurious `}'",
248 "<see errno>",
249 "Internal error"
250 };
251 if (e == ENV_SYSTEM)
252 return (strerror(errno));
253 else
254 return (tab[e]);
255 }
256
257 /* --- @peek@ --- *
258 *
259 * Arguments: @FILE *fp@ = stream to read from
260 *
261 * Returns: Next nonwhitespace character.
262 *
263 * Use: Advances the file position past whitespace characters, and
264 * returns the following nonwhitespace character. The character
265 * is not `read'.
266 */
267
268 static int peek(FILE *fp)
269 {
270 int ch;
271
272 do {
273 ch = getc(fp);
274 if (ch == EOF)
275 return (EOF);
276 } while (isspace((unsigned char)ch));
277 ungetc(ch, fp);
278 return (ch);
279 }
280
281 /* --- @env_var@ --- *
282 *
283 * Arguments: @sym_table *t@ = pointer to symbol table
284 * @FILE *fp@ = pointer to stream to read from
285 * @dstr *d@ = pointer to output variable
286 *
287 * Returns: One of the @ENV_@ constants.
288 *
289 * Use: Scans a variable name from the input stream.
290 */
291
292 int env_var(sym_table *t, FILE *fp, dstr *d)
293 {
294 int ch = getc(fp);
295
296 if (ch == EOF)
297 return (ENV_EOF);
298 if (ch != '_' && !isalpha((unsigned char)ch))
299 return (ENV_VCHAR);
300 for (;;) {
301 DPUTC(d, ch);
302 ch = getc(fp);
303 if (ch == EOF || (ch != '_' && !isalnum((unsigned char)ch)))
304 break;
305 }
306 ungetc(ch, fp);
307 DPUTZ(d);
308 return (ENV_OK);
309 }
310
311 /* --- @cmdsubst@ --- *
312 *
313 * Arguments: @sym_table *t@ = pointer to symbol table
314 * @FILE *fp@ = pointer to stream to read from
315 * @dstr *d@ = pointer to output variable
316 * @unsigned f@ = interesting flags
317 *
318 * Returns: An @ENV_@ magic code.
319 *
320 * Use: Rips a command line out of the input stream and writes the
321 * output of the command to the variable. The parsing has some
322 * bizarre artifacts, but it's fairly serviceable.
323 */
324
325 static int cmdsubst(sym_table *t, FILE *fp, dstr *d, unsigned f)
326 {
327 int term = (f & EVF_BACKTICK) ? '`' : ')';
328 int argc = 1;
329 size_t l = d->len;
330 pid_t kid;
331 int fd[2];
332 int e;
333 char **argv;
334
335 /* --- Snarfle the arguments --- */
336
337 f &= ~EVF_INCSPC;
338 while (peek(fp) != term) {
339 if ((e = env_value(t, fp, d, f)) != ENV_OK)
340 return (e);
341 DPUTC(d, 0);
342 argc++;
343 }
344 getc(fp);
345 if (argc == 1) {
346 d->len = l;
347 return (ENV_OK);
348 }
349
350 /* --- Make the @argv@ array --- */
351
352 {
353 char *p = d->buf + l;
354 char *lim = d->buf + d->len;
355 char **v;
356
357 v = argv = xmalloc(argc * sizeof(char *));
358 while (p < lim) {
359 *v++ = p;
360 while (*p) {
361 p++;
362 if (p >= lim)
363 goto done;
364 }
365 p++;
366 }
367 done:;
368 *v++ = 0;
369 }
370
371 /* --- Do the fork/exec thing --- */
372
373 if (pipe(fd))
374 goto fail_0;
375 if ((kid = fork()) < 0)
376 goto fail_1;
377
378 if (kid == 0) {
379 close(fd[0]);
380 if (fd[1] != 1) {
381 dup2(fd[1], 1);
382 close(fd[1]);
383 }
384 environ = env_export(t);
385 execvp(argv[0], argv);
386 _exit(127);
387 }
388
389 d->len = l;
390 close(fd[1]);
391 for (;;) {
392 char buf[4096];
393 ssize_t n = read(fd[0], buf, sizeof(buf));
394 if (n <= 0)
395 break;
396 DPUTM(d, buf, n);
397 }
398 close(fd[0]);
399 waitpid(kid, 0, 0);
400 l = d->len;
401 while (l > 0 && d->buf[l - 1] == '\n')
402 l--;
403 d->len = l;
404 free(argv);
405 return (ENV_OK);
406
407 fail_1:
408 close(fd[0]);
409 close(fd[1]);
410 fail_0:
411 free(argv);
412 return (ENV_SYSTEM);
413 }
414
415 /* --- @env_value@ --- *
416 *
417 * Arguments: @sym_table *t@ = pointer to symbol table
418 * @FILE *fp@ = pointer to stream to read from
419 * @dstr *d@ = pointer to output variable
420 * @unsigned f@ = various interesting flags
421 *
422 * Returns: 0 if OK, @EOF@ if end-of-file encountered, or >0 on error.
423 *
424 * Use: Scans a value from the input stream. The value read may be
425 * quoted in a Bourne-shell sort of a way, and contain Bourne-
426 * shell-like parameter substitutions. Some substitutions
427 * aren't available because they're too awkward to implement.
428 */
429
430 int env_value(sym_table *t, FILE *fp, dstr *d, unsigned f)
431 {
432 enum { Q_NONE, Q_SINGLE, Q_DOUBLE, Q_BACK } qt = Q_NONE;
433 int ch;
434
435 do {
436 ch = getc(fp);
437 if (ch == EOF)
438 return (ENV_EOF);
439 } while ((f & EVF_INITSPC) && isspace((unsigned char)ch));
440
441 for (;; ch = getc(fp)) {
442
443 /* --- Sort out the current character --- */
444
445 if (ch == EOF) break;
446
447 /* --- A backslash escapes the next character --- */
448
449 if (ch == '\\') {
450 if ((ch = getc(fp)) == EOF) break;
451 else if (ch != '\n')
452 DPUTC(d, ch);
453 continue;
454 }
455
456 /* --- A single quote starts single-quoting, unless quoted --- *
457 *
458 * Do the single-quoted snarf here rather than fiddling with anything
459 * else.
460 */
461
462 if (ch == '\'' && qt == Q_NONE) {
463 qt = Q_SINGLE;
464 for (;;) {
465 if ((ch = getc(fp)) == EOF) goto done;
466 if (ch == '\'') break;
467 DPUTC(d, ch);
468 }
469 qt = Q_NONE;
470 continue;
471 }
472
473 /* --- A backtick does the obvious thing --- */
474
475 if (ch == '`' && !(f & EVF_BACKTICK)) {
476 int e;
477 if ((e = cmdsubst(t, fp, d, f | EVF_BACKTICK)) != ENV_OK)
478 return (e);
479 continue;
480 }
481
482 /* --- Handle double-quoted text --- */
483
484 if (ch == '\"') {
485 if (qt == Q_DOUBLE)
486 qt = Q_NONE;
487 else if (qt == Q_NONE)
488 qt = Q_DOUBLE;
489 else
490 return (ENV_INTERNAL);
491 continue;
492 }
493
494 /* --- Handle variable references and similar magic --- */
495
496 if (ch == '$') {
497 size_t l = d->len;
498 int e;
499 char *v;
500 char *vn;
501
502 /* --- Read one character ahead --- */
503
504 if ((ch = getc(fp)) == EOF) goto done;
505
506 /* --- An alphabetic means this is a direct reference --- */
507
508 if (ch == '_' || isalpha(ch)) {
509 ungetc(ch, fp);
510 if ((e = env_var(t, fp, d)) != ENV_OK) return (e);
511 d->len = l;
512 if ((v = env_get(t, d->buf + l)) != 0)
513 DPUTS(d, v);
514 }
515
516 /* --- A brace means this is a more complex substitution --- */
517
518 else if (ch == '{') {
519 if ((e = env_var(t, fp, d)) != ENV_OK)
520 return (e);
521 d->len = l;
522 v = env_get(t, d->buf + l);
523
524 again:
525 ch = getc(fp);
526 switch (ch) {
527
528 case EOF:
529 goto done;
530
531 case '}':
532 if (v)
533 DPUTS(d, v);
534 ungetc(ch, fp);
535 break;
536
537 case ':':
538 if (v && !*v)
539 v = 0;
540 goto again; /* `::' and `:}' should be errors */
541
542 case '+':
543 if (v)
544 v = 0;
545 else
546 v = "";
547 /* Drop through hackily */
548
549 case '-':
550 if (v) {
551 DPUTS(d, v);
552 l = d->len;
553 }
554 if ((e = env_value(t, fp, d, EVF_INCSPC)) != ENV_OK)
555 return (e);
556 if (v)
557 d->len = l;
558 break;
559
560 case '=':
561 if (v) {
562 DPUTS(d, v);
563 l = d->len;
564 if ((e = env_value(t, fp, d, EVF_INCSPC)) != ENV_OK)
565 return (e);
566 d->len = l;
567 } else {
568 vn = xstrdup(d->buf + l);
569 if ((e = env_value(t, fp, d, EVF_INCSPC)) != ENV_OK)
570 return (e);
571 if (!(f & EVF_SKIP))
572 env_put(t, vn, d->buf + l);
573 free(vn);
574 }
575 break;
576
577 default:
578 return (ENV_SUBST);
579 }
580 if (getc(fp) != '}')
581 return (ENV_BRACE);
582 }
583
584 /* --- Handle `$(...)'-style command substitution --- */
585
586 else if (ch == '(') {
587 if ((e = cmdsubst(t, fp, d, f & ~EVF_BACKTICK)) != ENV_OK)
588 return (e);
589 }
590
591 /* --- No other `$...' tricks implemented yet --- *
592 *
593 * Other ideas: `$((...))' arithmetic.
594 */
595
596 else
597 return (ENV_SUBST);
598 continue;
599 }
600
601 /* --- At this point, anything else double-quoted is munched --- */
602
603 if (qt == Q_DOUBLE) {
604 DPUTC(d, ch);
605 continue;
606 }
607
608 /* --- Some characters just aren't allowed unquoted --- *
609 *
610 * They're not an error; they just mean I should stop parsing. They're
611 * probably interesting to the next level up.
612 */
613
614 switch (ch) {
615 case '(': case ')':
616 case '{': case '}':
617 case ';':
618 ungetc(ch, fp);
619 goto done;
620 case '`':
621 if (f & EVF_BACKTICK) {
622 ungetc(ch, fp);
623 goto done;
624 }
625 break;
626 }
627
628 /* --- Whitespace characters --- *
629 *
630 * I might snarf them myself anyway, according to flags. Or I might
631 * stop, and skip any following whitespace
632 */
633
634 if (isspace((unsigned char)ch) && (f & EVF_INCSPC) == 0) {
635 do
636 ch = getc(fp);
637 while (ch != EOF && isspace((unsigned char)ch));
638 ungetc(ch, fp);
639 break;
640 }
641
642 /* --- Get a new character and go around again --- */
643
644 DPUTC(d, ch);
645 }
646
647 /* --- Tidying --- */
648
649 done:
650 DPUTZ(d);
651 return (qt == Q_NONE ? ENV_OK : ENV_QUOTE);
652 }
653
654 /* --- @env_read@ --- *
655 *
656 * Arguments: @sym_table *t@ = pointer to symbol table
657 * @FILE *fp@ = file handle to read
658 * @unsigned f@ = various flags
659 *
660 * Returns: Zero if OK, @EOF@ for end-of-file, or error code.
661 *
662 * Use: Reads the environment assignment statements in the file.
663 */
664
665 int env_read(sym_table *t, FILE *fp, unsigned f)
666 {
667 dstr n = DSTR_INIT, v = DSTR_INIT;
668 int e = ENV_OK, ch;
669
670 for (;;) {
671 ch = peek(fp);
672
673 if (ch == ':') {
674 getc(fp);
675 peek(fp);
676 if ((e = env_value(t, fp, &v, f)) != ENV_OK)
677 goto done;
678 }
679
680 else if (ch == '#')
681 do ch = getc(fp); while (ch != EOF && ch != '\n');
682
683 else if (peek(fp) == '}' ||
684 (e = env_var(t, fp, &n)) != ENV_OK)
685 goto done;
686
687 else if (strcmp(n.buf, "include") == 0) {
688 peek(fp);
689 if ((e = env_value(t, fp, &v, f)) != ENV_OK)
690 goto done;
691 if (!(f & EVF_SKIP))
692 env_file(t, v.buf);
693 }
694
695 else if (strcmp(n.buf, "arch") == 0) {
696 peek(fp);
697 if ((e = env_value(t, fp, &v, f)) != ENV_OK)
698 goto done;
699 if (peek(fp) != '{') {
700 e = ENV_BRACE;
701 goto done;
702 }
703 getc(fp);
704 e = env_read(t, fp, strcmp(v.buf, ARCH) ? f | EVF_SKIP : f);
705 if (e != ENV_OK)
706 goto done;
707 if (getc(fp) != '}') {
708 e = ENV_BRACE;
709 goto done;
710 }
711 }
712
713 else if (strcmp(n.buf, "unset") == 0) {
714 peek(fp);
715 if ((e = env_var(t, fp, &v)) != ENV_OK)
716 goto done;
717 env_put(t, v.buf, 0);
718 }
719
720 else {
721 if (strcmp(n.buf, "set") == 0) {
722 DRESET(&n);
723 peek(fp);
724 if ((e = env_var(t, fp, &n)) != ENV_OK)
725 goto done;
726 }
727 if (peek(fp) == '=') {
728 getc(fp);
729 peek(fp);
730 }
731 if ((e = env_value(t, fp, &v, f)) != ENV_OK)
732 goto done;
733
734 if (!(f & EVF_SKIP))
735 env_put(t, n.buf, v.buf);
736 }
737
738 if (peek(fp) == ';')
739 getc(fp);
740 DRESET(&n);
741 DRESET(&v);
742 }
743
744 done:
745 dstr_destroy(&n);
746 dstr_destroy(&v);
747 return (e);
748 }
749
750 /* --- @env_file@ --- *
751 *
752 * Arguments: @sym_table *t@ = pointer to symbol table
753 * @const char *name@ = pointer to filename
754 *
755 * Returns: Zero if OK, or an error code.
756 *
757 * Use: Reads a named file of environment assignments.
758 */
759
760 int env_file(sym_table *t, const char *name)
761 {
762 FILE *fp;
763 int e;
764
765 if ((fp = fopen(name, "r")) == 0)
766 return (ENV_SYSTEM);
767 e = env_read(t, fp, 0);
768 fclose(fp);
769 if (e == ENV_EOF)
770 e = ENV_OK;
771 else if (e == ENV_OK)
772 e = ENV_BRACE;
773 return (e);
774 }
775
776 /*----- That's all, folks -------------------------------------------------*/