cc-test.sh: redo t_behave_mbox
[s-mailx.git] / popen.c
blobaff7f3f3e646ce763ec4b704fd44a539422fa520
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 - 2018 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,
52 FP_IMAP = 1u<<3,
53 FP_MAILDIR = 1u<<4,
54 FP_HOOK = 1u<<5,
55 FP_PIPE = 1u<<6,
56 FP_MASK = (1u<<7) - 1,
57 /* TODO FP_UNLINK: should be in a separated process so that unlinking
58 * TODO the temporary "garbage" is "safe"(r than it is like that) */
59 FP_UNLINK = 1u<<9,
60 FP_TERMIOS = 1u<<10
61 } flags;
62 long offset;
63 FILE *fp;
64 char *realfile;
65 char *save_cmd;
66 struct termios *fp_tios;
67 n_sighdl_t fp_osigint; /* Only if FP_TERMIOS */
70 struct child {
71 int pid;
72 char done;
73 char free;
74 int status;
75 struct child *link;
78 static struct fp *fp_head;
79 static struct child *_popen_child;
81 /* TODO Rather temporary: deal with job control with FD_PASS */
82 static struct termios a_popen_tios;
83 static sighandler_type a_popen_otstp, a_popen_ottin, a_popen_ottou;
84 static volatile int a_popen_hadsig;
86 static int a_popen_scan_mode(char const *mode, int *omode);
87 static void register_file(FILE *fp, int omode, int pid,
88 int flags, char const *realfile, long offset,
89 char const *save_cmd, struct termios *tiosp,
90 n_sighdl_t osigint);
91 static enum okay _file_save(struct fp *fpp);
92 static int a_popen_file_load(int flags, int infd, int outfd,
93 char const *load_cmd);
94 static enum okay unregister_file(FILE *fp, struct termios **tiosp,
95 n_sighdl_t *osigint);
96 static int file_pid(FILE *fp);
98 /* TODO Rather temporary: deal with job control with FD_PASS */
99 static void a_popen_jobsigs_up(void);
100 static void a_popen_jobsigs_down(void);
101 static void a_popen_jobsig(int sig);
103 /* Handle SIGCHLD */
104 static void a_popen_sigchld(int signo);
106 static struct child *a_popen_child_find(int pid, bool_t create);
107 static void a_popen_child_del(struct child *cp);
109 static int
110 a_popen_scan_mode(char const *mode, int *omode){
111 static struct{
112 char const mode[4];
113 int omode;
114 } const maps[] = {
115 {"r", O_RDONLY},
116 {"w", O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC},
117 {"wx", O_WRONLY | O_CREAT | O_EXCL},
118 {"a", O_WRONLY | O_APPEND | O_CREAT | n_O_NOXY_BITS},
119 {"a+", O_RDWR | O_APPEND | O_CREAT | n_O_NOXY_BITS},
120 {"r+", O_RDWR},
121 {"w+", O_RDWR | O_CREAT | O_EXCL}
123 int i;
124 NYD2_ENTER;
126 for(i = 0; UICMP(z, i, <, n_NELEM(maps)); ++i)
127 if(!strcmp(maps[i].mode, mode)){
128 *omode = maps[i].omode;
129 i = 0;
130 goto jleave;
133 DBG( n_alert(_("Internal error: bad stdio open mode %s"), mode); )
134 n_err_no = n_ERR_INVAL;
135 *omode = 0; /* (silence CC) */
136 i = -1;
137 jleave:
138 NYD2_LEAVE;
139 return i;
142 static void
143 register_file(FILE *fp, int omode, int pid, int flags,
144 char const *realfile, long offset, char const *save_cmd,
145 struct termios *tiosp, n_sighdl_t osigint)
147 struct fp *fpp;
148 NYD_ENTER;
150 assert(!(flags & FP_UNLINK) || realfile != NULL);
151 assert(!(flags & FP_TERMIOS) || tiosp != NULL);
153 fpp = smalloc(sizeof *fpp);
154 fpp->fp = fp;
155 fpp->omode = omode;
156 fpp->pid = pid;
157 fpp->link = fp_head;
158 fpp->flags = flags;
159 fpp->realfile = (realfile != NULL) ? sstrdup(realfile) : NULL;
160 fpp->save_cmd = (save_cmd != NULL) ? sstrdup(save_cmd) : NULL;
161 fpp->fp_tios = tiosp;
162 fpp->fp_osigint = osigint;
163 fpp->offset = offset;
164 fp_head = fpp;
165 NYD_LEAVE;
168 static enum okay
169 _file_save(struct fp *fpp)
171 char const *cmd[3];
172 int outfd;
173 enum okay rv;
174 NYD_ENTER;
176 if (fpp->omode == O_RDONLY) {
177 rv = OKAY;
178 goto jleave;
180 rv = STOP;
182 fflush(fpp->fp);
183 clearerr(fpp->fp);
185 /* Ensure the I/O library doesn't optimize the fseek(3) away! */
186 if(!n_real_seek(fpp->fp, fpp->offset, SEEK_SET)){
187 outfd = n_err_no;
188 n_err(_("Fatal: cannot restore file position and save %s: %s\n"),
189 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
190 goto jleave;
193 #ifdef HAVE_IMAP
194 if ((fpp->flags & FP_MASK) == FP_IMAP) {
195 rv = imap_append(fpp->realfile, fpp->fp, fpp->offset);
196 goto jleave;
198 #endif
200 if ((fpp->flags & FP_MASK) == FP_MAILDIR) {
201 rv = maildir_append(fpp->realfile, fpp->fp, fpp->offset);
202 goto jleave;
205 outfd = open(fpp->realfile,
206 ((fpp->omode | O_CREAT | (fpp->omode & O_APPEND ? 0 : O_TRUNC) |
207 n_O_NOXY_BITS) & ~O_EXCL), 0666);
208 if (outfd == -1) {
209 outfd = n_err_no;
210 n_err(_("Fatal: cannot create %s: %s\n"),
211 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
212 goto jleave;
215 cmd[2] = NULL;
216 switch(fpp->flags & FP_MASK){
217 case FP_HOOK:
218 if(n_poption & n_PO_D_V)
219 n_err(_("Using `filetype' handler %s to save %s\n"),
220 n_shexp_quote_cp(fpp->save_cmd, FAL0),
221 n_shexp_quote_cp(fpp->realfile, FAL0));
222 cmd[0] = ok_vlook(SHELL);
223 cmd[1] = "-c";
224 cmd[2] = fpp->save_cmd;
225 break;
226 default:
227 cmd[0] = "cat";
228 cmd[1] = NULL;
229 break;
231 if (n_child_run(cmd[0], 0, fileno(fpp->fp), outfd,
232 cmd[1], cmd[2], NULL, NULL, NULL) >= 0)
233 rv = OKAY;
235 close(outfd);
236 jleave:
237 NYD_LEAVE;
238 return rv;
241 static int
242 a_popen_file_load(int flags, int infd, int outfd, char const *load_cmd){
243 char const *cmd[3];
244 int rv;
245 NYD2_ENTER;
247 cmd[2] = NULL;
248 switch(flags & FP_MASK){
249 case FP_IMAP:
250 case FP_MAILDIR:
251 rv = 0;
252 goto jleave;
253 case FP_HOOK:
254 cmd[0] = ok_vlook(SHELL);
255 cmd[1] = "-c";
256 cmd[2] = load_cmd;
257 break;
258 default:
259 cmd[0] = "cat";
260 cmd[1] = NULL;
261 break;
264 rv = n_child_run(cmd[0], 0, infd, outfd, cmd[1], cmd[2], NULL, NULL, NULL);
265 jleave:
266 NYD2_LEAVE;
267 return rv;
270 static enum okay
271 unregister_file(FILE *fp, struct termios **tiosp, n_sighdl_t *osigint)
273 struct fp **pp, *p;
274 enum okay rv = OKAY;
275 NYD_ENTER;
277 if (tiosp)
278 *tiosp = NULL;
280 for (pp = &fp_head; (p = *pp) != NULL; pp = &p->link)
281 if (p->fp == fp) {
282 switch (p->flags & FP_MASK) {
283 case FP_RAW:
284 case FP_PIPE:
285 break;
286 default:
287 rv = _file_save(p);
288 break;
290 if ((p->flags & FP_UNLINK) && unlink(p->realfile))
291 rv = STOP;
293 *pp = p->link;
294 if (p->save_cmd != NULL)
295 free(p->save_cmd);
296 if (p->realfile != NULL)
297 free(p->realfile);
298 if (p->flags & FP_TERMIOS) {
299 if (tiosp != NULL) {
300 *tiosp = p->fp_tios;
301 *osigint = p->fp_osigint;
302 } else
303 free(p->fp_tios);
305 free(p);
306 goto jleave;
308 DBGOR(n_panic, n_alert)(_("Invalid file pointer"));
309 rv = STOP;
310 jleave:
311 NYD_LEAVE;
312 return rv;
315 static int
316 file_pid(FILE *fp)
318 int rv;
319 struct fp *p;
320 NYD2_ENTER;
322 rv = -1;
323 for (p = fp_head; p; p = p->link)
324 if (p->fp == fp) {
325 rv = p->pid;
326 break;
328 NYD2_LEAVE;
329 return rv;
332 static void
333 a_popen_jobsigs_up(void){
334 sigset_t nset, oset;
335 NYD2_ENTER;
337 sigfillset(&nset);
339 sigprocmask(SIG_BLOCK, &nset, &oset);
340 a_popen_otstp = safe_signal(SIGTSTP, &a_popen_jobsig);
341 a_popen_ottin = safe_signal(SIGTTIN, &a_popen_jobsig);
342 a_popen_ottou = safe_signal(SIGTTOU, &a_popen_jobsig);
344 /* This assumes oset contains nothing but SIGCHLD, so to say */
345 sigdelset(&oset, SIGTSTP);
346 sigdelset(&oset, SIGTTIN);
347 sigdelset(&oset, SIGTTOU);
348 sigprocmask(SIG_SETMASK, &oset, NULL);
349 NYD2_LEAVE;
352 static void
353 a_popen_jobsigs_down(void){
354 sigset_t nset, oset;
355 NYD2_ENTER;
357 sigfillset(&nset);
359 sigprocmask(SIG_BLOCK, &nset, &oset);
360 safe_signal(SIGTSTP, a_popen_otstp);
361 safe_signal(SIGTTIN, a_popen_ottin);
362 safe_signal(SIGTTOU, a_popen_ottou);
364 sigaddset(&oset, SIGTSTP);
365 sigaddset(&oset, SIGTTIN);
366 sigaddset(&oset, SIGTTOU);
367 sigprocmask(SIG_SETMASK, &oset, NULL);
368 NYD2_LEAVE;
371 static void
372 a_popen_jobsig(int sig){
373 sighandler_type oldact;
374 sigset_t nset;
375 bool_t hadsig;
376 NYD_X; /* Signal handler */
378 hadsig = (a_popen_hadsig != 0);
379 a_popen_hadsig = 1;
380 if(!hadsig)
381 n_TERMCAP_SUSPEND(TRU1);
383 oldact = safe_signal(sig, SIG_DFL);
385 sigemptyset(&nset);
386 sigaddset(&nset, sig);
387 sigprocmask(SIG_UNBLOCK, &nset, NULL);
388 n_raise(sig);
389 sigprocmask(SIG_BLOCK, &nset, NULL);
391 safe_signal(sig, oldact);
394 static void
395 a_popen_sigchld(int signo){
396 pid_t pid;
397 int status;
398 struct child *cp;
399 NYD_X; /* Signal handler */
400 n_UNUSED(signo);
402 for (;;) {
403 pid = waitpid(-1, &status, WNOHANG);
404 if (pid <= 0) {
405 if (pid == -1 && n_err_no == n_ERR_INTR)
406 continue;
407 break;
410 if ((cp = a_popen_child_find(pid, FAL0)) != NULL) {
411 cp->done = 1;
412 if (cp->free)
413 cp->pid = -1; /* XXX Was _delchild(cp);# */
414 else {
415 cp->status = status;
421 static struct child *
422 a_popen_child_find(int pid, bool_t create){
423 struct child **cpp, *cp;
424 NYD2_ENTER;
426 for(cpp = &_popen_child; (cp = *cpp) != NULL && cp->pid != pid;
427 cpp = &(*cpp)->link)
430 if(cp == NULL && create)
431 (*cpp = cp = scalloc(1, sizeof *cp))->pid = pid;
432 NYD2_LEAVE;
433 return cp;
436 static void
437 a_popen_child_del(struct child *cp){
438 struct child **cpp;
439 NYD2_ENTER;
441 cpp = &_popen_child;
443 for(;;){
444 if(*cpp == cp){
445 *cpp = cp->link;
446 free(cp);
447 break;
449 if(*(cpp = &(*cpp)->link) == NULL){
450 DBG( n_err("! a_popen_child_del(): implementation error\n"); )
451 break;
454 NYD2_LEAVE;
457 FL void
458 n_child_manager_start(void)
460 struct sigaction nact, oact;
461 NYD_ENTER;
463 nact.sa_handler = &a_popen_sigchld;
464 sigemptyset(&nact.sa_mask);
465 nact.sa_flags = SA_RESTART
466 #ifdef SA_NOCLDSTOP
467 | SA_NOCLDSTOP
468 #endif
470 if (sigaction(SIGCHLD, &nact, &oact) != 0)
471 n_panic(_("Cannot install signal handler for child process management"));
472 NYD_LEAVE;
475 FL FILE *
476 safe_fopen(char const *file, char const *oflags, int *xflags)
478 int osflags, fd;
479 FILE *fp = NULL;
480 NYD2_ENTER; /* (only for Fopen() and once in go.c) */
482 if (a_popen_scan_mode(oflags, &osflags) < 0)
483 goto jleave;
484 osflags |= _O_CLOEXEC;
485 if (xflags != NULL)
486 *xflags = osflags;
488 if ((fd = open(file, osflags, 0666)) == -1)
489 goto jleave;
490 _CLOEXEC_SET(fd);
492 fp = fdopen(fd, oflags);
493 jleave:
494 NYD2_LEAVE;
495 return fp;
498 FL FILE *
499 Fopen(char const *file, char const *oflags)
501 FILE *fp;
502 int osflags;
503 NYD_ENTER;
505 if ((fp = safe_fopen(file, oflags, &osflags)) != NULL)
506 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
507 NYD_LEAVE;
508 return fp;
511 FL FILE *
512 Fdopen(int fd, char const *oflags, bool_t nocloexec)
514 FILE *fp;
515 int osflags;
516 NYD_ENTER;
518 a_popen_scan_mode(oflags, &osflags);
519 if (!nocloexec)
520 osflags |= _O_CLOEXEC; /* Ensured to be set by caller as documented! */
522 if ((fp = fdopen(fd, oflags)) != NULL)
523 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
524 NYD_LEAVE;
525 return fp;
528 FL int
529 Fclose(FILE *fp)
531 int i = 0;
532 NYD_ENTER;
534 if (unregister_file(fp, NULL, NULL) == OKAY)
535 i |= 1;
536 if (fclose(fp) == 0)
537 i |= 2;
538 NYD_LEAVE;
539 return (i == 3 ? 0 : EOF);
542 FL FILE *
543 n_fopen_any(char const *file, char const *oflags, /* TODO should take flags */
544 enum n_fopen_state *fs_or_null){ /* TODO as bits, return state */
545 /* TODO Support file locking upon open time */
546 long offset;
547 enum protocol p;
548 enum oflags rof;
549 int osflags, flags, omode, infd;
550 char const *cload, *csave;
551 enum n_fopen_state fs;
552 FILE *rv;
553 NYD_ENTER;
555 rv = NULL;
556 fs = n_FOPEN_STATE_NONE;
557 cload = csave = NULL;
559 if(a_popen_scan_mode(oflags, &osflags) < 0)
560 goto jleave;
562 flags = 0;
563 rof = OF_RDWR | OF_UNLINK;
564 if(osflags & O_APPEND)
565 rof |= OF_APPEND;
566 omode = (osflags == O_RDONLY) ? R_OK : R_OK | W_OK;
568 /* We don't want to find mbox.bz2 when doing "copy * mbox", but only for
569 * "file mbox", so don't try hooks when writing */
570 p = which_protocol(csave = file, TRU1, ((omode & W_OK) == 0), &file);
571 fs = (enum n_fopen_state)p;
572 switch(p){
573 default:
574 goto jleave;
575 case n_PROTO_IMAP:
576 #ifdef HAVE_IMAP
577 file = csave;
578 flags |= FP_IMAP;
579 osflags = O_RDWR | O_APPEND | O_CREAT | n_O_NOXY_BITS;
580 infd = -1;
581 break;
582 #else
583 n_err_no = n_ERR_OPNOTSUPP;
584 goto jleave;
585 #endif
586 case n_PROTO_MAILDIR:
587 if(fs_or_null != NULL && !access(file, F_OK))
588 fs |= n_FOPEN_STATE_EXISTS;
589 flags |= FP_MAILDIR;
590 osflags = O_RDWR | O_APPEND | O_CREAT | n_O_NOXY_BITS;
591 infd = -1;
592 break;
593 case n_PROTO_FILE:{
594 struct n_file_type ft;
596 if(!(osflags & O_EXCL) && fs_or_null != NULL && !access(file, F_OK))
597 fs |= n_FOPEN_STATE_EXISTS;
599 if(n_filetype_exists(&ft, file)){
600 flags |= FP_HOOK;
601 cload = ft.ft_load_dat;
602 csave = ft.ft_save_dat;
603 /* Cause truncation for compressor/hook output files */
604 osflags &= ~O_APPEND;
605 rof &= ~OF_APPEND;
606 if((infd = open(file, (omode & W_OK ? O_RDWR : O_RDONLY))) != -1){
607 fs |= n_FOPEN_STATE_EXISTS;
608 if(n_poption & n_PO_D_V)
609 n_err(_("Using `filetype' handler %s to load %s\n"),
610 n_shexp_quote_cp(cload, FAL0), n_shexp_quote_cp(file, FAL0));
611 }else if(!(osflags & O_CREAT) || n_err_no != n_ERR_NOENT)
612 goto jleave;
613 }else{
614 /*flags |= FP_RAW;*/
615 rv = Fopen(file, oflags);
616 if((osflags & O_EXCL) && rv == NULL)
617 fs |= n_FOPEN_STATE_EXISTS;
618 goto jleave;
620 }break;
623 /* Note rv is not yet register_file()d, fclose() it in error path! */
624 if((rv = Ftmp(NULL, "fopenany", rof)) == NULL){
625 n_perr(_("tmpfile"), 0);
626 goto jerr;
629 if(flags & (FP_IMAP | FP_MAILDIR))
631 else if(infd >= 0){
632 if(a_popen_file_load(flags, infd, fileno(rv), cload) < 0){
633 jerr:
634 if(rv != NULL)
635 fclose(rv);
636 rv = NULL;
637 if(infd >= 0)
638 close(infd);
639 goto jleave;
641 }else{
642 if((infd = creat(file, 0666)) == -1){
643 fclose(rv);
644 rv = NULL;
645 goto jleave;
649 if(infd >= 0)
650 close(infd);
651 fflush(rv);
653 if(!(osflags & O_APPEND))
654 rewind(rv);
655 if((offset = ftell(rv)) == -1){
656 Fclose(rv);
657 rv = NULL;
658 goto jleave;
661 register_file(rv, osflags, 0, flags, file, offset, csave, NULL,NULL);
662 jleave:
663 if(fs_or_null != NULL)
664 *fs_or_null = fs;
665 NYD_LEAVE;
666 return rv;
669 FL FILE *
670 Ftmp(char **fn, char const *namehint, enum oflags oflags)
672 /* The 8 is arbitrary but leaves room for a six character suffix (the
673 * POSIX minimum path length is 14, though we don't check that XXX).
674 * 8 should be more than sufficient given that we use base64url encoding
675 * for our random string */
676 enum {_RANDCHARS = 8u};
678 char *cp_base, *cp;
679 size_t maxname, xlen, i;
680 char const *tmpdir;
681 int osoflags, fd, e;
682 bool_t relesigs;
683 FILE *fp;
684 NYD_ENTER;
686 assert(namehint != NULL);
687 assert((oflags & OF_WRONLY) || (oflags & OF_RDWR));
688 assert(!(oflags & OF_RDONLY));
689 assert(!(oflags & OF_REGISTER_UNLINK) || (oflags & OF_REGISTER));
691 fp = NULL;
692 relesigs = FAL0;
693 e = 0;
694 tmpdir = ok_vlook(TMPDIR);
695 maxname = NAME_MAX;
696 #ifdef HAVE_PATHCONF
697 { long pc;
699 if ((pc = pathconf(tmpdir, _PC_NAME_MAX)) != -1)
700 maxname = (size_t)pc;
702 #endif
704 if ((oflags & OF_SUFFIX) && *namehint != '\0') {
705 if ((xlen = strlen(namehint)) > maxname - _RANDCHARS) {
706 n_err_no = n_ERR_NAMETOOLONG;
707 goto jleave;
709 } else
710 xlen = 0;
712 /* Prepare the template string once, then iterate over the random range */
713 cp_base =
714 cp = smalloc(strlen(tmpdir) + 1 + maxname +1);
715 cp = sstpcpy(cp, tmpdir);
716 *cp++ = '/';
718 char *x = sstpcpy(cp, VAL_UAGENT);
719 *x++ = '-';
720 if (!(oflags & OF_SUFFIX))
721 x = sstpcpy(x, namehint);
723 i = PTR2SIZE(x - cp);
724 if (i > maxname - xlen - _RANDCHARS) {
725 size_t j = maxname - xlen - _RANDCHARS;
726 x -= i - j;
729 if ((oflags & OF_SUFFIX) && xlen > 0)
730 memcpy(x + _RANDCHARS, namehint, xlen);
732 x[xlen + _RANDCHARS] = '\0';
733 cp = x;
736 osoflags = O_CREAT | O_EXCL | _O_CLOEXEC;
737 osoflags |= (oflags & OF_WRONLY) ? O_WRONLY : O_RDWR;
738 if (oflags & OF_APPEND)
739 osoflags |= O_APPEND;
741 for(relesigs = TRU1, i = 0;; ++i){
742 memcpy(cp, n_random_create_cp(_RANDCHARS, NULL), _RANDCHARS);
744 hold_all_sigs();
746 if((fd = open(cp_base, osoflags, 0600)) != -1){
747 _CLOEXEC_SET(fd);
748 break;
750 if(i >= FTMP_OPEN_TRIES){
751 e = n_err_no;
752 goto jfree;
754 rele_all_sigs();
757 if (oflags & OF_REGISTER) {
758 char const *osflags = (oflags & OF_RDWR ? "w+" : "w");
759 int osflagbits;
761 a_popen_scan_mode(osflags, &osflagbits); /* TODO osoflags&xy ?!!? */
762 if ((fp = fdopen(fd, osflags)) != NULL)
763 register_file(fp, osflagbits | _O_CLOEXEC, 0,
764 (FP_RAW | (oflags & OF_REGISTER_UNLINK ? FP_UNLINK : 0)),
765 cp_base, 0L, NULL, NULL,NULL);
766 } else
767 fp = fdopen(fd, (oflags & OF_RDWR ? "w+" : "w"));
769 if (fp == NULL || (oflags & OF_UNLINK)) {
770 e = n_err_no;
771 unlink(cp_base);
772 goto jfree;
773 }else if(fp != NULL){
774 /* We will succeed and keep the file around for further usage, likely
775 * another stream will be opened for pure reading purposes (this is true
776 * at the time of this writing. A restrictive umask(2) settings may have
777 * turned the path inaccessible, so ensure it may be read at least!
778 * TODO once ok_vlook() can return an integer, look up *umask* first! */
779 (void)fchmod(fd, S_IWUSR | S_IRUSR);
782 if (fn != NULL)
783 *fn = cp_base;
784 else
785 free(cp_base);
786 jleave:
787 if (relesigs && (fp == NULL || !(oflags & OF_HOLDSIGS)))
788 rele_all_sigs();
789 if (fp == NULL)
790 n_err_no = e;
791 NYD_LEAVE;
792 return fp;
793 jfree:
794 if ((cp = cp_base) != NULL)
795 free(cp);
796 goto jleave;
799 FL void
800 Ftmp_release(char **fn)
802 char *cp;
803 NYD_ENTER;
805 cp = *fn;
806 *fn = NULL;
807 if (cp != NULL) {
808 unlink(cp);
809 rele_all_sigs();
810 free(cp);
812 NYD_LEAVE;
815 FL void
816 Ftmp_free(char **fn) /* TODO DROP: OF_REGISTER_FREEPATH! */
818 char *cp;
819 NYD_ENTER;
821 cp = *fn;
822 *fn = NULL;
823 if (cp != NULL)
824 free(cp);
825 NYD_LEAVE;
828 FL bool_t
829 pipe_cloexec(int fd[2]){
830 bool_t rv;
831 NYD_ENTER;
833 rv = FAL0;
835 #ifdef HAVE_PIPE2
836 if(pipe2(fd, O_CLOEXEC) != -1)
837 rv = TRU1;
838 #else
839 if(pipe(fd) != -1){
840 n_fd_cloexec_set(fd[0]);
841 n_fd_cloexec_set(fd[1]);
842 rv = TRU1;
844 #endif
845 NYD_LEAVE;
846 return rv;
849 FL FILE *
850 Popen(char const *cmd, char const *mode, char const *sh,
851 char const **env_addon, int newfd1)
853 int p[2], myside, hisside, fd0, fd1, pid;
854 sigset_t nset;
855 char mod[2];
856 n_sighdl_t osigint;
857 struct termios *tiosp;
858 FILE *rv;
859 NYD_ENTER;
861 /* First clean up child structures */
862 /* C99 */{
863 struct child **cpp, *cp;
865 hold_all_sigs();
866 for (cpp = &_popen_child; *cpp != NULL;) {
867 if ((*cpp)->pid == -1) {
868 cp = *cpp;
869 *cpp = cp->link;
870 free(cp);
871 } else
872 cpp = &(*cpp)->link;
874 rele_all_sigs();
877 rv = NULL;
878 tiosp = NULL;
879 n_UNINIT(osigint, SIG_ERR);
880 mod[0] = '0', mod[1] = '\0';
882 if (!pipe_cloexec(p))
883 goto jleave;
885 if (*mode == 'r') {
886 myside = p[READ];
887 fd0 = n_CHILD_FD_PASS;
888 hisside = fd1 = p[WRITE];
889 mod[0] = *mode;
890 } else if (*mode == 'W') {
891 myside = p[WRITE];
892 hisside = fd0 = p[READ];
893 fd1 = newfd1;
894 mod[0] = 'w';
895 } else {
896 myside = p[WRITE];
897 hisside = fd0 = p[READ];
898 fd1 = n_CHILD_FD_PASS;
899 mod[0] = 'w';
902 /* In interactive mode both STDIN and STDOUT point to the terminal. If we
903 * pass through the TTY restore terminal attributes after pipe returns.
904 * XXX It shouldn't matter which FD we actually use in this case */
905 if ((n_psonce & n_PSO_INTERACTIVE) && (fd0 == n_CHILD_FD_PASS ||
906 fd1 == n_CHILD_FD_PASS)) {
907 osigint = n_signal(SIGINT, SIG_IGN);
908 tiosp = smalloc(sizeof *tiosp);
909 tcgetattr(STDIN_FILENO, tiosp);
910 n_TERMCAP_SUSPEND(TRU1);
913 sigemptyset(&nset);
915 if (cmd == (char*)-1) {
916 if ((pid = n_child_fork()) == -1)
917 n_perr(_("fork"), 0);
918 else if (pid == 0) {
919 union {char const *ccp; int (*ptf)(void); int es;} u;
920 n_child_prepare(&nset, fd0, fd1);
921 close(p[READ]);
922 close(p[WRITE]);
923 /* TODO should close all other open FDs except stds and reset memory */
924 /* Standard I/O drives me insane! All we need is a sync operation
925 * that causes n_stdin to forget about any read buffer it may have.
926 * We cannot use fflush(3), this works with Musl and Solaris, but not
927 * with GlibC. (For at least pipes.) We cannot use fdreopen(),
928 * because this function does not exist! Luckily (!!!) we only use
929 * n_stdin not stdin in our child, otherwise all bets were off!
930 * TODO (Unless we would fiddle around with FILE* directly:
931 * TODO #ifdef __GLIBC__
932 * TODO n_stdin->_IO_read_ptr = n_stdin->_IO_read_end;
933 * TODO #elif *BSD*
934 * TODO n_stdin->_r = 0;
935 * TODO #elif n_OS_SOLARIS || n_OS_SUNOS
936 * TODO n_stdin->_cnt = 0;
937 * TODO #endif
938 * TODO ) which should have additional config test for sure! */
939 n_stdin = fdopen(STDIN_FILENO, "r");
940 /*n_stdout = fdopen(STDOUT_FILENO, "w");*/
941 /*n_stderr = fdopen(STDERR_FILENO, "w");*/
942 u.ccp = sh;
943 u.es = (*u.ptf)();
944 /*fflush(NULL);*/
945 _exit(u.es);
947 } else if (sh == NULL) {
948 pid = n_child_start(cmd, &nset, fd0, fd1, NULL, NULL, NULL, env_addon);
949 } else {
950 pid = n_child_start(sh, &nset, fd0, fd1, "-c", cmd, NULL, env_addon);
952 if (pid < 0) {
953 close(p[READ]);
954 close(p[WRITE]);
955 goto jleave;
957 close(hisside);
958 if ((rv = fdopen(myside, mod)) != NULL)
959 register_file(rv, 0, pid,
960 (tiosp == NULL ? FP_PIPE : FP_PIPE | FP_TERMIOS),
961 NULL, 0L, NULL, tiosp, osigint);
962 else
963 close(myside);
964 jleave:
965 if(rv == NULL && tiosp != NULL){
966 n_TERMCAP_RESUME(TRU1);
967 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
968 n_free(tiosp);
969 n_signal(SIGINT, osigint);
971 NYD_LEAVE;
972 return rv;
975 FL bool_t
976 Pclose(FILE *ptr, bool_t dowait)
978 n_sighdl_t osigint;
979 struct termios *tiosp;
980 int pid;
981 bool_t rv = FAL0;
982 NYD_ENTER;
984 pid = file_pid(ptr);
985 if(pid < 0)
986 goto jleave;
988 unregister_file(ptr, &tiosp, &osigint);
989 fclose(ptr);
991 if(dowait){
992 hold_all_sigs();
993 rv = n_child_wait(pid, NULL);
994 if(tiosp != NULL){
995 n_TERMCAP_RESUME(TRU1);
996 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
997 n_signal(SIGINT, osigint);
999 rele_all_sigs();
1000 }else{
1001 n_child_free(pid);
1002 rv = TRU1;
1005 if(tiosp != NULL)
1006 free(tiosp);
1007 jleave:
1008 NYD_LEAVE;
1009 return rv;
1012 VL int
1013 n_psignal(FILE *fp, int sig){
1014 int rv;
1015 NYD2_ENTER;
1017 if((rv = file_pid(fp)) >= 0){
1018 struct child *cp;
1020 if((cp = a_popen_child_find(rv, FAL0)) != NULL){
1021 if((rv = kill(rv, sig)) != 0)
1022 rv = n_err_no;
1023 }else
1024 rv = -1;
1026 NYD2_LEAVE;
1027 return rv;
1030 FL FILE *
1031 n_pager_open(void)
1033 char const *env_add[2], *pager;
1034 FILE *rv;
1035 NYD_ENTER;
1037 assert(n_psonce & n_PSO_INTERACTIVE);
1039 pager = n_pager_get(env_add + 0);
1040 env_add[1] = NULL;
1042 if ((rv = Popen(pager, "w", NULL, env_add, n_CHILD_FD_PASS)) == NULL)
1043 n_perr(pager, 0);
1044 NYD_LEAVE;
1045 return rv;
1048 FL bool_t
1049 n_pager_close(FILE *fp)
1051 sighandler_type sh;
1052 bool_t rv;
1053 NYD_ENTER;
1055 sh = safe_signal(SIGPIPE, SIG_IGN);
1056 rv = Pclose(fp, TRU1);
1057 safe_signal(SIGPIPE, sh);
1058 NYD_LEAVE;
1059 return rv;
1062 FL void
1063 close_all_files(void)
1065 NYD_ENTER;
1066 while (fp_head != NULL)
1067 if ((fp_head->flags & FP_MASK) == FP_PIPE)
1068 Pclose(fp_head->fp, TRU1);
1069 else
1070 Fclose(fp_head->fp);
1071 NYD_LEAVE;
1074 /* TODO The entire n_child_ series should be replaced with an object, but
1075 * TODO at least have carrier arguments. We anyway need a command manager
1076 * TODO that keeps track and knows how to handle job control ++++! */
1078 FL int
1079 n_child_run(char const *cmd, sigset_t *mask_or_null, int infd, int outfd,
1080 char const *a0_or_null, char const *a1_or_null, char const *a2_or_null,
1081 char const **env_addon_or_null, int *wait_status_or_null)
1083 sigset_t nset, oset;
1084 sighandler_type soldint;
1085 int rv, e;
1086 enum {a_NONE = 0, a_INTIGN = 1<<0, a_TTY = 1<<1} f;
1087 NYD_ENTER;
1089 f = a_NONE;
1090 n_UNINIT(soldint, SIG_ERR);
1092 /* TODO Of course this is a joke given that during a "p*" the PAGER may
1093 * TODO be up and running while we play around like this... but i guess
1094 * TODO this can't be helped at all unless we perform complete and true
1095 * TODO process group separation and ensure we don't deadlock us out
1096 * TODO via TTY jobcontrol signal storms (could this really happen?).
1097 * TODO Or have a built-in pager. Or query any necessity BEFORE we start
1098 * TODO any action, and shall we find we need to run programs dump it
1099 * TODO all into a temporary file which is then passed through to the
1100 * TODO PAGER. Ugh. That still won't help for "needsterminal" anyway */
1101 if(infd == n_CHILD_FD_PASS || outfd == n_CHILD_FD_PASS){
1102 soldint = safe_signal(SIGINT, SIG_IGN);
1103 f = a_INTIGN;
1105 if(n_psonce & n_PSO_INTERACTIVE){
1106 f |= a_TTY;
1107 tcgetattr((n_psonce & n_PSO_TTYIN ? STDIN_FILENO : STDOUT_FILENO),
1108 &a_popen_tios);
1109 n_TERMCAP_SUSPEND(FAL0);
1110 sigfillset(&nset);
1111 sigdelset(&nset, SIGCHLD);
1112 sigdelset(&nset, SIGINT);
1113 /* sigdelset(&nset, SIGPIPE); TODO would need a handler */
1114 sigprocmask(SIG_BLOCK, &nset, &oset);
1115 a_popen_hadsig = 0;
1116 a_popen_jobsigs_up();
1120 if((rv = n_child_start(cmd, mask_or_null, infd, outfd, a0_or_null,
1121 a1_or_null, a2_or_null, env_addon_or_null)) < 0){
1122 e = n_err_no;
1123 rv = -1;
1124 }else{
1125 int ws;
1127 e = 0;
1128 if(n_child_wait(rv, &ws))
1129 rv = 0;
1130 else if(wait_status_or_null == NULL || !WIFEXITED(ws)){
1131 if(ok_blook(bsdcompat) || ok_blook(bsdmsgs))
1132 n_err(_("Fatal error in process\n"));
1133 e = n_ERR_CHILD;
1134 rv = -1;
1136 if(wait_status_or_null != NULL)
1137 *wait_status_or_null = ws;
1140 if(f & a_TTY){
1141 a_popen_jobsigs_down();
1142 n_TERMCAP_RESUME(a_popen_hadsig ? TRU1 : FAL0);
1143 tcsetattr(((n_psonce & n_PSO_TTYIN) ? STDIN_FILENO : STDOUT_FILENO),
1144 ((n_psonce & n_PSO_TTYIN) ? TCSAFLUSH : TCSADRAIN), &a_popen_tios);
1145 sigprocmask(SIG_SETMASK, &oset, NULL);
1147 if(f & a_INTIGN){
1148 if(soldint != SIG_IGN)
1149 safe_signal(SIGINT, soldint);
1152 if(e != 0)
1153 n_err_no = e;
1154 NYD_LEAVE;
1155 return rv;
1158 FL int
1159 n_child_start(char const *cmd, sigset_t *mask_or_null, int infd, int outfd,
1160 char const *a0_or_null, char const *a1_or_null, char const *a2_or_null,
1161 char const **env_addon_or_null)
1163 int rv, e;
1164 NYD_ENTER;
1166 if ((rv = n_child_fork()) == -1) {
1167 e = n_err_no;
1168 n_perr(_("fork"), 0);
1169 n_err_no = e;
1170 rv = -1;
1171 } else if (rv == 0) {
1172 char *argv[128];
1173 int i;
1175 if (env_addon_or_null != NULL) {
1176 extern char **environ;
1177 size_t ei, ei_orig, ai, ai_orig;
1178 char **env;
1180 /* TODO note we don't check the POSIX limit:
1181 * the total space used to store the environment and the arguments to
1182 * the process is limited to {ARG_MAX} bytes */
1183 for (ei = 0; environ[ei] != NULL; ++ei)
1185 ei_orig = ei;
1186 for (ai = 0; env_addon_or_null[ai] != NULL; ++ai)
1188 ai_orig = ai;
1189 env = ac_alloc(sizeof(*env) * (ei + ai +1));
1190 memcpy(env, environ, sizeof(*env) * ei);
1192 /* Replace all those keys that yet exist */
1193 while (ai-- > 0) {
1194 char const *ee, *kvs;
1195 size_t kl;
1197 ee = env_addon_or_null[ai];
1198 kvs = strchr(ee, '=');
1199 assert(kvs != NULL);
1200 kl = PTR2SIZE(kvs - ee);
1201 assert(kl > 0);
1202 for (ei = ei_orig; ei-- > 0;) {
1203 char const *ekvs = strchr(env[ei], '=');
1204 if (ekvs != NULL && kl == PTR2SIZE(ekvs - env[ei]) &&
1205 !memcmp(ee, env[ei], kl)) {
1206 env[ei] = n_UNCONST(ee);
1207 env_addon_or_null[ai] = NULL;
1208 break;
1213 /* And append the rest */
1214 for (ei = ei_orig, ai = ai_orig; ai-- > 0;)
1215 if (env_addon_or_null[ai] != NULL)
1216 env[ei++] = n_UNCONST(env_addon_or_null[ai]);
1218 env[ei] = NULL;
1219 environ = env;
1222 i = (int)getrawlist(TRU1, argv, n_NELEM(argv), cmd, strlen(cmd));
1224 if ((argv[i++] = n_UNCONST(a0_or_null)) != NULL &&
1225 (argv[i++] = n_UNCONST(a1_or_null)) != NULL &&
1226 (argv[i++] = n_UNCONST(a2_or_null)) != NULL)
1227 argv[i] = NULL;
1228 n_child_prepare(mask_or_null, infd, outfd);
1229 execvp(argv[0], argv);
1230 perror(argv[0]);
1231 _exit(n_EXIT_ERR);
1233 NYD_LEAVE;
1234 return rv;
1237 FL int
1238 n_child_fork(void){
1239 /* Strictly speaking we should do so in the waitpid(2) case too, but since
1240 * we explicitly waitpid(2) on the pid if just the structure exists, which
1241 * n_child_wait() does in the parent, all is fine */
1242 #if n_SIGSUSPEND_NOT_WAITPID
1243 sigset_t nset, oset;
1244 #endif
1245 struct child *cp;
1246 int pid;
1247 NYD2_ENTER;
1249 #if n_SIGSUSPEND_NOT_WAITPID
1250 sigfillset(&nset);
1251 sigprocmask(SIG_BLOCK, &nset, &oset);
1252 #endif
1254 cp = a_popen_child_find(0, TRU1);
1256 if((cp->pid = pid = fork()) == -1){
1257 a_popen_child_del(cp);
1258 n_perr(_("fork"), 0);
1261 #if n_SIGSUSPEND_NOT_WAITPID
1262 sigprocmask(SIG_SETMASK, &oset, NULL);
1263 #endif
1264 NYD2_LEAVE;
1265 return pid;
1268 FL void
1269 n_child_prepare(sigset_t *nset_or_null, int infd, int outfd)
1271 int i;
1272 sigset_t fset;
1273 NYD_ENTER;
1275 /* All file descriptors other than 0, 1, and 2 are supposed to be cloexec */
1276 /* TODO WHAT IS WITH STDERR_FILENO DAMN? */
1277 if ((i = (infd == n_CHILD_FD_NULL)))
1278 infd = open(n_path_devnull, O_RDONLY);
1279 if (infd >= 0) {
1280 dup2(infd, STDIN_FILENO);
1281 if (i)
1282 close(infd);
1285 if ((i = (outfd == n_CHILD_FD_NULL)))
1286 outfd = open(n_path_devnull, O_WRONLY);
1287 if (outfd >= 0) {
1288 dup2(outfd, STDOUT_FILENO);
1289 if (i)
1290 close(outfd);
1293 if (nset_or_null != NULL) {
1294 for (i = 1; i < NSIG; ++i)
1295 if (sigismember(nset_or_null, i))
1296 safe_signal(i, SIG_IGN);
1297 if (!sigismember(nset_or_null, SIGINT))
1298 safe_signal(SIGINT, SIG_DFL);
1301 sigemptyset(&fset);
1302 sigprocmask(SIG_SETMASK, &fset, NULL);
1303 NYD_LEAVE;
1306 FL void
1307 n_child_free(int pid){
1308 sigset_t nset, oset;
1309 struct child *cp;
1310 NYD2_ENTER;
1312 sigemptyset(&nset);
1313 sigaddset(&nset, SIGCHLD);
1314 sigprocmask(SIG_BLOCK, &nset, &oset);
1316 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1317 if(cp->done)
1318 a_popen_child_del(cp);
1319 else
1320 cp->free = TRU1;
1323 sigprocmask(SIG_SETMASK, &oset, NULL);
1324 NYD2_LEAVE;
1327 FL bool_t
1328 n_child_wait(int pid, int *wait_status_or_null){
1329 #if !n_SIGSUSPEND_NOT_WAITPID
1330 sigset_t oset;
1331 #endif
1332 sigset_t nset;
1333 struct child *cp;
1334 int ws;
1335 bool_t rv;
1336 NYD_ENTER;
1338 #if !n_SIGSUSPEND_NOT_WAITPID
1339 sigemptyset(&nset);
1340 sigaddset(&nset, SIGCHLD);
1341 sigprocmask(SIG_BLOCK, &nset, &oset);
1342 #endif
1344 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1345 #if n_SIGSUSPEND_NOT_WAITPID
1346 sigfillset(&nset);
1347 sigdelset(&nset, SIGCHLD);
1348 while(!cp->done)
1349 sigsuspend(&nset); /* TODO we should allow more than SIGCHLD!! */
1350 ws = cp->status;
1351 #else
1352 waitpid(pid, &ws, 0);
1353 #endif
1354 a_popen_child_del(cp);
1355 }else
1356 ws = 0;
1358 #if !n_SIGSUSPEND_NOT_WAITPID
1359 sigprocmask(SIG_SETMASK, &oset, NULL);
1360 #endif
1362 if(wait_status_or_null != NULL)
1363 *wait_status_or_null = ws;
1364 rv = (WIFEXITED(ws) && WEXITSTATUS(ws) == 0);
1365 NYD_LEAVE;
1366 return rv;
1369 /* s-it-mode */