chiark / gitweb /
Merge some changes from 1.0.4. Very odd.
[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 -------------------------------------------------*/