Bump S-nail v14.9.0-pre4, 2017-04-13
[s-mailx.git] / popen.c
blobd1312ceaf8472062486b6054c4182ba0f6c28a74
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Handling of pipes, child processes, temporary files, file enwrapping.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE popen
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <sys/wait.h>
44 #define READ 0
45 #define WRITE 1
47 struct fp {
48 struct fp *link;
49 int omode;
50 int pid;
51 enum {
52 FP_RAW = 0,
53 FP_GZIP = 1<<0,
54 FP_XZ = 1<<1,
55 FP_BZIP2 = 1<<2,
56 FP_MAILDIR = 1<<4,
57 FP_HOOK = 1<<5,
58 FP_PIPE = 1<<6,
59 FP_MASK = (1<<7) - 1,
60 /* TODO FP_UNLINK: should be in a separated process so that unlinking
61 * TODO the temporary "garbage" is "safe"(r than it is like that) */
62 FP_UNLINK = 1<<9,
63 FP_TERMIOS = 1<<10
64 } flags;
65 long offset;
66 FILE *fp;
67 char *realfile;
68 char *save_cmd;
69 struct termios *fp_tios;
70 n_sighdl_t fp_osigint; /* Only if FP_TERMIOS */
73 struct child {
74 int pid;
75 char done;
76 char free;
77 int status;
78 struct child *link;
81 static struct fp *fp_head;
82 static struct child *_popen_child;
84 /* TODO Rather temporary: deal with job control with FD_PASS */
85 static struct termios a_popen_tios;
86 static sighandler_type a_popen_otstp, a_popen_ottin, a_popen_ottou;
87 static volatile int a_popen_hadsig;
89 static int scan_mode(char const *mode, int *omode);
90 static void register_file(FILE *fp, int omode, int pid,
91 int flags, char const *realfile, long offset,
92 char const *save_cmd, struct termios *tiosp,
93 n_sighdl_t osigint);
94 static enum okay _file_save(struct fp *fpp);
95 static int _file_load(int flags, int infd, int outfd,
96 char const *load_cmd);
97 static enum okay unregister_file(FILE *fp, struct termios **tiosp,
98 n_sighdl_t *osigint);
99 static int file_pid(FILE *fp);
101 /* TODO Rather temporary: deal with job control with FD_PASS */
102 static void a_popen_jobsigs_up(void);
103 static void a_popen_jobsigs_down(void);
104 static void a_popen_jobsig(int sig);
106 /* Handle SIGCHLD */
107 static void a_popen_sigchld(int signo);
109 static struct child *a_popen_child_find(int pid, bool_t create);
110 static void a_popen_child_del(struct child *cp);
112 static int
113 scan_mode(char const *mode, int *omode)
115 static struct {
116 char const mode[4];
117 int omode;
118 } const maps[] = {
119 {"r", O_RDONLY},
120 {"w", O_WRONLY | O_CREAT | n_O_NOFOLLOW | O_TRUNC},
121 {"wx", O_WRONLY | O_CREAT | O_EXCL},
122 {"a", O_WRONLY | O_APPEND | O_CREAT | n_O_NOFOLLOW},
123 {"a+", O_RDWR | O_APPEND},
124 {"r+", O_RDWR},
125 {"w+", O_RDWR | O_CREAT | O_EXCL}
128 int i;
129 NYD2_ENTER;
131 for (i = 0; UICMP(z, i, <, n_NELEM(maps)); ++i)
132 if (!strcmp(maps[i].mode, mode)) {
133 *omode = maps[i].omode;
134 i = 0;
135 goto jleave;
138 n_alert(_("Internal error: bad stdio open mode %s"), mode);
139 n_err_no = n_ERR_INVAL;
140 *omode = 0; /* (silence CC) */
141 i = -1;
142 jleave:
143 NYD2_LEAVE;
144 return i;
147 static void
148 register_file(FILE *fp, int omode, int pid, int flags,
149 char const *realfile, long offset, char const *save_cmd,
150 struct termios *tiosp, n_sighdl_t osigint)
152 struct fp *fpp;
153 NYD_ENTER;
155 assert(!(flags & FP_UNLINK) || realfile != NULL);
156 assert(!(flags & FP_TERMIOS) || tiosp != NULL);
158 fpp = smalloc(sizeof *fpp);
159 fpp->fp = fp;
160 fpp->omode = omode;
161 fpp->pid = pid;
162 fpp->link = fp_head;
163 fpp->flags = flags;
164 fpp->realfile = (realfile != NULL) ? sstrdup(realfile) : NULL;
165 fpp->save_cmd = (save_cmd != NULL) ? sstrdup(save_cmd) : NULL;
166 fpp->fp_tios = tiosp;
167 fpp->fp_osigint = osigint;
168 fpp->offset = offset;
169 fp_head = fpp;
170 NYD_LEAVE;
173 static enum okay
174 _file_save(struct fp *fpp)
176 char const *cmd[3];
177 int outfd;
178 enum okay rv;
179 NYD_ENTER;
181 if (fpp->omode == O_RDONLY) {
182 rv = OKAY;
183 goto jleave;
185 rv = STOP;
187 fflush(fpp->fp);
188 clearerr(fpp->fp);
190 /* Ensure the I/O library doesn't optimize the fseek(3) away! */
191 if(!n_real_seek(fpp->fp, fpp->offset, SEEK_SET)){
192 outfd = n_err_no;
193 n_err(_("Fatal: cannot restore file position and save %s: %s\n"),
194 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
195 goto jleave;
198 if ((fpp->flags & FP_MASK) == FP_MAILDIR) {
199 rv = maildir_append(fpp->realfile, fpp->fp, fpp->offset);
200 goto jleave;
203 outfd = open(fpp->realfile,
204 ((fpp->omode | O_CREAT | (fpp->omode & O_APPEND ? 0 : O_TRUNC) |
205 n_O_NOFOLLOW) & ~O_EXCL), 0666);
206 if (outfd == -1) {
207 outfd = n_err_no;
208 n_err(_("Fatal: cannot create %s: %s\n"),
209 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
210 goto jleave;
213 cmd[2] = NULL;
214 switch (fpp->flags & FP_MASK) {
215 case FP_GZIP:
216 cmd[0] = "gzip"; cmd[1] = "-c"; break;
217 case FP_BZIP2:
218 cmd[0] = "bzip2"; cmd[1] = "-c"; break;
219 case FP_XZ:
220 cmd[0] = "xz"; cmd[1] = "-c"; break;
221 default:
222 cmd[0] = "cat"; cmd[1] = NULL; break;
223 case FP_HOOK:
224 cmd[0] = ok_vlook(SHELL);
225 cmd[1] = "-c";
226 cmd[2] = fpp->save_cmd;
228 if (n_child_run(cmd[0], 0, fileno(fpp->fp), outfd,
229 cmd[1], cmd[2], NULL, NULL) >= 0)
230 rv = OKAY;
232 close(outfd);
233 jleave:
234 NYD_LEAVE;
235 return rv;
238 static int
239 _file_load(int flags, int infd, int outfd, char const *load_cmd)
241 char const *cmd[3];
242 int rv;
243 NYD_ENTER;
245 cmd[2] = NULL;
246 switch (flags & FP_MASK) {
247 case FP_GZIP: cmd[0] = "gzip"; cmd[1] = "-cd"; break;
248 case FP_BZIP2: cmd[0] = "bzip2"; cmd[1] = "-cd"; break;
249 case FP_XZ: cmd[0] = "xz"; cmd[1] = "-cd"; break;
250 default: cmd[0] = "cat"; cmd[1] = NULL; break;
251 case FP_HOOK:
252 cmd[0] = ok_vlook(SHELL);
253 cmd[1] = "-c";
254 cmd[2] = load_cmd;
255 break;
256 case FP_MAILDIR:
257 rv = 0;
258 goto jleave;
261 rv = n_child_run(cmd[0], 0, infd, outfd, cmd[1], cmd[2], NULL, NULL);
262 jleave:
263 NYD_LEAVE;
264 return rv;
267 static enum okay
268 unregister_file(FILE *fp, struct termios **tiosp, n_sighdl_t *osigint)
270 struct fp **pp, *p;
271 enum okay rv = OKAY;
272 NYD_ENTER;
274 if (tiosp)
275 *tiosp = NULL;
277 for (pp = &fp_head; (p = *pp) != NULL; pp = &p->link)
278 if (p->fp == fp) {
279 switch (p->flags & FP_MASK) {
280 case FP_RAW:
281 case FP_PIPE:
282 break;
283 default:
284 rv = _file_save(p);
285 break;
287 if ((p->flags & FP_UNLINK) && unlink(p->realfile))
288 rv = STOP;
290 *pp = p->link;
291 if (p->save_cmd != NULL)
292 free(p->save_cmd);
293 if (p->realfile != NULL)
294 free(p->realfile);
295 if (p->flags & FP_TERMIOS) {
296 if (tiosp != NULL) {
297 *tiosp = p->fp_tios;
298 *osigint = p->fp_osigint;
299 } else
300 free(p->fp_tios);
302 free(p);
303 goto jleave;
305 DBGOR(n_panic, n_alert)(_("Invalid file pointer"));
306 rv = STOP;
307 jleave:
308 NYD_LEAVE;
309 return rv;
312 static int
313 file_pid(FILE *fp)
315 int rv;
316 struct fp *p;
317 NYD2_ENTER;
319 rv = -1;
320 for (p = fp_head; p; p = p->link)
321 if (p->fp == fp) {
322 rv = p->pid;
323 break;
325 NYD2_LEAVE;
326 return rv;
329 static void
330 a_popen_jobsigs_up(void){
331 sigset_t nset, oset;
332 NYD2_ENTER;
334 sigfillset(&nset);
336 sigprocmask(SIG_BLOCK, &nset, &oset);
337 a_popen_otstp = safe_signal(SIGTSTP, &a_popen_jobsig);
338 a_popen_ottin = safe_signal(SIGTTIN, &a_popen_jobsig);
339 a_popen_ottou = safe_signal(SIGTTOU, &a_popen_jobsig);
341 /* This assumes oset contains nothing but SIGCHLD, so to say */
342 sigdelset(&oset, SIGTSTP);
343 sigdelset(&oset, SIGTTIN);
344 sigdelset(&oset, SIGTTOU);
345 sigprocmask(SIG_SETMASK, &oset, NULL);
346 NYD2_LEAVE;
349 static void
350 a_popen_jobsigs_down(void){
351 sigset_t nset, oset;
352 NYD2_ENTER;
354 sigfillset(&nset);
356 sigprocmask(SIG_BLOCK, &nset, &oset);
357 safe_signal(SIGTSTP, a_popen_otstp);
358 safe_signal(SIGTTIN, a_popen_ottin);
359 safe_signal(SIGTTOU, a_popen_ottou);
361 sigaddset(&oset, SIGTSTP);
362 sigaddset(&oset, SIGTTIN);
363 sigaddset(&oset, SIGTTOU);
364 sigprocmask(SIG_SETMASK, &oset, NULL);
365 NYD2_LEAVE;
368 static void
369 a_popen_jobsig(int sig){
370 sighandler_type oldact;
371 sigset_t nset;
372 bool_t hadsig;
373 NYD_X; /* Signal handler */
375 hadsig = (a_popen_hadsig != 0);
376 a_popen_hadsig = 1;
377 if(!hadsig)
378 n_TERMCAP_SUSPEND(TRU1);
380 oldact = safe_signal(sig, SIG_DFL);
382 sigemptyset(&nset);
383 sigaddset(&nset, sig);
384 sigprocmask(SIG_UNBLOCK, &nset, NULL);
385 n_raise(sig);
386 sigprocmask(SIG_BLOCK, &nset, NULL);
388 safe_signal(sig, oldact);
391 static void
392 a_popen_sigchld(int signo){
393 pid_t pid;
394 int status;
395 struct child *cp;
396 NYD_X; /* Signal handler */
397 n_UNUSED(signo);
399 for (;;) {
400 pid = waitpid(-1, &status, WNOHANG);
401 if (pid <= 0) {
402 if (pid == -1 && n_err_no == n_ERR_INTR)
403 continue;
404 break;
407 if ((cp = a_popen_child_find(pid, FAL0)) != NULL) {
408 if (cp->free)
409 cp->pid = -1; /* XXX Was _delchild(cp);# */
410 else {
411 cp->done = 1;
412 cp->status = status;
418 static struct child *
419 a_popen_child_find(int pid, bool_t create){
420 struct child **cpp, *cp;
421 NYD2_ENTER;
423 for(cpp = &_popen_child; (cp = *cpp) != NULL && cp->pid != pid;
424 cpp = &(*cpp)->link)
427 if(cp == NULL && create)
428 (*cpp = cp = scalloc(1, sizeof *cp))->pid = pid;
429 NYD2_LEAVE;
430 return cp;
433 static void
434 a_popen_child_del(struct child *cp){
435 struct child **cpp;
436 NYD2_ENTER;
438 cpp = &_popen_child;
440 for(;;){
441 if(*cpp == cp){
442 *cpp = cp->link;
443 free(cp);
444 break;
446 if(*(cpp = &(*cpp)->link) == NULL){
447 DBG( n_err("! a_popen_child_del(): implementation error\n"); )
448 break;
451 NYD2_LEAVE;
454 FL void
455 n_child_manager_start(void)
457 struct sigaction nact, oact;
458 NYD_ENTER;
460 nact.sa_handler = &a_popen_sigchld;
461 sigemptyset(&nact.sa_mask);
462 nact.sa_flags = SA_RESTART
463 #ifdef SA_NOCLDSTOP
464 | SA_NOCLDSTOP
465 #endif
467 if (sigaction(SIGCHLD, &nact, &oact) != 0)
468 n_panic(_("Cannot install signal handler for child process management"));
469 NYD_LEAVE;
472 FL FILE *
473 safe_fopen(char const *file, char const *oflags, int *xflags)
475 int osflags, fd;
476 FILE *fp = NULL;
477 NYD2_ENTER; /* (only for Fopen() and once in go.c) */
479 if (scan_mode(oflags, &osflags) < 0)
480 goto jleave;
481 osflags |= _O_CLOEXEC;
482 if (xflags != NULL)
483 *xflags = osflags;
485 if ((fd = open(file, osflags, 0666)) == -1)
486 goto jleave;
487 _CLOEXEC_SET(fd);
489 fp = fdopen(fd, oflags);
490 jleave:
491 NYD2_LEAVE;
492 return fp;
495 FL FILE *
496 Fopen(char const *file, char const *oflags)
498 FILE *fp;
499 int osflags;
500 NYD_ENTER;
502 if ((fp = safe_fopen(file, oflags, &osflags)) != NULL)
503 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
504 NYD_LEAVE;
505 return fp;
508 FL FILE *
509 Fdopen(int fd, char const *oflags, bool_t nocloexec)
511 FILE *fp;
512 int osflags;
513 NYD_ENTER;
515 scan_mode(oflags, &osflags);
516 if (!nocloexec)
517 osflags |= _O_CLOEXEC; /* Ensured to be set by caller as documented! */
519 if ((fp = fdopen(fd, oflags)) != NULL)
520 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
521 NYD_LEAVE;
522 return fp;
525 FL int
526 Fclose(FILE *fp)
528 int i = 0;
529 NYD_ENTER;
531 if (unregister_file(fp, NULL, NULL) == OKAY)
532 i |= 1;
533 if (fclose(fp) == 0)
534 i |= 2;
535 NYD_LEAVE;
536 return (i == 3 ? 0 : EOF);
539 FL FILE *
540 Zopen(char const *file, char const *oflags) /* FIXME MESS! */
542 FILE *rv = NULL;
543 char const *cload = NULL, *csave = NULL;
544 int flags, osflags, mode, infd;
545 enum oflags rof;
546 long offset;
547 enum protocol p;
548 NYD_ENTER;
550 if (scan_mode(oflags, &osflags) < 0)
551 goto jleave;
553 flags = 0;
554 rof = OF_RDWR | OF_UNLINK;
555 if (osflags & O_APPEND)
556 rof |= OF_APPEND;
557 mode = (osflags == O_RDONLY) ? R_OK : R_OK | W_OK;
559 if ((osflags & O_APPEND) && ((p = which_protocol(file)) == PROTO_MAILDIR)) {
560 flags |= FP_MAILDIR;
561 osflags = O_RDWR | O_APPEND | O_CREAT | n_O_NOFOLLOW;
562 infd = -1;
563 } else {
564 char const *ext;
566 if ((ext = strrchr(file, '.')) != NULL) {
567 if (!asccasecmp(ext, ".gz"))
568 flags |= FP_GZIP;
569 else if (!asccasecmp(ext, ".xz")) {
570 flags |= FP_XZ;
571 osflags &= ~O_APPEND;
572 rof &= ~OF_APPEND;
573 } else if (!asccasecmp(ext, ".bz2")) {
574 flags |= FP_BZIP2;
575 osflags &= ~O_APPEND;
576 rof &= ~OF_APPEND;
577 } else {
578 #undef _X1
579 #define _X1 "file-hook-load-"
580 #undef _X2
581 #define _X2 "file-hook-save-"
582 size_t l = strlen(++ext);
583 char *vbuf = ac_alloc(l + n_MAX(sizeof(_X1), sizeof(_X2)));
585 memcpy(vbuf, _X1, sizeof(_X1) -1);
586 memcpy(vbuf + sizeof(_X1) -1, ext, l);
587 vbuf[sizeof(_X1) -1 + l] = '\0';
588 cload = n_var_vlook(vbuf, FAL0);
589 memcpy(vbuf, _X2, sizeof(_X2) -1);
590 memcpy(vbuf + sizeof(_X2) -1, ext, l);
591 vbuf[sizeof(_X2) -1 + l] = '\0';
592 csave = n_var_vlook(vbuf, FAL0);
593 #undef _X2
594 #undef _X1
595 ac_free(vbuf);
597 if ((csave != NULL) && (cload != NULL)) {
598 flags |= FP_HOOK;
599 osflags &= ~O_APPEND;
600 rof &= ~OF_APPEND;
601 } else if ((csave != NULL) | (cload != NULL)) {
602 n_alert(_("Only one of *mailbox-(load|save)-%s* is set! "
603 "Treating as plain text!"), ext);
604 goto jraw;
605 } else
606 goto jraw;
608 } else {
609 jraw:
610 /*flags |= FP_RAW;*/
611 rv = Fopen(file, oflags);
612 goto jleave;
615 if ((infd = open(file, (mode & W_OK) ? O_RDWR : O_RDONLY)) == -1 &&
616 (!(osflags & O_CREAT) || n_err_no != n_ERR_NOENT))
617 goto jleave;
620 /* Note rv is not yet register_file()d, fclose() it in error path! */
621 if ((rv = Ftmp(NULL, "zopen", rof)) == NULL) {
622 n_perr(_("tmpfile"), 0);
623 goto jerr;
626 if (flags & FP_MAILDIR)
628 else if (infd >= 0) {
629 if (_file_load(flags, infd, fileno(rv), cload) < 0) {
630 jerr:
631 if (rv != NULL)
632 fclose(rv);
633 rv = NULL;
634 if (infd >= 0)
635 close(infd);
636 goto jleave;
638 } else {
639 if ((infd = creat(file, 0666)) == -1) {
640 fclose(rv);
641 rv = NULL;
642 goto jleave;
646 if (infd >= 0)
647 close(infd);
648 fflush(rv);
650 if (!(osflags & O_APPEND))
651 rewind(rv);
652 if ((offset = ftell(rv)) == -1) {
653 Fclose(rv);
654 rv = NULL;
655 goto jleave;
658 register_file(rv, osflags, 0, flags, file, offset, csave, NULL,NULL);
659 jleave:
660 NYD_LEAVE;
661 return rv;
664 FL FILE *
665 Ftmp(char **fn, char const *namehint, enum oflags oflags)
667 /* The 8 is arbitrary but leaves room for a six character suffix (the
668 * POSIX minimum path length is 14, though we don't check that XXX).
669 * 8 should be more than sufficient given that we use base64url encoding
670 * for our random string */
671 enum {_RANDCHARS = 8u};
673 char *cp_base, *cp;
674 size_t maxname, xlen, i;
675 char const *tmpdir;
676 int osoflags, fd, e;
677 bool_t relesigs;
678 FILE *fp;
679 NYD_ENTER;
681 assert(namehint != NULL);
682 assert((oflags & OF_WRONLY) || (oflags & OF_RDWR));
683 assert(!(oflags & OF_RDONLY));
684 assert(!(oflags & OF_REGISTER_UNLINK) || (oflags & OF_REGISTER));
686 fp = NULL;
687 relesigs = FAL0;
688 e = 0;
689 tmpdir = ok_vlook(TMPDIR);
690 maxname = NAME_MAX;
691 #ifdef HAVE_PATHCONF
692 { long pc;
694 if ((pc = pathconf(tmpdir, _PC_NAME_MAX)) != -1)
695 maxname = (size_t)pc;
697 #endif
699 if ((oflags & OF_SUFFIX) && *namehint != '\0') {
700 if ((xlen = strlen(namehint)) > maxname - _RANDCHARS) {
701 n_err_no = n_ERR_NAMETOOLONG;
702 goto jleave;
704 } else
705 xlen = 0;
707 /* Prepare the template string once, then iterate over the random range */
708 cp_base =
709 cp = smalloc(strlen(tmpdir) + 1 + maxname +1);
710 cp = sstpcpy(cp, tmpdir);
711 *cp++ = '/';
713 char *x = sstpcpy(cp, VAL_UAGENT);
714 *x++ = '-';
715 if (!(oflags & OF_SUFFIX))
716 x = sstpcpy(x, namehint);
718 i = PTR2SIZE(x - cp);
719 if (i > maxname - xlen - _RANDCHARS) {
720 size_t j = maxname - xlen - _RANDCHARS;
721 x -= i - j;
722 i = j;
725 if ((oflags & OF_SUFFIX) && xlen > 0)
726 memcpy(x + _RANDCHARS, namehint, xlen);
728 x[xlen + _RANDCHARS] = '\0';
729 cp = x;
732 osoflags = O_CREAT | O_EXCL | _O_CLOEXEC;
733 osoflags |= (oflags & OF_WRONLY) ? O_WRONLY : O_RDWR;
734 if (oflags & OF_APPEND)
735 osoflags |= O_APPEND;
737 for (i = 0;; ++i) {
738 memcpy(cp, n_random_create_cp(_RANDCHARS), _RANDCHARS);
740 hold_all_sigs();
741 relesigs = TRU1;
743 if ((fd = open(cp_base, osoflags, 0600)) != -1) {
744 _CLOEXEC_SET(fd);
745 break;
747 if (i >= FTMP_OPEN_TRIES) {
748 e = n_err_no;
749 goto jfree;
751 relesigs = FAL0;
752 rele_all_sigs();
755 if (oflags & OF_REGISTER) {
756 char const *osflags = (oflags & OF_RDWR ? "w+" : "w");
757 int osflagbits;
759 scan_mode(osflags, &osflagbits); /* TODO osoflags&xy ?!!? */
760 if ((fp = fdopen(fd, osflags)) != NULL)
761 register_file(fp, osflagbits | _O_CLOEXEC, 0,
762 (FP_RAW | (oflags & OF_REGISTER_UNLINK ? FP_UNLINK : 0)),
763 cp_base, 0L, NULL, NULL,NULL);
764 } else
765 fp = fdopen(fd, (oflags & OF_RDWR ? "w+" : "w"));
767 if (fp == NULL || (oflags & OF_UNLINK)) {
768 e = n_err_no;
769 unlink(cp_base);
770 goto jfree;
773 if (fn != NULL)
774 *fn = cp_base;
775 else
776 free(cp_base);
777 jleave:
778 if (relesigs && (fp == NULL || !(oflags & OF_HOLDSIGS)))
779 rele_all_sigs();
780 if (fp == NULL)
781 n_err_no = e;
782 NYD_LEAVE;
783 return fp;
784 jfree:
785 if ((cp = cp_base) != NULL)
786 free(cp);
787 goto jleave;
790 FL void
791 Ftmp_release(char **fn)
793 char *cp;
794 NYD_ENTER;
796 cp = *fn;
797 *fn = NULL;
798 if (cp != NULL) {
799 unlink(cp);
800 rele_all_sigs();
801 free(cp);
803 NYD_LEAVE;
806 FL void
807 Ftmp_free(char **fn) /* TODO DROP: OF_REGISTER_FREEPATH! */
809 char *cp;
810 NYD_ENTER;
812 cp = *fn;
813 *fn = NULL;
814 if (cp != NULL)
815 free(cp);
816 NYD_LEAVE;
819 FL bool_t
820 pipe_cloexec(int fd[2])
822 bool_t rv = FAL0;
823 NYD_ENTER;
825 #ifdef HAVE_PIPE2
826 if (pipe2(fd, O_CLOEXEC) == -1)
827 goto jleave;
828 #else
829 if (pipe(fd) == -1)
830 goto jleave;
831 (void)fcntl(fd[0], F_SETFD, FD_CLOEXEC);
832 (void)fcntl(fd[1], F_SETFD, FD_CLOEXEC);
833 #endif
834 rv = TRU1;
835 jleave:
836 NYD_LEAVE;
837 return rv;
840 FL FILE *
841 Popen(char const *cmd, char const *mode, char const *sh,
842 char const **env_addon, int newfd1)
844 int p[2], myside, hisside, fd0, fd1, pid;
845 sigset_t nset;
846 char mod[2];
847 n_sighdl_t osigint;
848 struct termios *tiosp;
849 FILE *rv;
850 NYD_ENTER;
852 /* First clean up child structures */
853 /* C99 */{
854 struct child **cpp, *cp;
856 hold_all_sigs();
857 for (cpp = &_popen_child; *cpp != NULL;) {
858 if ((*cpp)->pid == -1) {
859 cp = *cpp;
860 *cpp = cp->link;
861 free(cp);
862 } else
863 cpp = &(*cpp)->link;
865 rele_all_sigs();
868 rv = NULL;
869 tiosp = NULL;
870 n_UNINIT(osigint, SIG_ERR);
871 mod[0] = '0', mod[1] = '\0';
873 if (!pipe_cloexec(p))
874 goto jleave;
876 if (*mode == 'r') {
877 myside = p[READ];
878 fd0 = n_CHILD_FD_PASS;
879 hisside = fd1 = p[WRITE];
880 mod[0] = *mode;
881 } else if (*mode == 'W') {
882 myside = p[WRITE];
883 hisside = fd0 = p[READ];
884 fd1 = newfd1;
885 mod[0] = 'w';
886 } else {
887 myside = p[WRITE];
888 hisside = fd0 = p[READ];
889 fd1 = n_CHILD_FD_PASS;
890 mod[0] = 'w';
893 /* In interactive mode both STDIN and STDOUT point to the terminal. If we
894 * pass through the TTY restore terminal attributes after pipe returns.
895 * XXX It shouldn't matter which FD we actually use in this case */
896 if ((n_psonce & n_PSO_INTERACTIVE) && (fd0 == n_CHILD_FD_PASS ||
897 fd1 == n_CHILD_FD_PASS)) {
898 osigint = n_signal(SIGINT, SIG_IGN);
899 tiosp = smalloc(sizeof *tiosp);
900 tcgetattr(STDIN_FILENO, tiosp);
901 n_TERMCAP_SUSPEND(TRU1);
904 sigemptyset(&nset);
906 if (cmd == (char*)-1) {
907 if ((pid = n_child_fork()) == -1)
908 n_perr(_("fork"), 0);
909 else if (pid == 0) {
910 union {char const *ccp; int (*ptf)(void); int es;} u;
911 n_child_prepare(&nset, fd0, fd1);
912 close(p[READ]);
913 close(p[WRITE]);
914 u.ccp = sh;
915 u.es = (*u.ptf)();
916 _exit(u.es);
918 } else if (sh == NULL) {
919 pid = n_child_start(cmd, &nset, fd0, fd1, NULL, NULL, NULL, env_addon);
920 } else {
921 pid = n_child_start(sh, &nset, fd0, fd1, "-c", cmd, NULL, env_addon);
923 if (pid < 0) {
924 close(p[READ]);
925 close(p[WRITE]);
926 goto jleave;
928 close(hisside);
929 if ((rv = fdopen(myside, mod)) != NULL)
930 register_file(rv, 0, pid,
931 (tiosp == NULL ? FP_PIPE : FP_PIPE | FP_TERMIOS),
932 NULL, 0L, NULL, tiosp, osigint);
933 else
934 close(myside);
935 jleave:
936 if(rv == NULL && tiosp != NULL){
937 n_TERMCAP_RESUME(TRU1);
938 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
939 n_free(tiosp);
940 n_signal(SIGINT, osigint);
942 NYD_LEAVE;
943 return rv;
946 FL bool_t
947 Pclose(FILE *ptr, bool_t dowait)
949 n_sighdl_t osigint;
950 struct termios *tiosp;
951 int pid;
952 bool_t rv = FAL0;
953 NYD_ENTER;
955 pid = file_pid(ptr);
956 if(pid < 0)
957 goto jleave;
959 unregister_file(ptr, &tiosp, &osigint);
960 fclose(ptr);
962 if(dowait){
963 hold_all_sigs();
964 rv = n_child_wait(pid, NULL);
965 if(tiosp != NULL){
966 n_TERMCAP_RESUME(TRU1);
967 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
968 n_signal(SIGINT, osigint);
970 rele_all_sigs();
971 }else{
972 n_child_free(pid);
973 rv = TRU1;
976 if(tiosp != NULL)
977 free(tiosp);
978 jleave:
979 NYD_LEAVE;
980 return rv;
983 FL FILE *
984 n_pager_open(void)
986 char const *env_add[2], *pager;
987 FILE *rv;
988 NYD_ENTER;
990 assert(n_psonce & n_PSO_INTERACTIVE);
992 pager = n_pager_get(env_add + 0);
993 env_add[1] = NULL;
995 if ((rv = Popen(pager, "w", NULL, env_add, n_CHILD_FD_PASS)) == NULL)
996 n_perr(pager, 0);
997 NYD_LEAVE;
998 return rv;
1001 FL bool_t
1002 n_pager_close(FILE *fp)
1004 sighandler_type sh;
1005 bool_t rv;
1006 NYD_ENTER;
1008 sh = safe_signal(SIGPIPE, SIG_IGN);
1009 rv = Pclose(fp, TRU1);
1010 safe_signal(SIGPIPE, sh);
1011 NYD_LEAVE;
1012 return rv;
1015 FL void
1016 close_all_files(void)
1018 NYD_ENTER;
1019 while (fp_head != NULL)
1020 if ((fp_head->flags & FP_MASK) == FP_PIPE)
1021 Pclose(fp_head->fp, TRU1);
1022 else
1023 Fclose(fp_head->fp);
1024 NYD_LEAVE;
1027 FL int
1028 n_child_run(char const *cmd, sigset_t *mask, int infd, int outfd,
1029 char const *a0, char const *a1, char const *a2, char const **env_addon)
1031 sigset_t nset, oset;
1032 sighandler_type soldint;
1033 int rv;
1034 enum {a_NONE = 0, a_INTIGN = 1<<0, a_TTY = 1<<1} f;
1035 NYD_ENTER;
1037 f = a_NONE;
1038 n_UNINIT(soldint, SIG_ERR);
1040 /* TODO Of course this is a joke given that during a "p*" the PAGER may
1041 * TODO be up and running while we play around like this... but i guess
1042 * TODO this can't be helped at all unless we perform complete and true
1043 * TODO process group separation and ensure we don't deadlock us out
1044 * TODO via TTY jobcontrol signal storms (could this really happen?).
1045 * TODO Or have a builtin pager. Or query any necessity BEFORE we start
1046 * TODO any action, and shall we find we need to run programs dump it
1047 * TODO all into a temporary file which is then passed through to the
1048 * TODO PAGER. Ugh. That still won't help for "needsterminal" anyway */
1049 if(infd == n_CHILD_FD_PASS || outfd == n_CHILD_FD_PASS){
1050 soldint = safe_signal(SIGINT, SIG_IGN);
1051 f = a_INTIGN;
1053 if(n_psonce & n_PSO_INTERACTIVE){
1054 f |= a_TTY;
1055 tcgetattr((n_psonce & n_PSO_TTYIN ? STDIN_FILENO : STDOUT_FILENO),
1056 &a_popen_tios);
1057 n_TERMCAP_SUSPEND(FAL0);
1058 sigfillset(&nset);
1059 sigdelset(&nset, SIGCHLD);
1060 sigdelset(&nset, SIGINT);
1061 /* sigdelset(&nset, SIGPIPE); TODO would need a handler */
1062 sigprocmask(SIG_BLOCK, &nset, &oset);
1063 a_popen_hadsig = 0;
1064 a_popen_jobsigs_up();
1068 if((rv = n_child_start(cmd, mask, infd, outfd, a0, a1, a2, env_addon)) < 0)
1069 rv = -1;
1070 else{
1071 if(n_child_wait(rv, NULL))
1072 rv = 0;
1073 else{
1074 if(ok_blook(bsdcompat) || ok_blook(bsdmsgs))
1075 n_err(_("Fatal error in process\n"));
1076 rv = -1;
1080 if(f & a_TTY){
1081 a_popen_jobsigs_down();
1082 n_TERMCAP_RESUME(a_popen_hadsig ? TRU1 : FAL0);
1083 tcsetattr(((n_psonce & n_PSO_TTYIN) ? STDIN_FILENO : STDOUT_FILENO),
1084 ((n_psonce & n_PSO_TTYIN) ? TCSAFLUSH : TCSADRAIN), &a_popen_tios);
1085 sigprocmask(SIG_SETMASK, &oset, NULL);
1087 if(f & a_INTIGN){
1088 if(soldint != SIG_IGN)
1089 safe_signal(SIGINT, soldint);
1091 NYD_LEAVE;
1092 return rv;
1095 FL int
1096 n_child_start(char const *cmd, sigset_t *mask, int infd, int outfd,
1097 char const *a0, char const *a1, char const *a2,
1098 char const **env_addon)
1100 int rv;
1101 NYD_ENTER;
1103 if ((rv = n_child_fork()) == -1) {
1104 n_perr(_("fork"), 0);
1105 rv = -1;
1106 } else if (rv == 0) {
1107 char *argv[128];
1108 int i;
1110 if (env_addon != NULL) { /* TODO env_addon; should have struct child */
1111 extern char **environ;
1112 size_t ei, ei_orig, ai, ai_orig;
1113 char **env;
1115 /* TODO note we don't check the POSIX limit:
1116 * the total space used to store the environment and the arguments to
1117 * the process is limited to {ARG_MAX} bytes */
1118 for (ei = 0; environ[ei] != NULL; ++ei)
1120 ei_orig = ei;
1121 for (ai = 0; env_addon[ai] != NULL; ++ai)
1123 ai_orig = ai;
1124 env = ac_alloc(sizeof(*env) * (ei + ai +1));
1125 memcpy(env, environ, sizeof(*env) * ei);
1127 /* Replace all those keys that yet exist */
1128 while (ai-- > 0) {
1129 char const *ee, *kvs;
1130 size_t kl;
1132 ee = env_addon[ai];
1133 kvs = strchr(ee, '=');
1134 assert(kvs != NULL);
1135 kl = PTR2SIZE(kvs - ee);
1136 assert(kl > 0);
1137 for (ei = ei_orig; ei-- > 0;) {
1138 char const *ekvs = strchr(env[ei], '=');
1139 if (ekvs != NULL && kl == PTR2SIZE(ekvs - env[ei]) &&
1140 !memcmp(ee, env[ei], kl)) {
1141 env[ei] = n_UNCONST(ee);
1142 env_addon[ai] = NULL;
1143 break;
1148 /* And append the rest */
1149 for (ei = ei_orig, ai = ai_orig; ai-- > 0;)
1150 if (env_addon[ai] != NULL)
1151 env[ei++] = n_UNCONST(env_addon[ai]);
1153 env[ei] = NULL;
1154 environ = env;
1157 i = (int)getrawlist(TRU1, argv, n_NELEM(argv), cmd, strlen(cmd));
1159 if ((argv[i++] = n_UNCONST(a0)) != NULL &&
1160 (argv[i++] = n_UNCONST(a1)) != NULL &&
1161 (argv[i++] = n_UNCONST(a2)) != NULL)
1162 argv[i] = NULL;
1163 n_child_prepare(mask, infd, outfd);
1164 execvp(argv[0], argv);
1165 perror(argv[0]);
1166 _exit(n_EXIT_ERR);
1168 NYD_LEAVE;
1169 return rv;
1172 FL int
1173 n_child_fork(void){
1174 struct child *cp;
1175 int pid;
1176 NYD2_ENTER;
1178 cp = a_popen_child_find(0, TRU1);
1180 if((cp->pid = pid = fork()) == -1){
1181 a_popen_child_del(cp);
1182 n_perr(_("fork"), 0);
1184 NYD2_LEAVE;
1185 return pid;
1188 FL void
1189 n_child_prepare(sigset_t *nset, int infd, int outfd)
1191 int i;
1192 sigset_t fset;
1193 NYD_ENTER;
1195 /* All file descriptors other than 0, 1, and 2 are supposed to be cloexec */
1196 /* TODO WHAT IS WITH STDERR_FILENO DAMN? */
1197 if ((i = (infd == n_CHILD_FD_NULL)))
1198 infd = open("/dev/null", O_RDONLY);
1199 if (infd >= 0) {
1200 dup2(infd, STDIN_FILENO);
1201 if (i)
1202 close(infd);
1205 if ((i = (outfd == n_CHILD_FD_NULL)))
1206 outfd = open("/dev/null", O_WRONLY);
1207 if (outfd >= 0) {
1208 dup2(outfd, STDOUT_FILENO);
1209 if (i)
1210 close(outfd);
1213 if (nset) {
1214 for (i = 1; i < NSIG; ++i)
1215 if (sigismember(nset, i))
1216 safe_signal(i, SIG_IGN);
1217 if (!sigismember(nset, SIGINT))
1218 safe_signal(SIGINT, SIG_DFL);
1221 sigemptyset(&fset);
1222 sigprocmask(SIG_SETMASK, &fset, NULL);
1223 NYD_LEAVE;
1226 FL void
1227 n_child_free(int pid){
1228 sigset_t nset, oset;
1229 struct child *cp;
1230 NYD2_ENTER;
1232 sigemptyset(&nset);
1233 sigaddset(&nset, SIGCHLD);
1234 sigprocmask(SIG_BLOCK, &nset, &oset);
1236 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1237 if(cp->done)
1238 a_popen_child_del(cp);
1239 else
1240 cp->free = TRU1;
1243 sigprocmask(SIG_SETMASK, &oset, NULL);
1244 NYD2_LEAVE;
1247 FL bool_t
1248 n_child_wait(int pid, int *wait_status){
1249 sigset_t nset, oset;
1250 struct child *cp;
1251 int ws;
1252 bool_t rv;
1253 NYD_ENTER;
1255 sigemptyset(&nset);
1256 sigaddset(&nset, SIGCHLD);
1257 sigprocmask(SIG_BLOCK, &nset, &oset);
1259 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1260 while(!cp->done)
1261 sigsuspend(&oset);
1262 ws = cp->status;
1263 a_popen_child_del(cp);
1264 }else
1265 ws = 0;
1267 sigprocmask(SIG_SETMASK, &oset, NULL);
1269 if(wait_status != NULL)
1270 *wait_status = ws;
1271 rv = (WIFEXITED(ws) && WEXITSTATUS(ws) == 0);
1272 NYD_LEAVE;
1273 return rv;
1276 /* s-it-mode */