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