1 /* source: xio-progcall.c */
2 /* Copyright Gerhard Rieger and contributors (see file CHANGES) */
3 /* Published under the GNU General Public License V.2, see file COPYING */
5 /* this file contains common code dealing with program calls (exec, system) */
7 #include "xiosysincludes.h"
10 #include "xio-process.h"
11 #include "xio-progcall.h"
13 #include "xio-socket.h"
16 /* these options are used by address pty too */
18 const struct optdesc opt_openpty
= { "openpty", NULL
, OPT_OPENPTY
, GROUP_PTY
, PH_BIGEN
, TYPE_BOOL
, OFUNC_SPEC
};
19 #endif /* HAVE_OPENPTY */
20 #if HAVE_DEV_PTMX || HAVE_DEV_PTC
21 const struct optdesc opt_ptmx
= { "ptmx", NULL
, OPT_PTMX
, GROUP_PTY
, PH_BIGEN
, TYPE_BOOL
, OFUNC_SPEC
};
24 #if WITH_EXEC || WITH_SYSTEM
26 #define MAXPTYNAMELEN 64
28 const struct optdesc opt_fdin
= { "fdin", NULL
, OPT_FDIN
, GROUP_FORK
, PH_PASTBIGEN
, TYPE_USHORT
, OFUNC_SPEC
};
29 const struct optdesc opt_fdout
= { "fdout", NULL
, OPT_FDOUT
, GROUP_FORK
, PH_PASTBIGEN
, TYPE_USHORT
, OFUNC_SPEC
};
30 const struct optdesc opt_path
= { "path", NULL
, OPT_PATH
, GROUP_EXEC
, PH_PREEXEC
, TYPE_STRING
, OFUNC_SPEC
};
31 const struct optdesc opt_pipes
= { "pipes", NULL
, OPT_PIPES
, GROUP_FORK
, PH_BIGEN
, TYPE_BOOL
, OFUNC_SPEC
};
33 const struct optdesc opt_pty
= { "pty", NULL
, OPT_PTY
, GROUP_FORK
, PH_BIGEN
, TYPE_BOOL
, OFUNC_SPEC
};
35 const struct optdesc opt_stderr
= { "stderr", NULL
, OPT_STDERR
, GROUP_FORK
, PH_PASTFORK
, TYPE_BOOL
, OFUNC_SPEC
};
36 const struct optdesc opt_nofork
= { "nofork", NULL
, OPT_NOFORK
, GROUP_FORK
, PH_BIGEN
, TYPE_BOOL
, OFUNC_SPEC
};
37 const struct optdesc opt_sighup
= { "sighup", NULL
, OPT_SIGHUP
, GROUP_PARENT
, PH_LATE
, TYPE_CONST
, OFUNC_SIGNAL
, SIGHUP
};
38 const struct optdesc opt_sigint
= { "sigint", NULL
, OPT_SIGINT
, GROUP_PARENT
, PH_LATE
, TYPE_CONST
, OFUNC_SIGNAL
, SIGINT
};
39 const struct optdesc opt_sigquit
= { "sigquit", NULL
, OPT_SIGQUIT
, GROUP_PARENT
, PH_LATE
, TYPE_CONST
, OFUNC_SIGNAL
, SIGQUIT
};
42 /* fork for exec/system, but return before exec'ing.
43 return=0: is child process
44 return>0: is parent process
45 return<0: error occurred, assume parent process and no child exists !!!
47 int _xioopen_foxec(int xioflags
, /* XIO_RDONLY etc. */
50 struct opt
**copts
, /* in: opts; out: opts for child */
51 int *duptostderr
/* out: redirect stderr to output fd */
53 struct opt
*popts
; /* parent process options */
55 int d
, sv
[2], rdpip
[2], wrpip
[2];
56 int rw
= (xioflags
& XIO_ACCMODE
);
57 bool usepipes
= false;
59 int ptyfd
= -1, ttyfd
= -1;
60 bool usebestpty
= false; /* use the best available way to open pty */
61 #if defined(HAVE_DEV_PTMX) || defined(HAVE_DEV_PTC)
62 bool useptmx
= false; /* use /dev/ptmx or equivalent */
65 bool useopenpty
= false; /* try only openpty */
66 #endif /* HAVE_OPENPTY */
67 bool usepty
= false; /* any of the pty options is selected */
68 char ptyname
[MAXPTYNAMELEN
];
70 pid_t pid
= 0; /* mostly int */
71 short fdi
= 0, fdo
= 1;
73 bool withstderr
= false;
77 popts
= moveopts(*copts
, GROUP_ALL
);
78 if (applyopts_single(fd
, popts
, PH_INIT
) < 0) return -1;
79 applyopts2(-1, popts
, PH_INIT
, PH_EARLY
);
81 retropt_bool(popts
, OPT_NOFORK
, &nofork
);
84 retropt_bool(popts
, OPT_PIPES
, &usepipes
);
86 retropt_bool(popts
, OPT_PTY
, &usebestpty
);
88 retropt_bool(popts
, OPT_OPENPTY
, &useopenpty
);
90 #if defined(HAVE_DEV_PTMX) || defined(HAVE_DEV_PTC)
91 retropt_bool(popts
, OPT_PTMX
, &useptmx
);
97 #if defined(HAVE_DEV_PTMX) || defined(HAVE_DEV_PTC)
101 if (usepipes
&& usepty
) {
102 Warn("_xioopen_foxec(): options \"pipes\" and \"pty\" must not be specified together; ignoring \"pipes\"");
105 #endif /* HAVE_PTY */
107 if (retropt_ushort(popts
, OPT_FDIN
, (unsigned short *)&fdi
) >= 0) {
108 if ((xioflags
&XIO_ACCMODE
) == XIO_RDONLY
) {
109 Error("_xioopen_foxec(): option fdin is useless in read-only mode");
112 if (retropt_ushort(popts
, OPT_FDOUT
, (unsigned short *)&fdo
) >= 0) {
113 if ((xioflags
&XIO_ACCMODE
) == XIO_WRONLY
) {
114 Error("_xioopen_foxec(): option fdout is useless in write-only mode");
119 if (!(xioflags
&XIO_MAYCHILD
)) {
120 Error("cannot fork off child process here");
121 /*!! free something */
124 fd
->flags
|= XIO_DOESCHILD
;
127 Notice2("forking off child, using %s for %s",
128 &("socket\0\0pipes\0\0\0pty\0\0\0\0\0"[(usepipes
<<3)|(usepty
<<4)]),
131 Notice2("forking off child, using %s for %s",
132 &("socket\0\0pipes\0\0\0"[(usepipes
<<3)]),
134 #endif /* HAVE_PTY */
136 applyopts(-1, popts
, PH_PREBIGEN
);
139 /*0 struct single *stream1, *stream2;*/
141 if (!(xioflags
& XIO_MAYEXEC
/* means exec+nofork */)) {
142 Error("option nofork is not allowed here");
143 /*!! free something */
146 fd
->flags
|= XIO_DOESEXEC
;
149 *copts
= moveopts(popts
, GROUP_ALL
);
152 if (sock1
->tag
== XIO_TAG_DUAL
) {
153 stream1
= &sock1
->dual
.stream
[0]->stream
;
154 stream2
= &sock1
->dual
.stream
[1]->stream
;
156 stream1
= &sock1
->stream
;
157 stream2
= &sock1
->stream
;
159 if (stream1
->dtype
== DATA_READLINE
|| stream2
->dtype
== DATA_READLINE
||
160 stream1
->dtype
== DATA_OPENSSL
|| stream2
->dtype
== DATA_OPENSSL
162 Error("with option nofork, openssl and readline in address1 do not work");
164 if (stream1
->lineterm
!= LINETERM_RAW
||
165 stream2
->lineterm
!= LINETERM_RAW
||
166 stream1
->ignoreeof
|| stream2
->ignoreeof
) {
167 Warn("due to option nofork, address1 options for lineterm and igoreeof do not apply");
171 /* remember: fdin is the fd where the sub program reads from, thus it is
173 /*! problem: when fdi==WRFD(sock[0]) or fdo==RDFD(sock[0]) */
174 if (rw
!= XIO_WRONLY
) {
175 if (XIO_GETWRFD(sock
[0]/*!!*/) == fdo
) {
176 if (Fcntl_l(fdo
, F_SETFD
, 0) < 0) {
177 Warn2("fcntl(%d, F_SETFD, 0): %s", fdo
, strerror(errno
));
180 /* make sure that the internal diagnostic socket pair fds do not conflict
182 diag_reserve_fd(fdo
);
183 if (Dup2(XIO_GETWRFD(sock
[0]), fdo
) < 0) {
184 Error3("dup2(%d, %d): %s",
185 XIO_GETWRFD(sock
[0]), fdo
, strerror(errno
));
188 /*0 Info2("dup2(%d, %d)", XIO_GETRDFD(sock[0]), fdi);*/
190 if (rw
!= XIO_RDONLY
) {
191 if (XIO_GETRDFD(sock
[0]) == fdi
) {
192 if (Fcntl_l(fdi
, F_SETFD
, 0) < 0) {
193 Warn2("fcntl(%d, F_SETFD, 0): %s", fdi
, strerror(errno
));
196 /* make sure that the internal diagnostic socket pair fds do not conflict
198 diag_reserve_fd(fdi
);
199 if (Dup2(XIO_GETRDFD(sock
[0]), fdi
) < 0) {
200 Error3("dup2(%d, %d): %s)",
201 XIO_GETRDFD(sock
[0]), fdi
, strerror(errno
));
203 /*0 Info2("dup2(%d, %d)", XIO_GETWRFD(sock[0]), fdo);*/
210 #if defined(HAVE_DEV_PTMX)
211 # define PTMX "/dev/ptmx" /* Linux */
213 # define PTMX "/dev/ptc" /* AIX 4.3.3 */
215 fd
->dtype
= XIODATA_PTY
;
216 #if HAVE_DEV_PTMX || HAVE_DEV_PTC
217 if (usebestpty
|| useptmx
) {
218 if ((ptyfd
= Open(PTMX
, O_RDWR
|O_NOCTTY
, 0620)) < 0) {
219 Warn1("open(\""PTMX
"\", O_RDWR|O_NOCTTY, 0620): %s",
223 /*0 Info2("open(\"%s\", O_RDWR|O_NOCTTY, 0620) -> %d", PTMX, ptyfd);*/
225 if (ptyfd
>= 0 && ttyfd
< 0) {
227 /* we used PTMX before forking */
228 extern char *ptsname(int);
229 #if HAVE_GRANTPT /* AIX, not Linux */
230 if (Grantpt(ptyfd
)/*!*/ < 0) {
231 Warn2("grantpt(%d): %s", ptyfd
, strerror(errno
));
233 #endif /* HAVE_GRANTPT */
235 if (Unlockpt(ptyfd
)/*!*/ < 0) {
236 Warn2("unlockpt(%d): %s", ptyfd
, strerror(errno
));
238 #endif /* HAVE_UNLOCKPT */
239 #if HAVE_PROTOTYPE_LIB_ptsname /* AIX, not Linux */
240 if ((tn
= Ptsname(ptyfd
)) == NULL
) {
241 Warn2("ptsname(%d): %s", ptyfd
, strerror(errno
));
243 #endif /* HAVE_PROTOTYPE_LIB_ptsname */
245 if ((tn
= Ttyname(ptyfd
)) == NULL
) {
246 Error2("ttyname(%d): %s", ptyfd
, strerror(errno
));
249 ptyname
[0] = '\0'; strncat(ptyname
, tn
, MAXPTYNAMELEN
-1);
250 if ((ttyfd
= Open(tn
, O_RDWR
|O_NOCTTY
, 0620)) < 0) {
251 Warn2("open(\"%s\", O_RDWR|O_NOCTTY, 0620): %s", tn
, strerror(errno
));
253 /*0 Info2("open(\"%s\", O_RDWR|O_NOCTTY, 0620) -> %d", tn, ttyfd);*/
257 /* Linux: I_PUSH def'd; pty: ioctl(, I_FIND, ...) -> -1 EINVAL */
258 /* AIX: I_PUSH def'd; pty: ioctl(, I_FIND, ...) -> 1 */
259 /* SunOS: I_PUSH def'd; pty: ioctl(, I_FIND, ...) -> 0 */
260 /* HP-UX: I_PUSH def'd; pty: ioctl(, I_FIND, ...) -> 0 */
261 if (Ioctl(ttyfd
, I_FIND
, "ldterm\0") == 0) {
262 Ioctl(ttyfd
, I_PUSH
, "ptem\0\0\0"); /* 0 */ /* padding for AdressSanitizer */
263 Ioctl(ttyfd
, I_PUSH
, "ldterm\0"); /* 0 */
264 Ioctl(ttyfd
, I_PUSH
, "ttcompat"); /* HP-UX: -1 */
268 #if 0 /* the following block need not work */
270 if (ttyfd
>= 0 && ((tn
= Ttyname(ttyfd
)) == NULL
)) {
271 Warn2("ttyname(%d): %s", ttyfd
, strerror(errno
));
274 Error("could not open pty");
278 Info1("opened pseudo terminal %s", tn
);
281 #endif /* HAVE_DEV_PTMX || HAVE_DEV_PTC */
285 if ((result
= Openpty(&ptyfd
, &ttyfd
, ptyname
, NULL
, NULL
)) < 0) {
286 Error4("openpty(%p, %p, %p, NULL, NULL): %s",
287 &ptyfd
, &ttyfd
, ptyname
, strerror(errno
));
291 #endif /* HAVE_OPENPTY */
293 if ((*copts
= moveopts(popts
, GROUP_TERMIOS
|GROUP_FORK
|GROUP_EXEC
|GROUP_PROCESS
)) == NULL
) {
296 applyopts_cloexec(ptyfd
, popts
);/*!*/
297 /* exec:...,pty did not kill child process under some circumstances */
298 if (fd
->howtoend
== END_UNSPEC
) {
299 fd
->howtoend
= END_CLOSE_KILL
;
302 /* this for parent, was after fork */
303 applyopts(ptyfd
, popts
, PH_FD
);
304 applyopts(ptyfd
, popts
, PH_LATE
);
305 if (applyopts_single(fd
, popts
, PH_LATE
) < 0) return -1;
309 /* this for child, was after fork */
310 applyopts(ttyfd
, *copts
, PH_FD
);
312 #endif /* HAVE_PTY */
314 struct opt
*popts2
, *copts2
;
317 fd
->dtype
= XIODATA_2PIPE
;
318 if (rw
!= XIO_WRONLY
) {
319 if (Pipe(rdpip
) < 0) {
320 Error2("pipe(%p): %s", rdpip
, strerror(errno
));
324 /*0 Info2("pipe({%d,%d})", rdpip[0], rdpip[1]);*/
325 /* rdpip[0]: read by socat; rdpip[1]: write by child */
327 if ((*copts
= moveopts(popts
, GROUP_FORK
|GROUP_EXEC
|GROUP_PROCESS
))
332 popts2
= copyopts(popts
, GROUP_ALL
);
333 copts2
= copyopts(*copts
, GROUP_ALL
);
335 if (rw
!= XIO_WRONLY
) {
336 applyopts_cloexec(rdpip
[0], popts
);
337 applyopts(rdpip
[0], popts
, PH_FD
);
338 applyopts(rdpip
[1], *copts
, PH_FD
);
341 if (rw
!= XIO_RDONLY
) {
342 if (Pipe(wrpip
) < 0) {
343 Error2("pipe(%p): %s", wrpip
, strerror(errno
));
347 /*0 Info2("pipe({%d,%d})", wrpip[0], wrpip[1]);*/
349 /* wrpip[1]: write by socat; wrpip[0]: read by child */
350 if (rw
!= XIO_RDONLY
) {
351 applyopts_cloexec(wrpip
[1], popts2
);
352 applyopts(wrpip
[1], popts2
, PH_FD
);
353 applyopts(wrpip
[0], copts2
, PH_FD
);
355 if (fd
->howtoend
== END_UNSPEC
) {
356 fd
->howtoend
= END_CLOSE_KILL
;
359 /* this for parent, was after fork */
361 case XIO_RDONLY
: fd
->fd
= rdpip
[0]; break;
362 case XIO_WRONLY
: fd
->fd
= wrpip
[1]; break;
363 case XIO_RDWR
: fd
->fd
= rdpip
[0];
364 fd
->para
.exec
.fdout
= wrpip
[1];
367 applyopts(fd
->fd
, popts
, PH_FD
);
368 applyopts(fd
->fd
, popts
, PH_LATE
);
369 if (applyopts_single(fd
, popts
, PH_LATE
) < 0) return -1;
372 retropt_int(popts
, OPT_PROTOCOL_FAMILY
, &d
);
373 result
= xiosocketpair(popts
, d
, SOCK_STREAM
, 0, sv
);
377 /*0 Info5("socketpair(%d, %d, %d, {%d,%d})",
378 d, type, protocol, sv[0], sv[1]);*/
380 if ((*copts
= moveopts(popts
, GROUP_FORK
|GROUP_EXEC
|GROUP_PROCESS
)) == NULL
) {
383 applyopts(sv
[0], *copts
, PH_PASTSOCKET
);
384 applyopts(sv
[1], popts
, PH_PASTSOCKET
);
386 applyopts_cloexec(sv
[0], *copts
);
387 applyopts(sv
[0], *copts
, PH_FD
);
388 applyopts(sv
[1], popts
, PH_FD
);
390 applyopts(sv
[0], *copts
, PH_PREBIND
);
391 applyopts(sv
[0], *copts
, PH_BIND
);
392 applyopts(sv
[0], *copts
, PH_PASTBIND
);
393 applyopts(sv
[1], popts
, PH_PREBIND
);
394 applyopts(sv
[1], popts
, PH_BIND
);
395 applyopts(sv
[1], popts
, PH_PASTBIND
);
397 if (fd
->howtoend
== END_UNSPEC
) {
398 fd
->howtoend
= END_SHUTDOWN_KILL
;
401 /* this for parent, was after fork */
403 applyopts(fd
->fd
, popts
, PH_FD
);
404 applyopts(fd
->fd
, popts
, PH_LATE
);
405 if (applyopts_single(fd
, popts
, PH_LATE
) < 0) return -1;
407 /*0 if ((optpr = copyopts(*copts, GROUP_PROCESS)) == NULL)
409 retropt_bool(*copts
, OPT_STDERR
, &withstderr
);
411 xiosetchilddied(); /* set SIGCHLD handler */
414 pid
= xio_fork(true, E_ERROR
);
419 if (!withfork
|| pid
== 0) { /* child */
424 /* The child should have default handling for SIGCHLD. */
425 /* In particular, it's not defined whether ignoring SIGCHLD is inheritable. */
426 if (Signal(SIGCHLD
, SIG_DFL
) == SIG_ERR
) {
427 Warn1("signal(SIGCHLD, SIG_DFL): %s", strerror(errno
));
433 if (rw
!= XIO_RDONLY
&& fdi
!= ttyfd
) {
434 /* make sure that the internal diagnostic socket pair fds do not conflict
436 diag_reserve_fd(fdi
);
437 if (Dup2(ttyfd
, fdi
) < 0) {
438 Error3("dup2(%d, %d): %s", ttyfd
, fdi
, strerror(errno
));
440 /*0 Info2("dup2(%d, %d)", ttyfd, fdi);*/
442 if (rw
!= XIO_WRONLY
&& fdo
!= ttyfd
) {
443 /* make sure that the internal diagnostic socket pair fds do not conflict
445 diag_reserve_fd(fdo
);
446 if (Dup2(ttyfd
, fdo
) < 0) {
447 Error3("dup2(%d, %d): %s", ttyfd
, fdo
, strerror(errno
));
449 /*0 Info2("dup2(%d, %d)", ttyfd, fdo);*/
451 if ((rw
== XIO_RDONLY
|| fdi
!= ttyfd
) &&
452 (rw
== XIO_WRONLY
|| fdo
!= ttyfd
)) {
453 applyopts_cloexec(ttyfd
, *copts
);
456 applyopts(ttyfd
, *copts
, PH_LATE
);
458 applyopts(ttyfd
, *copts
, PH_LATE2
);
460 #endif /* HAVE_PTY */
462 /* we might have a temporary conflict between what FDs are
463 currently allocated, and which are to be used. We try to find
464 a graceful solution via temporary descriptors */
467 if (rw
!= XIO_WRONLY
) Close(rdpip
[0]);
468 if (rw
!= XIO_RDONLY
) Close(wrpip
[1]);
469 if (fdi
== rdpip
[1]) { /* a conflict here */
470 if ((tmpi
= Dup(wrpip
[0])) < 0) {
471 Error2("dup(%d): %s", wrpip
[0], strerror(errno
));
474 /*0 Info2("dup(%d) -> %d", wrpip[0], tmpi);*/
477 if (fdo
== wrpip
[0]) { /* a conflict here */
478 if ((tmpo
= Dup(rdpip
[1])) < 0) {
479 Error2("dup(%d): %s", rdpip
[1], strerror(errno
));
482 /*0 Info2("dup(%d) -> %d", rdpip[1], tmpo);*/
486 if (rw
!= XIO_WRONLY
&& rdpip
[1] != fdo
) {
487 /* make sure that the internal diagnostic socket pair fds do not conflict
489 diag_reserve_fd(fdo
);
490 if (Dup2(rdpip
[1], fdo
) < 0) {
491 Error3("dup2(%d, %d): %s", rdpip
[1], fdo
, strerror(errno
));
495 /*0 Info2("dup2(%d, %d)", rdpip[1], fdo);*/
496 /*0 applyopts_cloexec(fdo, *copts);*/
498 if (rw
!= XIO_RDONLY
&& wrpip
[0] != fdi
) {
499 /* make sure that the internal diagnostic socket pair fds do not conflict
501 diag_reserve_fd(fdi
);
502 if (Dup2(wrpip
[0], fdi
) < 0) {
503 Error3("dup2(%d, %d): %s", wrpip
[0], fdi
, strerror(errno
));
507 /*0 Info2("dup2(%d, %d)", wrpip[0], fdi);*/
508 /*0 applyopts_cloexec(wrpip[0], *copts);*/ /* option is already consumed! */
509 /* applyopts_cloexec(fdi, *copts);*/ /* option is already consumed! */
512 applyopts(fdi
, *copts
, PH_LATE
);
513 applyopts(fdo
, *copts
, PH_LATE
);
514 applyopts(fdi
, *copts
, PH_LATE2
);
515 applyopts(fdo
, *copts
, PH_LATE2
);
517 } else { /* socketpair */
519 if (rw
!= XIO_RDONLY
&& fdi
!= sv
[1]) {
520 /* make sure that the internal diagnostic socket pair fds do not conflict
522 diag_reserve_fd(fdi
);
523 if (Dup2(sv
[1], fdi
) < 0) {
524 Error3("dup2(%d, %d): %s", sv
[1], fdi
, strerror(errno
));
526 /*0 Info2("dup2(%d, %d)", sv[1], fdi);*/
528 if (rw
!= XIO_WRONLY
&& fdo
!= sv
[1]) {
529 /* make sure that the internal diagnostic socket pair fds do not conflict
531 diag_reserve_fd(fdo
);
532 if (Dup2(sv
[1], fdo
) < 0) {
533 Error3("dup2(%d, %d): %s", sv
[1], fdo
, strerror(errno
));
535 /*0 Info2("dup2(%d, %d)", sv[1], fdo);*/
537 if (fdi
!= sv
[1] && fdo
!= sv
[1]) {
538 applyopts_cloexec(sv
[1], *copts
);
542 applyopts(fdi
, *copts
, PH_LATE
);
543 applyopts(fdi
, *copts
, PH_LATE2
);
547 applyopts(-1, *copts
, PH_LATE
);
548 applyopts(-1, *copts
, PH_LATE2
);
550 _xioopen_setdelayeduser();
551 /* set group before user - maybe you are not permitted afterwards */
552 if (retropt_gidt(*copts
, OPT_SETGID
, &group
) >= 0) {
555 if (retropt_uidt(*copts
, OPT_SETUID
, &user
) >= 0) {
564 return 0; /* indicate child process */
567 /* for parent (this is our socat process) */
568 Notice1("forked off child process "F_pid
, pid
);
571 if ((popts
= copyopts(*copts
,
572 GROUP_FD
|GROUP_TERMIOS
|GROUP_FORK
|GROUP_SOCKET
|GROUP_SOCK_UNIX
|GROUP_FIFO
)) == NULL
)
573 return STAT_RETRYLATER
;
578 if (Close(ttyfd
) < 0) {
579 Info2("close(%d): %s", ttyfd
, strerror(errno
));
582 #endif /* HAVE_PTY */
584 if (rw
== XIO_RDONLY
) Close(rdpip
[1]);
585 if (rw
== XIO_WRONLY
) Close(wrpip
[0]);
589 fd
->para
.exec
.pid
= pid
;
591 if (applyopts_single(fd
, popts
, PH_LATE
) < 0) return -1;
592 applyopts_signal(fd
, popts
);
593 if ((numleft
= leftopts(popts
)) > 0) {
594 Error1("%d option(s) could not be used", numleft
);
599 return pid
; /* indicate parent (main) process */
601 #endif /* WITH_EXEC || WITH_SYSTEM */
604 int setopt_path(struct opt
*opts
, char **path
) {
605 if (retropt_string(opts
, OPT_PATH
, path
) >= 0) {
606 if (setenv("PATH", *path
, 1) < 0) {
607 Error1("setenv(\"PATH\", \"%s\", 1): insufficient space", *path
);