chiark / gitweb /
Stupid thing doesn't automatically distribute manpages.
[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 -------------------------------------------------*/