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 -------------------------------------------------*/