3315e8b3 |
1 | /* -*-c-*- |
2 | * |
9796a787 |
3 | * $Id: sw_env.c,v 1.3 2004/04/08 01:52:19 mdw Exp $ |
3315e8b3 |
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 | |
3315e8b3 |
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> |
436d5c73 |
48 | #include <mLib/env.h> |
3315e8b3 |
49 | #include <mLib/report.h> |
50 | #include <mLib/sym.h> |
51 | |
52 | #include "sw_env.h" |
53 | |
3315e8b3 |
54 | /*----- Main code ---------------------------------------------------------*/ |
55 | |
3315e8b3 |
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 -------------------------------------------------*/ |