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