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