chiark / gitweb /
Various: Report errors encountered while doing channel I/O.
[fwd] / exec.c
1 /* -*-c-*-
2  *
3  * Source and target for executable programs
4  *
5  * (c) 1999 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the `fwd' port forwarder.
11  *
12  * `fwd' is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * `fwd' is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with `fwd'; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 #include "fwd.h"
28
29 /*----- Data structures ---------------------------------------------------*/
30
31 /* --- Resource usage --- */
32
33 #ifdef HAVE_SETRLIMIT
34
35 typedef struct xlimit {
36 #define R(r, n) struct rlimit n;
37 #include "rlimits.h"
38 } xlimit;
39
40 #endif
41
42 /* --- Environment variable modifications --- */
43
44 typedef struct xenv {
45   struct xenv *next;
46   unsigned act;
47   char *name;
48   char *value;
49 } xenv;
50
51 #define XEA_SET 0u
52 #define XEA_CLEAR 1u
53
54 /* --- Program options --- */
55
56 typedef struct xopts {
57   unsigned ref;
58   unsigned f;
59   const char *dir;
60   const char *root;
61   uid_t uid;
62   gid_t gid;
63   xenv *env;
64   xenv **etail;
65 #ifdef HAVE_SETRLIMIT
66   xlimit xl;
67 #endif
68 } xopts;
69
70 #define XF_NOLOG 1u
71
72 /* --- Executable program arguments --- */
73
74 typedef struct xargs {
75   unsigned ref;
76   char *file;
77   char *argv[1];
78 } xargs;
79
80 #define XARGS_SZ(n) (sizeof(xargs) + sizeof(char *) * (n))
81
82 /* --- Executable endpoints --- */
83
84 typedef struct xept {
85   endpt e;
86   struct xept *next, *prev;
87   pid_t kid;
88   endpt *f;
89   char *desc;
90   int st;
91   xargs *xa;
92   xopts *xo;
93   selbuf err;
94 } xept;
95
96 #define XEF_CLOSE 16u
97 #define XEF_EXIT 32u
98
99 /* --- Executable program data --- */
100
101 typedef struct xdata {
102   xargs *xa;
103   xopts *xo;
104 } xdata;
105
106 /* --- Executable source block --- */
107
108 typedef struct xsource {
109   source s;
110   xdata x;
111 } xsource;
112
113 /* --- Executable target block --- */
114
115 typedef struct xtarget {
116   target t;
117   xdata x;
118 } xtarget;
119
120 /*----- Static variables --------------------------------------------------*/
121
122 static xopts exec_opts = { 1, 0, 0, 0, -1, -1, 0, &exec_opts.env };
123 static xept *xept_list;
124 static sig xept_sig;
125 static sym_table env;
126
127 /*----- Fiddling about with resource limits -------------------------------*/
128
129 #ifdef HAVE_SETRLIMIT
130
131 /* --- Table mapping user-level names to OS interface bits --- */
132
133 typedef struct rlimit_ent {
134   const char *name;
135   const char *rname;
136   int r;
137   size_t off;
138 } rlimit_ent;
139
140 static rlimit_ent rlimits[] = {
141 #define R(r, n) { #n, #r, r, offsetof(xlimit, n) },
142 #include "rlimits.h"
143   { 0, 0, 0, 0 }
144 };
145
146 #define RLIMIT(xl, o) ((struct rlimit *)((char *)(xl) + (o)))
147
148 /* --- @rlimit_get@ --- *
149  *
150  * Arguments:   @xlimit *xl@ = pointer to limit structure
151  *
152  * Returns:     ---
153  *
154  * Use:         Initializes a limit structure from the current limits.
155  */
156
157 static void rlimit_get(xlimit *xl)
158 {
159   rlimit_ent *r;
160
161   for (r = rlimits; r->name; r++) {
162     if (getrlimit(r->r, RLIMIT(xl, r->off))) {
163       moan("couldn't read %s: %s", r->rname, strerror(errno));
164       _exit(1);
165     }
166   }
167 }
168
169 /* --- @rlimit_set@ --- *
170  *
171  * Arguments:   @xlimit *xl@ = pointer to limit structure
172  *
173  * Returns:     ---
174  *
175  * Use:         Sets resource limits from the supplied limits structure.
176  */
177
178 static void rlimit_set(xlimit *xl)
179 {
180   rlimit_ent *r;
181
182   for (r = rlimits; r->name; r++) {
183     if (setrlimit(r->r, RLIMIT(xl, r->off))) {
184       moan("couldn't set %s: %s", r->rname, strerror(errno));
185       _exit(1);
186     }
187   }
188 }
189
190 /* --- @rlimit_option@ --- */
191
192 static int rlimit_option(xlimit *xl, scanner *sc)
193 {
194   CONF_BEGIN(sc, "rlimit", "resource limit")
195   enum { w_soft, w_hard, w_both } which = w_both;
196   rlimit_ent *chosen;
197   struct rlimit *rl;
198   long v;
199
200   /* --- Find out which resource is being fiddled --- */
201
202   {
203     rlimit_ent *r;
204
205     chosen = 0;
206     for (r = rlimits; r->name; r++) {
207       if (strncmp(sc->d.buf, r->name, sc->d.len) == 0) {
208         if (r->name[sc->d.len] == 0) {
209           chosen = r;
210           break;
211         } else if (chosen)
212           error(sc, "ambiguous resource limit name `%s'", sc->d.buf);
213         else if (CONF_QUAL)
214           chosen = r;
215       }
216     }
217     if (!chosen)
218       CONF_REJECT;
219     token(sc);
220     rl = RLIMIT(xl, chosen->off);
221   }
222
223   /* --- Look for hard or soft restrictions --- */
224
225   {
226     int i;
227     if (sc->t == '.')
228       token(sc);
229     if (sc->t == CTOK_WORD) {
230       if ((i = conf_enum(sc, "soft,hard",
231                          ENUM_ABBREV | ENUM_NONE, "limit type")) != -1)
232         which = i;
233     }
234   }
235
236   /* --- Now read the new value --- */
237
238   if (sc->t == '=')
239     token(sc);
240   if (sc->t != CTOK_WORD)
241     error(sc, "parse error, expected limit value");
242
243   if (conf_enum(sc, "unlimited,infinity",
244                 ENUM_ABBREV | ENUM_NONE, "limit value") > -1)
245     v = RLIM_INFINITY;
246   else {
247     char *p;
248
249     v = strtol(sc->d.buf, &p, 0);
250     if (p == sc->d.buf)
251       error(sc, "parse error, invalid limit value `%s'", sc->d.buf);
252     switch (tolower((unsigned char)*p)) {
253       case 0: break;
254       case 'b': v *= 512; break;
255       case 'g': v *= 1024;
256       case 'm': v *= 1024;
257       case 'k': v *= 1024; break;
258       default: error(sc, "parse error, invalid limit scale `%c'", *p);
259     }
260     token(sc);
261   }
262
263   /* --- Store the limit value away --- */
264
265   switch (which) {
266     case w_both:
267       rl->rlim_cur = v;
268       rl->rlim_max = v;
269       break;
270     case w_soft:
271       if (v > rl->rlim_max)
272         error(sc, "soft limit %l exceeds hard limit %l for %s",
273               v, rl->rlim_max, chosen->rname);
274       rl->rlim_cur = v;
275       break;
276     case w_hard:
277       rl->rlim_max = v;
278       if (rl->rlim_cur > v)
279         rl->rlim_cur = v;
280       break;
281   }
282
283   CONF_ACCEPT;
284   CONF_END;
285 }
286
287 #endif
288
289 /*----- Environment fiddling ----------------------------------------------*/
290
291 /* --- @xenv_option@ --- *
292  *
293  * Arguments:   @xopts *xo@ = pointer to options block
294  *              @scanner *sc@ = pointer to scanner
295  *
296  * Returns:     Nonzero if claimed
297  *
298  * Use:         Parses environment variable assignments.
299  */
300
301 static int xenv_option(xopts *xo, scanner *sc)
302 {
303   CONF_BEGIN(sc, "env", "environment")
304   xenv *xe;
305
306   /* --- Unset a variable --- */
307
308   if (strcmp(sc->d.buf, "unset") == 0) {
309     token(sc);
310     if (sc->t != CTOK_WORD)
311       error(sc, "parse error, expected environment variable name");
312     xe = CREATE(xenv);
313     xe->name = xstrdup(sc->d.buf);
314     xe->value = 0;
315     xe->act = XEA_SET;
316     token(sc);
317     goto link;
318   }
319
320   /* --- Clear the entire environment --- */
321
322   if (strcmp(sc->d.buf, "clear") == 0) {
323     token(sc);
324     xe = CREATE(xenv);
325     xe->act = XEA_CLEAR;
326     goto link;
327   }
328
329   /* --- Allow `set' to be omitted if there's a prefix --- */
330
331   if (strcmp(sc->d.buf, "set") == 0)
332     token(sc);
333   else if (!CONF_QUAL)
334     CONF_REJECT;
335
336   /* --- Set a variable --- */
337
338   if (sc->t != CTOK_WORD)
339     error(sc, "parse error, expected environment variable name");
340   xe = CREATE(xenv);
341   xe->name = xstrdup(sc->d.buf);
342   token(sc);
343   if (sc->t == '=')
344     token(sc);
345   if (sc->t != CTOK_WORD)
346     error(sc, "parse error, expected environment variable value");
347   xe->value = xstrdup(sc->d.buf);
348   xe->act = XEA_SET;
349   token(sc);
350   goto link;
351
352 link:
353   xe->next = 0;
354   *xo->etail = xe;
355   xo->etail = &xe->next;
356   CONF_ACCEPT;
357
358   /* --- Nothing else to try --- */
359
360   CONF_END;
361 }
362
363 /* --- @xenv_apply@ --- *
364  *
365  * Arguments:   @xenv *xe@ = pointer to a variable change list
366  *
367  * Returns:     ---
368  *
369  * Use:         Modifies the environment (in @env@) according to the list.
370  */
371
372 static void xenv_apply(xenv *xe)
373 {
374   while (xe) {
375     switch (xe->act) {
376       case XEA_SET:
377         env_put(&env, xe->name, xe->value);
378         break;
379       case XEA_CLEAR:
380         env_destroy(&env);
381         sym_create(&env);
382         break;
383     }
384     xe = xe->next;
385   }
386 }
387
388 /* --- @xenv_destroy@ --- *
389  *
390  * Arguments:   @xenv *xe@ = pointer to a variable change list
391  *
392  * Returns:     ---
393  *
394  * Use:         Frees the memory used by an environment variable change list.
395  */
396
397 static void xenv_destroy(xenv *xe)
398 {
399   while (xe) {
400     xenv *xxe = xe;
401     xe = xe->next;
402     xfree(xxe->name);
403     if (xxe->value)
404       xfree(xxe->value);
405     DESTROY(xxe);
406   }
407 }
408
409 /*----- Miscellaneous good things -----------------------------------------*/
410
411 /* --- @x_tidy@ --- *
412  *
413  * Arguments:   @xargs *xa@ = pointer to an arguments block
414  *              @xopts *xo@ = pointer to an options block
415  *
416  * Returns:     ---
417  *
418  * Use:         Releases a reference to argument and options blocks.
419  */
420
421 static void x_tidy(xargs *xa, xopts *xo)
422 {
423   xa->ref--;
424   if (!xa->ref)
425     xfree(xa);
426
427   xo->ref--;
428   if (!xo->ref) {
429     xenv_destroy(xo->env);
430     DESTROY(xo);
431   }
432 }
433
434 /*----- Executable endpoints ----------------------------------------------*/
435
436 /* --- @attach@ --- */
437
438 static void xept_error(char */*p*/, size_t /*len*/, void */*v*/);
439
440 static void xept_attach(endpt *e, reffd *in, reffd *out)
441 {
442   xept *xe = (xept *)e;
443   pid_t kid;
444   int fd[2];
445
446   /* --- Make a pipe for standard error --- */
447
448   if (pipe(fd)) {
449     fw_log(-1, "[%s] couldn't create pipe: %s", xe->desc, strerror(errno));
450     return;
451   }
452   fdflags(fd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
453
454   /* --- Fork a child, and handle an error if there was one --- */
455
456   if ((kid = fork()) == -1) {
457     fw_log(-1, "[%s] couldn't fork: %s", xe->desc, strerror(errno));
458     close(fd[0]);
459     close(fd[1]);
460     return;
461   }
462
463   /* --- Do the child thing --- */
464
465   if (kid == 0) {
466     xopts *xo = xe->xo;
467     mdup_fd md[3];
468
469     /* --- Fiddle with the file descriptors --- *
470      *
471      * Attach the other endpoint's descriptors to standard input and output.
472      * Attach my pipe to standard error.  Mark everything as blocking and
473      * not-to-be-closed-on-exec at this end.
474      */
475
476     close(fd[0]);
477     md[0].cur = in->fd;  md[0].want = STDIN_FILENO;
478     md[1].cur = out->fd; md[1].want = STDOUT_FILENO;
479     md[2].cur = fd[1];   md[2].want = STDERR_FILENO;
480     if (mdup(md, 3)) {
481       moan("couldn't manipulate file descriptors: %s", strerror(errno));
482       _exit(1);
483     }
484
485     fdflags(STDIN_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
486     fdflags(STDOUT_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
487     fdflags(STDERR_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
488
489     /* --- First of all set the @chroot@ prison --- */
490
491     if (xo->root && chroot(xo->root)) {
492       moan("couldn't set `%s' as filesystem root: %s",
493            xo->root, strerror(errno));
494       _exit(1);
495     }
496
497     /* --- Now set the current directory --- */
498
499     if (xo->dir ? chdir(xo->dir) : xo->root ? chdir("/") : 0) {
500       moan("couldn't set `%s' as current directory: %s",
501            xo->dir ? xo->dir : "/", strerror(errno));
502       _exit(1);
503     }
504
505     /* --- Set the resource limits --- */
506
507 #ifdef HAVE_SETRLIMIT
508     rlimit_set(&xo->xl);
509 #endif
510
511     /* --- Set group id --- */
512
513     if (xo->gid != (gid_t)-1) {
514       if (setgid(xo->gid)) {
515         moan("couldn't set gid %i: %s", xo->gid, strerror(errno));
516         _exit(1);
517       }
518 #ifdef HAVE_SETGROUPS
519       if (setgroups(1, &xo->gid))
520         moan("warning: couldn't set group list to %i: %s", xo->gid,
521              strerror(errno));
522 #endif
523     }
524
525     /* --- Set uid --- */
526
527     if (xo->uid != (uid_t)-1) {
528       if (setuid(xo->uid)) {
529         moan("couldn't set uid %i: %s", xo->uid, strerror(errno));
530         _exit(1);
531       }
532     }
533
534     /* --- Play with signal dispositions --- */
535
536     signal(SIGPIPE, SIG_DFL);
537
538     /* --- Fiddle with the environment --- */
539
540     xenv_apply(exec_opts.env);
541     xenv_apply(xe->xo->env);
542     environ = env_export(&env);
543
544     /* --- Run the program --- */
545
546     execvp(xe->xa->file, xe->xa->argv);
547     moan("couldn't execute `%s': %s", xe->xa->file, strerror(errno));
548     _exit(127);
549   }
550
551   /* --- The child's done; see to the parent --- */
552
553   xe->kid = kid;
554   selbuf_init(&xe->err, sel, fd[0], xept_error, xe);
555   close(fd[1]);
556   xe->next = xept_list;
557   xe->prev = 0;
558   if (xept_list)
559     xept_list->prev = xe;
560   xept_list = xe;
561   if (!(xe->xo->f & XF_NOLOG))
562     fw_log(-1, "[%s] started with pid %i", xe->desc, kid);
563   fw_inc();
564   return;
565 }
566
567 /* --- @xept_file@ --- */
568
569 static void xept_file(endpt *e, endpt *f)
570 {
571   xept *xe = (xept *)e;
572   xe->f = f;
573 }
574
575 /* --- @xept_close@ --- */
576
577 static void xept_close(endpt *e)
578 {
579   xept *xe = (xept *)e;
580   if (xe->kid == -1) {
581     if (xe->f)
582       xe->f->ops->close(xe->f);
583     x_tidy(xe->xa, xe->xo);
584     DESTROY(xe);
585     fw_dec();
586   }
587 }
588
589 /* --- @xept_destroy@ --- */
590
591 static void xept_destroy(xept *xe)
592 {
593   /* --- First emit the news about the process --- */
594
595   if (xe->xo->f & XF_NOLOG)
596     /* Nothin' doin' */;
597   else if (WIFEXITED(xe->st)) {
598     if (WEXITSTATUS(xe->st) == 0)
599       fw_log(-1, "[%s] pid %i exited successfully", xe->desc, xe->kid);
600     else {
601       fw_log(-1, "[%s] pid %i failed: status %i",
602              xe->desc, xe->kid, WEXITSTATUS(xe->st));
603     }
604   } else if (WIFSIGNALED(xe->st)) {
605     const char *s;
606 #ifdef HAVE_STRSIGNAL
607     s = strsignal(WTERMSIG(xe->st));
608 #elif HAVE__SYS_SIGLIST
609     s = _sys_siglist[WTERMSIG(xe->st)];
610 #else
611     char buf[32];
612     sprintf(buf, "signal %i", WTERMSIG(xe->st));
613     s = buf;
614 #endif
615     fw_log(-1, "[%s] pid %i failed: %s", xe->desc, xe->kid, s);
616   } else
617     fw_log(-1, "[%s] pid %i failed: unrecognized status", xe->desc, xe->kid);
618
619   /* --- Free up the parent-side resources --- */
620
621   if (xe->next)
622     xe->next->prev = xe->prev;
623   if (xe->prev)
624     xe->prev->next = xe->next;
625   else
626     xept_list = xe->next;
627
628   xfree(xe->desc);
629   if (xe->f)
630     xe->f->ops->close(xe->f);
631   x_tidy(xe->xa, xe->xo);
632   fw_dec();
633   DESTROY(xe);
634 }
635
636 /* --- @xept_chld@ --- *
637  *
638  * Arguments:   @int n@ = signal number
639  *              @void *p@ = uninteresting pointer
640  *
641  * Returns:     ---
642  *
643  * Use:         Deals with child death situations.
644  */
645
646 static void xept_chld(int n, void *p)
647 {
648   pid_t kid;
649   int st;
650
651   while ((kid = waitpid(-1, &st, WNOHANG)) > 0) {
652     xept *xe = xept_list;
653     while (xe) {
654       xept *xxe = xe;
655       xe = xe->next;
656       if (kid == xxe->kid) {
657         xxe->st = st;
658         xxe->e.f |= XEF_EXIT;
659         if (xxe->e.f & XEF_CLOSE)
660           xept_destroy(xxe);
661         break;
662       }
663     }
664   }
665 }
666
667 /* --- @xept_error@ --- *
668  *
669  * Arguments:   @char *p@ = pointer to string read from stderr
670  *              @size_t len@ = length of the string
671  *              @void *v@ = pointer to by endpoint
672  *
673  * Returns:     ---
674  *
675  * Use:         Handles error reports from a child process.
676  */
677
678 static void xept_error(char *p, size_t len, void *v)
679 {
680   xept *xe = v;
681   if (p)
682     fw_log(-1, "[%s] pid %i: %s", xe->desc, xe->kid, p);
683   else {
684     close(xe->err.reader.fd);
685     selbuf_destroy(&xe->err);
686     xe->e.f |= XEF_CLOSE;
687     if (xe->e.f & XEF_EXIT)
688       xept_destroy(xe);
689   }
690 }
691
692 /* --- Endpoint operations --- */
693
694 static endpt_ops xept_ops = { xept_attach, xept_file, 0, xept_close };
695
696 /*----- General operations on sources and targets -------------------------*/
697
698 /* --- @exec_init@ --- *
699  *
700  * Arguments:   ---
701  *
702  * Returns:     ---
703  *
704  * Use:         Initializes the executable problem source and target.
705  */
706
707 void exec_init(void)
708 {
709 #ifdef HAVE_SETRLIMIT
710   rlimit_get(&exec_opts.xl);
711 #endif
712   sig_add(&xept_sig, SIGCHLD, xept_chld, 0);
713   sym_create(&env);
714   env_import(&env, environ);
715 }
716
717 /* --- @exec_option@ --- */
718
719 static int exec_option(xdata *x, scanner *sc)
720 {
721   xopts *xo = x ? x->xo : &exec_opts;
722
723   CONF_BEGIN(sc, "exec", "executable");
724
725   /* --- Logging settings --- */
726
727   if (strcmp(sc->d.buf, "logging") == 0 ||
728       strcmp(sc->d.buf, "log") == 0) {
729     token(sc);
730     if (sc->t == '=')
731       token(sc);
732     if (conf_enum(sc, "no,yes", ENUM_ABBREV, "logging status"))
733       xo->f &= ~XF_NOLOG;
734     else
735       xo->f |= XF_NOLOG;
736     CONF_ACCEPT;
737   }
738
739   /* --- Current directory setting --- *
740    *
741    * Lots of possibilities to guard against possible brainoes.
742    */
743
744   if (strcmp(sc->d.buf, "dir") == 0 ||
745       strcmp(sc->d.buf, "cd") == 0 ||
746       strcmp(sc->d.buf, "chdir") == 0 ||
747       strcmp(sc->d.buf, "cwd") == 0) {
748     dstr d = DSTR_INIT;
749     token(sc);
750     if (sc->t == '=')
751       token(sc);
752     conf_fname(sc, &d);
753     xo->dir = xstrdup(d.buf);
754     dstr_destroy(&d);
755     CONF_ACCEPT;
756   }
757
758   /* --- Set a chroot prison --- */
759
760   if (strcmp(sc->d.buf, "root") == 0 ||
761       strcmp(sc->d.buf, "chroot") == 0) {
762     dstr d = DSTR_INIT;
763     token(sc);
764     if (sc->t == '=')
765       token(sc);
766     conf_fname(sc, &d);
767     xo->root = xstrdup(d.buf);
768     dstr_destroy(&d);
769     CONF_ACCEPT;
770   }
771
772   /* --- Set the target user id --- */
773
774   if (strcmp(sc->d.buf, "uid") == 0 ||
775       strcmp(sc->d.buf, "user") == 0) {
776     token(sc);
777     if (sc->t == '=')
778       token(sc);
779     if (sc->t != CTOK_WORD)
780       error(sc, "parse error, expected user name or uid");
781     if (isdigit((unsigned char)*sc->d.buf))
782       xo->uid = atoi(sc->d.buf);
783     else {
784       struct passwd *pw = getpwnam(sc->d.buf);
785       if (!pw)
786         error(sc, "unknown user name `%s'", sc->d.buf);
787       xo->uid = pw->pw_uid;
788     }
789     token(sc);
790     CONF_ACCEPT;
791   }
792
793   /* --- Set the target group id --- */
794
795   if (strcmp(sc->d.buf, "gid") == 0 ||
796       strcmp(sc->d.buf, "group") == 0) {
797     token(sc);
798     if (sc->t == '=')
799       token(sc);
800     if (sc->t != CTOK_WORD)
801       error(sc, "parse error, expected group name or gid");
802     if (isdigit((unsigned char)*sc->d.buf))
803       xo->gid = atoi(sc->d.buf);
804     else {
805       struct group *gr = getgrnam(sc->d.buf);
806       if (!gr)
807         error(sc, "unknown user name `%s'", sc->d.buf);
808       xo->gid = gr->gr_gid;
809     }
810     token(sc);
811     CONF_ACCEPT;
812   }
813
814   /* --- Now try resource limit settings --- */
815
816 #ifdef HAVE_SETRLIMIT
817   if (rlimit_option(&xo->xl, sc))
818     CONF_ACCEPT;
819 #endif
820
821   /* --- And then environment settings --- */
822
823   if (xenv_option(xo, sc))
824     CONF_ACCEPT;
825
826   /* --- Nothing found --- */
827
828   CONF_END;
829 }
830
831 /* --- @exec_desc@ --- */
832
833 static void exec_desc(xdata *x, dstr *d)
834 {
835   char **p;
836   char sep = '[';
837   dstr_puts(d, "exec ");
838   if (strcmp(x->xa->file, x->xa->argv[0]) != 0) {
839     dstr_puts(d, x->xa->file);
840     dstr_putc(d, ' ');
841   }
842   for (p = x->xa->argv; *p; p++) {
843     dstr_putc(d, sep);
844     dstr_puts(d, *p);
845     sep = ' ';
846   }
847   dstr_putc(d, ']');
848   dstr_putz(d);
849 }
850
851 /* --- @exec_read@ --- */
852
853 static void exec_read(xdata *x, scanner *sc)
854 {
855   size_t base = 0;
856   dstr d = DSTR_INIT;
857   xargs *xa;
858
859   /* --- Read the first word --- *
860    *
861    * This is either a shell command or the actual program to run.
862    */
863
864   if (sc->t == CTOK_WORD) {
865     dstr_putd(&d, &sc->d); d.len++;
866     base = d.len;
867     token(sc);
868   }
869
870   /* --- See if there's a list of arguments --- *
871    *
872    * If not, then the thing I saw was a shell command, so build the proper
873    * arguments for that.
874    */
875
876   if (sc->t != '[') {
877     char *p;
878     if (!base)
879       error(sc, "parse error, expected shell command or argument list");
880     xa = xmalloc(XARGS_SZ(3) + 8 + 3 + d.len);
881     p = (char *)(xa->argv + 4);
882     xa->ref = 1;
883     xa->file = p;
884     xa->argv[0] = p; memcpy(p, "/bin/sh", 8); p += 8;
885     xa->argv[1] = p; memcpy(p, "-c", 3); p += 3;
886     xa->argv[2] = p; memcpy(p, d.buf, d.len); p += d.len;
887     xa->argv[3] = 0;
888   }
889
890   /* --- Snarf in a list of arguments --- */
891
892   else {
893     int argc = 0;
894     char *p, *q;
895     char **v;
896
897     /* --- Strip off the leading `[' --- *
898      *
899      * Allow various handy filename characters to be entered without quoting.
900      */
901
902     conf_undelim(sc, "=:/.", "=:/.");
903     token(sc);
904
905     /* --- Read a sequence of arguments --- */
906
907     while (sc->t == CTOK_WORD) {
908       dstr_putd(&d, &sc->d); d.len++;
909       token(sc);
910       argc++;
911     }
912     conf_undelim(sc, 0, 0);
913
914     /* --- Expect the closing `]' --- */
915
916     if (sc->t != ']')
917       error(sc, "parse error, missing `]'");
918     token(sc);
919
920     /* --- If there are no arguments, whinge --- */
921
922     if (!argc)
923       error(sc, "must specify at least one argument");
924
925     /* --- Allocate a lump of memory for the array --- */
926
927     xa = xmalloc(XARGS_SZ(argc) + d.len);
928     xa->ref = 1;
929     v = xa->argv;
930     p = (char *)(v + argc + 1);
931     memcpy(p, d.buf, d.len);
932     q = p + d.len;
933     xa->file = p;
934     p += base;
935
936     /* --- Start dumping addresses into the @argv@ array --- */
937
938     for (;;) {
939       *v++ = p;
940       while (*p++ && p < q)
941         ;
942       if (p >= q)
943         break;
944     }
945     *v++ = 0;
946   }
947
948   /* --- Do some other setting up --- */
949
950   dstr_destroy(&d);
951   x->xa = xa;
952   x->xo = CREATE(xopts);
953   *x->xo = exec_opts;
954   x->xo->ref = 1;
955   return;
956 }
957
958 /* --- @exec_endpt@ --- */
959
960 static endpt *exec_endpt(xdata *x, const char *desc)
961 {
962   xept *xe = CREATE(xept);
963   xe->e.ops = &xept_ops;
964   xe->e.other = 0;
965   xe->e.t = 0;
966   xe->e.f = 0;
967   xe->xa = x->xa; xe->xa->ref++;
968   xe->xo = x->xo; xe->xo->ref++;
969   xe->kid = -1;
970   xe->f = 0;
971   xe->desc = xstrdup(desc);
972   return (&xe->e);
973 }
974
975 /* --- @exec_destroy@ --- */
976
977 static void exec_destroy(xdata *x)
978 {
979   x_tidy(x->xa, x->xo);
980 }
981
982 /*----- Source definition -------------------------------------------------*/
983
984 /* --- @option@ --- */
985
986 static int xsource_option(source *s, scanner *sc)
987 {
988   xsource *xs = (xsource *)s;
989   return (exec_option(xs ? &xs->x : 0, sc));
990 }
991
992 /* --- @read@ --- */
993
994 static source *xsource_read(scanner *sc)
995 {
996   xsource *xs;
997
998   if (!conf_prefix(sc, "exec"))
999     return (0);
1000   xs = CREATE(xsource);
1001   xs->s.ops = &xsource_ops;
1002   xs->s.desc = 0;
1003   exec_read(&xs->x, sc);
1004   return (&xs->s);
1005 }
1006
1007 /* --- @attach@ --- */
1008
1009 static void xsource_destroy(source */*s*/);
1010
1011 static void xsource_attach(source *s, scanner *sc, target *t)
1012 {
1013   xsource *xs = (xsource *)s;
1014   endpt *e, *ee;
1015
1016   /* --- Set up the source description string --- */
1017
1018   {
1019     dstr d = DSTR_INIT;
1020     exec_desc(&xs->x, &d);
1021     dstr_puts(&d, " -> ");
1022     dstr_puts(&d, t->desc);
1023     xs->s.desc = xstrdup(d.buf);
1024     dstr_destroy(&d);
1025   }
1026
1027   /* --- Create the endpoints --- */
1028
1029   if ((ee = t->ops->create(t, xs->s.desc)) == 0)
1030     goto tidy;
1031   if ((e = exec_endpt(&xs->x, xs->s.desc)) == 0) {
1032     ee->ops->close(ee);
1033     goto tidy;
1034   }
1035   endpt_join(e, ee, xs->s.desc);
1036
1037   /* --- Dispose of source and target --- */
1038
1039 tidy:
1040   t->ops->destroy(t);
1041   xsource_destroy(&xs->s);
1042 }
1043
1044 /* --- @destroy@ --- */
1045
1046 static void xsource_destroy(source *s)
1047 {
1048   xsource *xs = (xsource *)s;
1049   xfree(xs->s.desc);
1050   exec_destroy(&xs->x);
1051   DESTROY(xs);
1052 }
1053
1054 /* --- Executable source operations --- */
1055
1056 source_ops xsource_ops = {
1057   "exec",
1058   xsource_option, xsource_read, xsource_attach, xsource_destroy
1059 };
1060
1061 /*----- Exec target description -------------------------------------------*/
1062
1063 /* --- @option@ --- */
1064
1065 static int xtarget_option(target *t, scanner *sc)
1066 {
1067   xtarget *xt = (xtarget *)t;
1068   return (exec_option(xt ? &xt->x : 0, sc));
1069 }
1070
1071 /* --- @read@ --- */
1072
1073 static target *xtarget_read(scanner *sc)
1074 {
1075   xtarget *xt;
1076   dstr d = DSTR_INIT;
1077
1078   if (!conf_prefix(sc, "exec"))
1079     return (0);
1080   xt = CREATE(xtarget);
1081   xt->t.ops = &xtarget_ops;
1082   exec_read(&xt->x, sc);
1083   exec_desc(&xt->x, &d);
1084   xt->t.desc = xstrdup(d.buf);
1085   dstr_destroy(&d);
1086   return (&xt->t);
1087 }
1088
1089 /* --- @create@ --- */
1090
1091 static endpt *xtarget_create(target *t, const char *desc)
1092 {
1093   xtarget *xt = (xtarget *)t;
1094   endpt *e = exec_endpt(&xt->x, desc);
1095   return (e);
1096 }
1097
1098 /* --- @destroy@ --- */
1099
1100 static void xtarget_destroy(target *t)
1101 {
1102   xtarget *xt = (xtarget *)t;
1103   xfree(xt->t.desc);
1104   exec_destroy(&xt->x);
1105   DESTROY(xt);
1106 }
1107
1108 /* --- Exec target operations --- */
1109
1110 target_ops xtarget_ops = {
1111   "exec",
1112   xtarget_option, xtarget_read, 0, xtarget_create, xtarget_destroy
1113 };
1114
1115 /*----- That's all, folks -------------------------------------------------*/