Fix error handling of `shcodec' and `urlcodec'
[s-mailx.git] / popen.c
blob465608b22c9fdd1b2f6bc6d8cd2c75ce04535973
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 #define READ 0
43 #define WRITE 1
45 struct fp {
46 struct fp *link;
47 int omode;
48 int pid;
49 enum {
50 FP_RAW = 0,
52 FP_MAILDIR = 1<<4,
53 FP_HOOK = 1<<5,
54 FP_PIPE = 1<<6,
55 FP_MASK = (1<<7) - 1,
56 /* TODO FP_UNLINK: should be in a separated process so that unlinking
57 * TODO the temporary "garbage" is "safe"(r than it is like that) */
58 FP_UNLINK = 1<<9,
59 FP_TERMIOS = 1<<10
60 } flags;
61 long offset;
62 FILE *fp;
63 char *realfile;
64 char *save_cmd;
65 struct termios *fp_tios;
66 n_sighdl_t fp_osigint; /* Only if FP_TERMIOS */
69 struct child {
70 int pid;
71 char done;
72 char free;
73 int status;
74 struct child *link;
77 static struct fp *fp_head;
78 static struct child *_popen_child;
80 /* TODO Rather temporary: deal with job control with FD_PASS */
81 static struct termios a_popen_tios;
82 static sighandler_type a_popen_otstp, a_popen_ottin, a_popen_ottou;
83 static volatile int a_popen_hadsig;
85 static int a_popen_scan_mode(char const *mode, int *omode);
86 static void register_file(FILE *fp, int omode, int pid,
87 int flags, char const *realfile, long offset,
88 char const *save_cmd, struct termios *tiosp,
89 n_sighdl_t osigint);
90 static enum okay _file_save(struct fp *fpp);
91 static int a_popen_file_load(int flags, int infd, int outfd,
92 char const *load_cmd);
93 static enum okay unregister_file(FILE *fp, struct termios **tiosp,
94 n_sighdl_t *osigint);
95 static int file_pid(FILE *fp);
97 /* TODO Rather temporary: deal with job control with FD_PASS */
98 static void a_popen_jobsigs_up(void);
99 static void a_popen_jobsigs_down(void);
100 static void a_popen_jobsig(int sig);
102 /* Handle SIGCHLD */
103 static void a_popen_sigchld(int signo);
105 static struct child *a_popen_child_find(int pid, bool_t create);
106 static void a_popen_child_del(struct child *cp);
108 static int
109 a_popen_scan_mode(char const *mode, int *omode){
110 static struct{
111 char const mode[4];
112 int omode;
113 } const maps[] = {
114 {"r", O_RDONLY},
115 {"w", O_WRONLY | O_CREAT | n_O_NOFOLLOW | O_TRUNC},
116 {"wx", O_WRONLY | O_CREAT | O_EXCL},
117 {"a", O_WRONLY | O_APPEND | O_CREAT | n_O_NOFOLLOW},
118 {"a+", O_RDWR | O_APPEND | O_CREAT | n_O_NOFOLLOW},
119 {"r+", O_RDWR},
120 {"w+", O_RDWR | O_CREAT | O_EXCL}
122 int i;
123 NYD2_ENTER;
125 for(i = 0; UICMP(z, i, <, n_NELEM(maps)); ++i)
126 if(!strcmp(maps[i].mode, mode)){
127 *omode = maps[i].omode;
128 i = 0;
129 goto jleave;
132 DBG( n_alert(_("Internal error: bad stdio open mode %s"), mode); )
133 n_err_no = n_ERR_INVAL;
134 *omode = 0; /* (silence CC) */
135 i = -1;
136 jleave:
137 NYD2_LEAVE;
138 return i;
141 static void
142 register_file(FILE *fp, int omode, int pid, int flags,
143 char const *realfile, long offset, char const *save_cmd,
144 struct termios *tiosp, n_sighdl_t osigint)
146 struct fp *fpp;
147 NYD_ENTER;
149 assert(!(flags & FP_UNLINK) || realfile != NULL);
150 assert(!(flags & FP_TERMIOS) || tiosp != NULL);
152 fpp = smalloc(sizeof *fpp);
153 fpp->fp = fp;
154 fpp->omode = omode;
155 fpp->pid = pid;
156 fpp->link = fp_head;
157 fpp->flags = flags;
158 fpp->realfile = (realfile != NULL) ? sstrdup(realfile) : NULL;
159 fpp->save_cmd = (save_cmd != NULL) ? sstrdup(save_cmd) : NULL;
160 fpp->fp_tios = tiosp;
161 fpp->fp_osigint = osigint;
162 fpp->offset = offset;
163 fp_head = fpp;
164 NYD_LEAVE;
167 static enum okay
168 _file_save(struct fp *fpp)
170 char const *cmd[3];
171 int outfd;
172 enum okay rv;
173 NYD_ENTER;
175 if (fpp->omode == O_RDONLY) {
176 rv = OKAY;
177 goto jleave;
179 rv = STOP;
181 fflush(fpp->fp);
182 clearerr(fpp->fp);
184 /* Ensure the I/O library doesn't optimize the fseek(3) away! */
185 if(!n_real_seek(fpp->fp, fpp->offset, SEEK_SET)){
186 outfd = n_err_no;
187 n_err(_("Fatal: cannot restore file position and save %s: %s\n"),
188 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
189 goto jleave;
192 if ((fpp->flags & FP_MASK) == FP_MAILDIR) {
193 rv = maildir_append(fpp->realfile, fpp->fp, fpp->offset);
194 goto jleave;
197 outfd = open(fpp->realfile,
198 ((fpp->omode | O_CREAT | (fpp->omode & O_APPEND ? 0 : O_TRUNC) |
199 n_O_NOFOLLOW) & ~O_EXCL), 0666);
200 if (outfd == -1) {
201 outfd = n_err_no;
202 n_err(_("Fatal: cannot create %s: %s\n"),
203 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
204 goto jleave;
207 cmd[2] = NULL;
208 switch(fpp->flags & FP_MASK){
209 case FP_HOOK:
210 cmd[0] = ok_vlook(SHELL);
211 cmd[1] = "-c";
212 cmd[2] = fpp->save_cmd;
213 break;
214 default:
215 cmd[0] = "cat";
216 cmd[1] = NULL;
217 break;
219 if (n_child_run(cmd[0], 0, fileno(fpp->fp), outfd,
220 cmd[1], cmd[2], NULL, NULL, NULL) >= 0)
221 rv = OKAY;
223 close(outfd);
224 jleave:
225 NYD_LEAVE;
226 return rv;
229 static int
230 a_popen_file_load(int flags, int infd, int outfd, char const *load_cmd){
231 char const *cmd[3];
232 int rv;
233 NYD2_ENTER;
235 cmd[2] = NULL;
236 switch(flags & FP_MASK){
237 case FP_MAILDIR:
238 rv = 0;
239 goto jleave;
240 case FP_HOOK:
241 cmd[0] = ok_vlook(SHELL);
242 cmd[1] = "-c";
243 cmd[2] = load_cmd;
244 break;
245 default:
246 cmd[0] = "cat";
247 cmd[1] = NULL;
248 break;
251 rv = n_child_run(cmd[0], 0, infd, outfd, cmd[1], cmd[2], NULL, NULL, NULL);
252 jleave:
253 NYD2_LEAVE;
254 return rv;
257 static enum okay
258 unregister_file(FILE *fp, struct termios **tiosp, n_sighdl_t *osigint)
260 struct fp **pp, *p;
261 enum okay rv = OKAY;
262 NYD_ENTER;
264 if (tiosp)
265 *tiosp = NULL;
267 for (pp = &fp_head; (p = *pp) != NULL; pp = &p->link)
268 if (p->fp == fp) {
269 switch (p->flags & FP_MASK) {
270 case FP_RAW:
271 case FP_PIPE:
272 break;
273 default:
274 rv = _file_save(p);
275 break;
277 if ((p->flags & FP_UNLINK) && unlink(p->realfile))
278 rv = STOP;
280 *pp = p->link;
281 if (p->save_cmd != NULL)
282 free(p->save_cmd);
283 if (p->realfile != NULL)
284 free(p->realfile);
285 if (p->flags & FP_TERMIOS) {
286 if (tiosp != NULL) {
287 *tiosp = p->fp_tios;
288 *osigint = p->fp_osigint;
289 } else
290 free(p->fp_tios);
292 free(p);
293 goto jleave;
295 DBGOR(n_panic, n_alert)(_("Invalid file pointer"));
296 rv = STOP;
297 jleave:
298 NYD_LEAVE;
299 return rv;
302 static int
303 file_pid(FILE *fp)
305 int rv;
306 struct fp *p;
307 NYD2_ENTER;
309 rv = -1;
310 for (p = fp_head; p; p = p->link)
311 if (p->fp == fp) {
312 rv = p->pid;
313 break;
315 NYD2_LEAVE;
316 return rv;
319 static void
320 a_popen_jobsigs_up(void){
321 sigset_t nset, oset;
322 NYD2_ENTER;
324 sigfillset(&nset);
326 sigprocmask(SIG_BLOCK, &nset, &oset);
327 a_popen_otstp = safe_signal(SIGTSTP, &a_popen_jobsig);
328 a_popen_ottin = safe_signal(SIGTTIN, &a_popen_jobsig);
329 a_popen_ottou = safe_signal(SIGTTOU, &a_popen_jobsig);
331 /* This assumes oset contains nothing but SIGCHLD, so to say */
332 sigdelset(&oset, SIGTSTP);
333 sigdelset(&oset, SIGTTIN);
334 sigdelset(&oset, SIGTTOU);
335 sigprocmask(SIG_SETMASK, &oset, NULL);
336 NYD2_LEAVE;
339 static void
340 a_popen_jobsigs_down(void){
341 sigset_t nset, oset;
342 NYD2_ENTER;
344 sigfillset(&nset);
346 sigprocmask(SIG_BLOCK, &nset, &oset);
347 safe_signal(SIGTSTP, a_popen_otstp);
348 safe_signal(SIGTTIN, a_popen_ottin);
349 safe_signal(SIGTTOU, a_popen_ottou);
351 sigaddset(&oset, SIGTSTP);
352 sigaddset(&oset, SIGTTIN);
353 sigaddset(&oset, SIGTTOU);
354 sigprocmask(SIG_SETMASK, &oset, NULL);
355 NYD2_LEAVE;
358 static void
359 a_popen_jobsig(int sig){
360 sighandler_type oldact;
361 sigset_t nset;
362 bool_t hadsig;
363 NYD_X; /* Signal handler */
365 hadsig = (a_popen_hadsig != 0);
366 a_popen_hadsig = 1;
367 if(!hadsig)
368 n_TERMCAP_SUSPEND(TRU1);
370 oldact = safe_signal(sig, SIG_DFL);
372 sigemptyset(&nset);
373 sigaddset(&nset, sig);
374 sigprocmask(SIG_UNBLOCK, &nset, NULL);
375 n_raise(sig);
376 sigprocmask(SIG_BLOCK, &nset, NULL);
378 safe_signal(sig, oldact);
381 static void
382 a_popen_sigchld(int signo){
383 pid_t pid;
384 int status;
385 struct child *cp;
386 NYD_X; /* Signal handler */
387 n_UNUSED(signo);
389 for (;;) {
390 pid = waitpid(-1, &status, WNOHANG);
391 if (pid <= 0) {
392 if (pid == -1 && n_err_no == n_ERR_INTR)
393 continue;
394 break;
397 if ((cp = a_popen_child_find(pid, FAL0)) != NULL) {
398 cp->done = 1;
399 if (cp->free)
400 cp->pid = -1; /* XXX Was _delchild(cp);# */
401 else {
402 cp->status = status;
408 static struct child *
409 a_popen_child_find(int pid, bool_t create){
410 struct child **cpp, *cp;
411 NYD2_ENTER;
413 for(cpp = &_popen_child; (cp = *cpp) != NULL && cp->pid != pid;
414 cpp = &(*cpp)->link)
417 if(cp == NULL && create)
418 (*cpp = cp = scalloc(1, sizeof *cp))->pid = pid;
419 NYD2_LEAVE;
420 return cp;
423 static void
424 a_popen_child_del(struct child *cp){
425 struct child **cpp;
426 NYD2_ENTER;
428 cpp = &_popen_child;
430 for(;;){
431 if(*cpp == cp){
432 *cpp = cp->link;
433 free(cp);
434 break;
436 if(*(cpp = &(*cpp)->link) == NULL){
437 DBG( n_err("! a_popen_child_del(): implementation error\n"); )
438 break;
441 NYD2_LEAVE;
444 FL void
445 n_child_manager_start(void)
447 struct sigaction nact, oact;
448 NYD_ENTER;
450 nact.sa_handler = &a_popen_sigchld;
451 sigemptyset(&nact.sa_mask);
452 nact.sa_flags = SA_RESTART
453 #ifdef SA_NOCLDSTOP
454 | SA_NOCLDSTOP
455 #endif
457 if (sigaction(SIGCHLD, &nact, &oact) != 0)
458 n_panic(_("Cannot install signal handler for child process management"));
459 NYD_LEAVE;
462 FL FILE *
463 safe_fopen(char const *file, char const *oflags, int *xflags)
465 int osflags, fd;
466 FILE *fp = NULL;
467 NYD2_ENTER; /* (only for Fopen() and once in go.c) */
469 if (a_popen_scan_mode(oflags, &osflags) < 0)
470 goto jleave;
471 osflags |= _O_CLOEXEC;
472 if (xflags != NULL)
473 *xflags = osflags;
475 if ((fd = open(file, osflags, 0666)) == -1)
476 goto jleave;
477 _CLOEXEC_SET(fd);
479 fp = fdopen(fd, oflags);
480 jleave:
481 NYD2_LEAVE;
482 return fp;
485 FL FILE *
486 Fopen(char const *file, char const *oflags)
488 FILE *fp;
489 int osflags;
490 NYD_ENTER;
492 if ((fp = safe_fopen(file, oflags, &osflags)) != NULL)
493 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
494 NYD_LEAVE;
495 return fp;
498 FL FILE *
499 Fdopen(int fd, char const *oflags, bool_t nocloexec)
501 FILE *fp;
502 int osflags;
503 NYD_ENTER;
505 a_popen_scan_mode(oflags, &osflags);
506 if (!nocloexec)
507 osflags |= _O_CLOEXEC; /* Ensured to be set by caller as documented! */
509 if ((fp = fdopen(fd, oflags)) != NULL)
510 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
511 NYD_LEAVE;
512 return fp;
515 FL int
516 Fclose(FILE *fp)
518 int i = 0;
519 NYD_ENTER;
521 if (unregister_file(fp, NULL, NULL) == OKAY)
522 i |= 1;
523 if (fclose(fp) == 0)
524 i |= 2;
525 NYD_LEAVE;
526 return (i == 3 ? 0 : EOF);
529 FL FILE *
530 n_fopen_any(char const *file, char const *oflags, /* TODO should take flags */
531 enum n_fopen_state *fs_or_null){ /* TODO as bits, return state */
532 /* TODO Support file locking upon open time */
533 long offset;
534 enum protocol p;
535 enum oflags rof;
536 int osflags, flags, omode, infd;
537 char const *cload, *csave;
538 enum n_fopen_state fs;
539 FILE *rv;
540 NYD_ENTER;
542 rv = NULL;
543 fs = n_FOPEN_STATE_NONE;
544 cload = csave = NULL;
546 if(a_popen_scan_mode(oflags, &osflags) < 0)
547 goto jleave;
549 flags = 0;
550 rof = OF_RDWR | OF_UNLINK;
551 if(osflags & O_APPEND)
552 rof |= OF_APPEND;
553 omode = (osflags == O_RDONLY) ? R_OK : R_OK | W_OK;
555 /* We don't want to find mbox.bz2 when doing "copy * mbox", but only for
556 * "file mbox", so don't try hooks when writing */
557 p = which_protocol(file, TRU1, ((omode & W_OK) == 0), &file);
558 fs = (enum n_fopen_state)p;
559 switch(p){
560 default:
561 goto jleave;
562 case n_PROTO_MAILDIR:
563 if(fs_or_null != NULL && !access(file, F_OK))
564 fs |= n_FOPEN_STATE_EXISTS;
565 flags |= FP_MAILDIR;
566 osflags = O_RDWR | O_APPEND | O_CREAT | n_O_NOFOLLOW;
567 infd = -1;
568 break;
569 case n_PROTO_FILE:{
570 struct n_file_type ft;
572 if(!(osflags & O_EXCL) && fs_or_null != NULL && !access(file, F_OK))
573 fs |= n_FOPEN_STATE_EXISTS;
575 if(n_filetype_exists(&ft, file)){
576 flags |= FP_HOOK;
577 cload = ft.ft_load_dat;
578 csave = ft.ft_save_dat;
579 /* Cause truncation for compressor/hook output files */
580 osflags &= ~O_APPEND;
581 rof &= ~OF_APPEND;
582 if((infd = open(file, (omode & W_OK ? O_RDWR : O_RDONLY))) == -1){
583 if(!(osflags & O_CREAT) || n_err_no != n_ERR_NOENT)
584 goto jleave;
586 fs |= n_FOPEN_STATE_EXISTS;
587 }else{
588 /*flags |= FP_RAW;*/
589 rv = Fopen(file, oflags);
590 if((osflags & O_EXCL) && rv == NULL)
591 fs |= n_FOPEN_STATE_EXISTS;
592 goto jleave;
594 } break;
597 /* Note rv is not yet register_file()d, fclose() it in error path! */
598 if((rv = Ftmp(NULL, "fopenany", rof)) == NULL){
599 n_perr(_("tmpfile"), 0);
600 goto jerr;
603 if(flags & FP_MAILDIR)
605 else if(infd >= 0){
606 if(a_popen_file_load(flags, infd, fileno(rv), cload) < 0){
607 jerr:
608 if(rv != NULL)
609 fclose(rv);
610 rv = NULL;
611 if(infd >= 0)
612 close(infd);
613 goto jleave;
615 }else{
616 if((infd = creat(file, 0666)) == -1){
617 fclose(rv);
618 rv = NULL;
619 goto jleave;
623 if(infd >= 0)
624 close(infd);
625 fflush(rv);
627 if(!(osflags & O_APPEND))
628 rewind(rv);
629 if((offset = ftell(rv)) == -1){
630 Fclose(rv);
631 rv = NULL;
632 goto jleave;
635 register_file(rv, osflags, 0, flags, file, offset, csave, NULL,NULL);
636 jleave:
637 if(fs_or_null != NULL)
638 *fs_or_null = fs;
639 NYD_LEAVE;
640 return rv;
643 FL FILE *
644 Ftmp(char **fn, char const *namehint, enum oflags oflags)
646 /* The 8 is arbitrary but leaves room for a six character suffix (the
647 * POSIX minimum path length is 14, though we don't check that XXX).
648 * 8 should be more than sufficient given that we use base64url encoding
649 * for our random string */
650 enum {_RANDCHARS = 8u};
652 char *cp_base, *cp;
653 size_t maxname, xlen, i;
654 char const *tmpdir;
655 int osoflags, fd, e;
656 bool_t relesigs;
657 FILE *fp;
658 NYD_ENTER;
660 assert(namehint != NULL);
661 assert((oflags & OF_WRONLY) || (oflags & OF_RDWR));
662 assert(!(oflags & OF_RDONLY));
663 assert(!(oflags & OF_REGISTER_UNLINK) || (oflags & OF_REGISTER));
665 fp = NULL;
666 relesigs = FAL0;
667 e = 0;
668 tmpdir = ok_vlook(TMPDIR);
669 maxname = NAME_MAX;
670 #ifdef HAVE_PATHCONF
671 { long pc;
673 if ((pc = pathconf(tmpdir, _PC_NAME_MAX)) != -1)
674 maxname = (size_t)pc;
676 #endif
678 if ((oflags & OF_SUFFIX) && *namehint != '\0') {
679 if ((xlen = strlen(namehint)) > maxname - _RANDCHARS) {
680 n_err_no = n_ERR_NAMETOOLONG;
681 goto jleave;
683 } else
684 xlen = 0;
686 /* Prepare the template string once, then iterate over the random range */
687 cp_base =
688 cp = smalloc(strlen(tmpdir) + 1 + maxname +1);
689 cp = sstpcpy(cp, tmpdir);
690 *cp++ = '/';
692 char *x = sstpcpy(cp, VAL_UAGENT);
693 *x++ = '-';
694 if (!(oflags & OF_SUFFIX))
695 x = sstpcpy(x, namehint);
697 i = PTR2SIZE(x - cp);
698 if (i > maxname - xlen - _RANDCHARS) {
699 size_t j = maxname - xlen - _RANDCHARS;
700 x -= i - j;
701 i = j;
704 if ((oflags & OF_SUFFIX) && xlen > 0)
705 memcpy(x + _RANDCHARS, namehint, xlen);
707 x[xlen + _RANDCHARS] = '\0';
708 cp = x;
711 osoflags = O_CREAT | O_EXCL | _O_CLOEXEC;
712 osoflags |= (oflags & OF_WRONLY) ? O_WRONLY : O_RDWR;
713 if (oflags & OF_APPEND)
714 osoflags |= O_APPEND;
716 for (i = 0;; ++i) {
717 memcpy(cp, n_random_create_cp(_RANDCHARS, NULL), _RANDCHARS);
719 hold_all_sigs();
720 relesigs = TRU1;
722 if ((fd = open(cp_base, osoflags, 0600)) != -1) {
723 _CLOEXEC_SET(fd);
724 break;
726 if (i >= FTMP_OPEN_TRIES) {
727 e = n_err_no;
728 goto jfree;
730 relesigs = FAL0;
731 rele_all_sigs();
734 if (oflags & OF_REGISTER) {
735 char const *osflags = (oflags & OF_RDWR ? "w+" : "w");
736 int osflagbits;
738 a_popen_scan_mode(osflags, &osflagbits); /* TODO osoflags&xy ?!!? */
739 if ((fp = fdopen(fd, osflags)) != NULL)
740 register_file(fp, osflagbits | _O_CLOEXEC, 0,
741 (FP_RAW | (oflags & OF_REGISTER_UNLINK ? FP_UNLINK : 0)),
742 cp_base, 0L, NULL, NULL,NULL);
743 } else
744 fp = fdopen(fd, (oflags & OF_RDWR ? "w+" : "w"));
746 if (fp == NULL || (oflags & OF_UNLINK)) {
747 e = n_err_no;
748 unlink(cp_base);
749 goto jfree;
752 if (fn != NULL)
753 *fn = cp_base;
754 else
755 free(cp_base);
756 jleave:
757 if (relesigs && (fp == NULL || !(oflags & OF_HOLDSIGS)))
758 rele_all_sigs();
759 if (fp == NULL)
760 n_err_no = e;
761 NYD_LEAVE;
762 return fp;
763 jfree:
764 if ((cp = cp_base) != NULL)
765 free(cp);
766 goto jleave;
769 FL void
770 Ftmp_release(char **fn)
772 char *cp;
773 NYD_ENTER;
775 cp = *fn;
776 *fn = NULL;
777 if (cp != NULL) {
778 unlink(cp);
779 rele_all_sigs();
780 free(cp);
782 NYD_LEAVE;
785 FL void
786 Ftmp_free(char **fn) /* TODO DROP: OF_REGISTER_FREEPATH! */
788 char *cp;
789 NYD_ENTER;
791 cp = *fn;
792 *fn = NULL;
793 if (cp != NULL)
794 free(cp);
795 NYD_LEAVE;
798 FL bool_t
799 pipe_cloexec(int fd[2])
801 bool_t rv = FAL0;
802 NYD_ENTER;
804 #ifdef HAVE_PIPE2
805 if (pipe2(fd, O_CLOEXEC) == -1)
806 goto jleave;
807 #else
808 if (pipe(fd) == -1)
809 goto jleave;
810 _CLOEXEC_SET(fd[0]);
811 _CLOEXEC_SET(fd[1]);
812 #endif
813 rv = TRU1;
814 jleave:
815 NYD_LEAVE;
816 return rv;
819 FL FILE *
820 Popen(char const *cmd, char const *mode, char const *sh,
821 char const **env_addon, int newfd1)
823 int p[2], myside, hisside, fd0, fd1, pid;
824 sigset_t nset;
825 char mod[2];
826 n_sighdl_t osigint;
827 struct termios *tiosp;
828 FILE *rv;
829 NYD_ENTER;
831 /* First clean up child structures */
832 /* C99 */{
833 struct child **cpp, *cp;
835 hold_all_sigs();
836 for (cpp = &_popen_child; *cpp != NULL;) {
837 if ((*cpp)->pid == -1) {
838 cp = *cpp;
839 *cpp = cp->link;
840 free(cp);
841 } else
842 cpp = &(*cpp)->link;
844 rele_all_sigs();
847 rv = NULL;
848 tiosp = NULL;
849 n_UNINIT(osigint, SIG_ERR);
850 mod[0] = '0', mod[1] = '\0';
852 if (!pipe_cloexec(p))
853 goto jleave;
855 if (*mode == 'r') {
856 myside = p[READ];
857 fd0 = n_CHILD_FD_PASS;
858 hisside = fd1 = p[WRITE];
859 mod[0] = *mode;
860 } else if (*mode == 'W') {
861 myside = p[WRITE];
862 hisside = fd0 = p[READ];
863 fd1 = newfd1;
864 mod[0] = 'w';
865 } else {
866 myside = p[WRITE];
867 hisside = fd0 = p[READ];
868 fd1 = n_CHILD_FD_PASS;
869 mod[0] = 'w';
872 /* In interactive mode both STDIN and STDOUT point to the terminal. If we
873 * pass through the TTY restore terminal attributes after pipe returns.
874 * XXX It shouldn't matter which FD we actually use in this case */
875 if ((n_psonce & n_PSO_INTERACTIVE) && (fd0 == n_CHILD_FD_PASS ||
876 fd1 == n_CHILD_FD_PASS)) {
877 osigint = n_signal(SIGINT, SIG_IGN);
878 tiosp = smalloc(sizeof *tiosp);
879 tcgetattr(STDIN_FILENO, tiosp);
880 n_TERMCAP_SUSPEND(TRU1);
883 sigemptyset(&nset);
885 if (cmd == (char*)-1) {
886 if ((pid = n_child_fork()) == -1)
887 n_perr(_("fork"), 0);
888 else if (pid == 0) {
889 union {char const *ccp; int (*ptf)(void); int es;} u;
890 n_child_prepare(&nset, fd0, fd1);
891 close(p[READ]);
892 close(p[WRITE]);
893 /* TODO should close all other open FDs except stds and reset memory */
894 /* Standard I/O drives me insane! All we need is a sync operation
895 * that causes n_stdin to forget about any read buffer it may have.
896 * We cannot use fflush(3), this works with Musl and Solaris, but not
897 * with GlibC. (For at least pipes.) We cannot use fdreopen(),
898 * because this function does not exist! Luckily (!!!) we only use
899 * n_stdin not stdin in our child, otherwise all bets were off!
900 * TODO (Unless we would fiddle around with FILE* directly:
901 * TODO #ifdef __GLIBC__
902 * TODO n_stdin->_IO_read_ptr = n_stdin->_IO_read_end;
903 * TODO #elif *BSD*
904 * TODO n_stdin->_r = 0;
905 * TODO #elif n_OS_SOLARIS || n_OS_SUNOS
906 * TODO n_stdin->_cnt = 0;
907 * TODO #endif
908 * TODO ) which should have additional config test for sure! */
909 n_stdin = fdopen(STDIN_FILENO, "r");
910 /*n_stdout = fdopen(STDOUT_FILENO, "w");*/
911 /*n_stderr = fdopen(STDERR_FILENO, "w");*/
912 u.ccp = sh;
913 u.es = (*u.ptf)();
914 /*fflush(NULL);*/
915 _exit(u.es);
917 } else if (sh == NULL) {
918 pid = n_child_start(cmd, &nset, fd0, fd1, NULL, NULL, NULL, env_addon);
919 } else {
920 pid = n_child_start(sh, &nset, fd0, fd1, "-c", cmd, NULL, env_addon);
922 if (pid < 0) {
923 close(p[READ]);
924 close(p[WRITE]);
925 goto jleave;
927 close(hisside);
928 if ((rv = fdopen(myside, mod)) != NULL)
929 register_file(rv, 0, pid,
930 (tiosp == NULL ? FP_PIPE : FP_PIPE | FP_TERMIOS),
931 NULL, 0L, NULL, tiosp, osigint);
932 else
933 close(myside);
934 jleave:
935 if(rv == NULL && tiosp != NULL){
936 n_TERMCAP_RESUME(TRU1);
937 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
938 n_free(tiosp);
939 n_signal(SIGINT, osigint);
941 NYD_LEAVE;
942 return rv;
945 FL bool_t
946 Pclose(FILE *ptr, bool_t dowait)
948 n_sighdl_t osigint;
949 struct termios *tiosp;
950 int pid;
951 bool_t rv = FAL0;
952 NYD_ENTER;
954 pid = file_pid(ptr);
955 if(pid < 0)
956 goto jleave;
958 unregister_file(ptr, &tiosp, &osigint);
959 fclose(ptr);
961 if(dowait){
962 hold_all_sigs();
963 rv = n_child_wait(pid, NULL);
964 if(tiosp != NULL){
965 n_TERMCAP_RESUME(TRU1);
966 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
967 n_signal(SIGINT, osigint);
969 rele_all_sigs();
970 }else{
971 n_child_free(pid);
972 rv = TRU1;
975 if(tiosp != NULL)
976 free(tiosp);
977 jleave:
978 NYD_LEAVE;
979 return rv;
982 VL int
983 n_psignal(FILE *fp, int sig){
984 int rv;
985 NYD2_ENTER;
987 if((rv = file_pid(fp)) >= 0){
988 struct child *cp;
990 if((cp = a_popen_child_find(rv, FAL0)) != NULL){
991 if((rv = kill(rv, sig)) != 0)
992 rv = n_err_no;
993 }else
994 rv = -1;
996 NYD2_LEAVE;
997 return rv;
1000 FL FILE *
1001 n_pager_open(void)
1003 char const *env_add[2], *pager;
1004 FILE *rv;
1005 NYD_ENTER;
1007 assert(n_psonce & n_PSO_INTERACTIVE);
1009 pager = n_pager_get(env_add + 0);
1010 env_add[1] = NULL;
1012 if ((rv = Popen(pager, "w", NULL, env_add, n_CHILD_FD_PASS)) == NULL)
1013 n_perr(pager, 0);
1014 NYD_LEAVE;
1015 return rv;
1018 FL bool_t
1019 n_pager_close(FILE *fp)
1021 sighandler_type sh;
1022 bool_t rv;
1023 NYD_ENTER;
1025 sh = safe_signal(SIGPIPE, SIG_IGN);
1026 rv = Pclose(fp, TRU1);
1027 safe_signal(SIGPIPE, sh);
1028 NYD_LEAVE;
1029 return rv;
1032 FL void
1033 close_all_files(void)
1035 NYD_ENTER;
1036 while (fp_head != NULL)
1037 if ((fp_head->flags & FP_MASK) == FP_PIPE)
1038 Pclose(fp_head->fp, TRU1);
1039 else
1040 Fclose(fp_head->fp);
1041 NYD_LEAVE;
1044 /* TODO The entire n_child_ series should be replaced with an object, but
1045 * TODO at least have carrier arguments. We anyway need a command manager
1046 * TODO that keeps track and knows how to handle job control ++++! */
1048 FL int
1049 n_child_run(char const *cmd, sigset_t *mask_or_null, int infd, int outfd,
1050 char const *a0_or_null, char const *a1_or_null, char const *a2_or_null,
1051 char const **env_addon_or_null, int *wait_status_or_null)
1053 sigset_t nset, oset;
1054 sighandler_type soldint;
1055 int rv, e;
1056 enum {a_NONE = 0, a_INTIGN = 1<<0, a_TTY = 1<<1} f;
1057 NYD_ENTER;
1059 f = a_NONE;
1060 n_UNINIT(soldint, SIG_ERR);
1062 /* TODO Of course this is a joke given that during a "p*" the PAGER may
1063 * TODO be up and running while we play around like this... but i guess
1064 * TODO this can't be helped at all unless we perform complete and true
1065 * TODO process group separation and ensure we don't deadlock us out
1066 * TODO via TTY jobcontrol signal storms (could this really happen?).
1067 * TODO Or have a built-in pager. Or query any necessity BEFORE we start
1068 * TODO any action, and shall we find we need to run programs dump it
1069 * TODO all into a temporary file which is then passed through to the
1070 * TODO PAGER. Ugh. That still won't help for "needsterminal" anyway */
1071 if(infd == n_CHILD_FD_PASS || outfd == n_CHILD_FD_PASS){
1072 soldint = safe_signal(SIGINT, SIG_IGN);
1073 f = a_INTIGN;
1075 if(n_psonce & n_PSO_INTERACTIVE){
1076 f |= a_TTY;
1077 tcgetattr((n_psonce & n_PSO_TTYIN ? STDIN_FILENO : STDOUT_FILENO),
1078 &a_popen_tios);
1079 n_TERMCAP_SUSPEND(FAL0);
1080 sigfillset(&nset);
1081 sigdelset(&nset, SIGCHLD);
1082 sigdelset(&nset, SIGINT);
1083 /* sigdelset(&nset, SIGPIPE); TODO would need a handler */
1084 sigprocmask(SIG_BLOCK, &nset, &oset);
1085 a_popen_hadsig = 0;
1086 a_popen_jobsigs_up();
1090 if((rv = n_child_start(cmd, mask_or_null, infd, outfd, a0_or_null,
1091 a1_or_null, a2_or_null, env_addon_or_null)) < 0){
1092 e = n_err_no;
1093 rv = -1;
1094 }else{
1095 int ws;
1097 e = 0;
1098 if(n_child_wait(rv, &ws))
1099 rv = 0;
1100 else if(wait_status_or_null == NULL || !WIFEXITED(ws)){
1101 if(ok_blook(bsdcompat) || ok_blook(bsdmsgs))
1102 n_err(_("Fatal error in process\n"));
1103 e = n_ERR_CHILD;
1104 rv = -1;
1106 if(wait_status_or_null != NULL)
1107 *wait_status_or_null = ws;
1110 if(f & a_TTY){
1111 a_popen_jobsigs_down();
1112 n_TERMCAP_RESUME(a_popen_hadsig ? TRU1 : FAL0);
1113 tcsetattr(((n_psonce & n_PSO_TTYIN) ? STDIN_FILENO : STDOUT_FILENO),
1114 ((n_psonce & n_PSO_TTYIN) ? TCSAFLUSH : TCSADRAIN), &a_popen_tios);
1115 sigprocmask(SIG_SETMASK, &oset, NULL);
1117 if(f & a_INTIGN){
1118 if(soldint != SIG_IGN)
1119 safe_signal(SIGINT, soldint);
1122 if(e != 0)
1123 n_err_no = e;
1124 NYD_LEAVE;
1125 return rv;
1128 FL int
1129 n_child_start(char const *cmd, sigset_t *mask_or_null, int infd, int outfd,
1130 char const *a0_or_null, char const *a1_or_null, char const *a2_or_null,
1131 char const **env_addon_or_null)
1133 int rv, e;
1134 NYD_ENTER;
1136 if ((rv = n_child_fork()) == -1) {
1137 e = n_err_no;
1138 n_perr(_("fork"), 0);
1139 n_err_no = e;
1140 rv = -1;
1141 } else if (rv == 0) {
1142 char *argv[128];
1143 int i;
1145 if (env_addon_or_null != NULL) {
1146 extern char **environ;
1147 size_t ei, ei_orig, ai, ai_orig;
1148 char **env;
1150 /* TODO note we don't check the POSIX limit:
1151 * the total space used to store the environment and the arguments to
1152 * the process is limited to {ARG_MAX} bytes */
1153 for (ei = 0; environ[ei] != NULL; ++ei)
1155 ei_orig = ei;
1156 for (ai = 0; env_addon_or_null[ai] != NULL; ++ai)
1158 ai_orig = ai;
1159 env = ac_alloc(sizeof(*env) * (ei + ai +1));
1160 memcpy(env, environ, sizeof(*env) * ei);
1162 /* Replace all those keys that yet exist */
1163 while (ai-- > 0) {
1164 char const *ee, *kvs;
1165 size_t kl;
1167 ee = env_addon_or_null[ai];
1168 kvs = strchr(ee, '=');
1169 assert(kvs != NULL);
1170 kl = PTR2SIZE(kvs - ee);
1171 assert(kl > 0);
1172 for (ei = ei_orig; ei-- > 0;) {
1173 char const *ekvs = strchr(env[ei], '=');
1174 if (ekvs != NULL && kl == PTR2SIZE(ekvs - env[ei]) &&
1175 !memcmp(ee, env[ei], kl)) {
1176 env[ei] = n_UNCONST(ee);
1177 env_addon_or_null[ai] = NULL;
1178 break;
1183 /* And append the rest */
1184 for (ei = ei_orig, ai = ai_orig; ai-- > 0;)
1185 if (env_addon_or_null[ai] != NULL)
1186 env[ei++] = n_UNCONST(env_addon_or_null[ai]);
1188 env[ei] = NULL;
1189 environ = env;
1192 i = (int)getrawlist(TRU1, argv, n_NELEM(argv), cmd, strlen(cmd));
1194 if ((argv[i++] = n_UNCONST(a0_or_null)) != NULL &&
1195 (argv[i++] = n_UNCONST(a1_or_null)) != NULL &&
1196 (argv[i++] = n_UNCONST(a2_or_null)) != NULL)
1197 argv[i] = NULL;
1198 n_child_prepare(mask_or_null, infd, outfd);
1199 execvp(argv[0], argv);
1200 perror(argv[0]);
1201 _exit(n_EXIT_ERR);
1203 NYD_LEAVE;
1204 return rv;
1207 FL int
1208 n_child_fork(void){
1209 struct child *cp;
1210 int pid;
1211 NYD2_ENTER;
1213 cp = a_popen_child_find(0, TRU1);
1215 if((cp->pid = pid = fork()) == -1){
1216 a_popen_child_del(cp);
1217 n_perr(_("fork"), 0);
1219 NYD2_LEAVE;
1220 return pid;
1223 FL void
1224 n_child_prepare(sigset_t *nset_or_null, int infd, int outfd)
1226 int i;
1227 sigset_t fset;
1228 NYD_ENTER;
1230 /* All file descriptors other than 0, 1, and 2 are supposed to be cloexec */
1231 /* TODO WHAT IS WITH STDERR_FILENO DAMN? */
1232 if ((i = (infd == n_CHILD_FD_NULL)))
1233 infd = open(n_path_devnull, O_RDONLY);
1234 if (infd >= 0) {
1235 dup2(infd, STDIN_FILENO);
1236 if (i)
1237 close(infd);
1240 if ((i = (outfd == n_CHILD_FD_NULL)))
1241 outfd = open(n_path_devnull, O_WRONLY);
1242 if (outfd >= 0) {
1243 dup2(outfd, STDOUT_FILENO);
1244 if (i)
1245 close(outfd);
1248 if (nset_or_null != NULL) {
1249 for (i = 1; i < NSIG; ++i)
1250 if (sigismember(nset_or_null, i))
1251 safe_signal(i, SIG_IGN);
1252 if (!sigismember(nset_or_null, SIGINT))
1253 safe_signal(SIGINT, SIG_DFL);
1256 sigemptyset(&fset);
1257 sigprocmask(SIG_SETMASK, &fset, NULL);
1258 NYD_LEAVE;
1261 FL void
1262 n_child_free(int pid){
1263 sigset_t nset, oset;
1264 struct child *cp;
1265 NYD2_ENTER;
1267 sigemptyset(&nset);
1268 sigaddset(&nset, SIGCHLD);
1269 sigprocmask(SIG_BLOCK, &nset, &oset);
1271 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1272 if(cp->done)
1273 a_popen_child_del(cp);
1274 else
1275 cp->free = TRU1;
1278 sigprocmask(SIG_SETMASK, &oset, NULL);
1279 NYD2_LEAVE;
1282 FL bool_t
1283 n_child_wait(int pid, int *wait_status_or_null){
1284 #if !n_SIGSUSPEND_NOT_WAITPID
1285 sigset_t oset;
1286 #endif
1287 sigset_t nset;
1288 struct child *cp;
1289 int ws;
1290 bool_t rv;
1291 NYD_ENTER;
1293 #if !n_SIGSUSPEND_NOT_WAITPID
1294 sigemptyset(&nset);
1295 sigaddset(&nset, SIGCHLD);
1296 sigprocmask(SIG_BLOCK, &nset, &oset);
1297 #endif
1299 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1300 #if n_SIGSUSPEND_NOT_WAITPID
1301 sigfillset(&nset);
1302 sigdelset(&nset, SIGCHLD);
1303 while(!cp->done)
1304 sigsuspend(&nset); /* TODO we should allow more than SIGCHLD!! */
1305 ws = cp->status;
1306 #else
1307 waitpid(pid, &ws, 0);
1308 #endif
1309 a_popen_child_del(cp);
1310 }else
1311 ws = 0;
1313 #if !n_SIGSUSPEND_NOT_WAITPID
1314 sigprocmask(SIG_SETMASK, &oset, NULL);
1315 #endif
1317 if(wait_status_or_null != NULL)
1318 *wait_status_or_null = ws;
1319 rv = (WIFEXITED(ws) && WEXITSTATUS(ws) == 0);
1320 NYD_LEAVE;
1321 return rv;
1324 /* s-it-mode */