make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / popen.c
blob075f7a3d91df72cb1ac02296b0f836ac3dfe1124
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 * SPDX-License-Identifier: BSD-3-Clause
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE popen
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 #define READ 0
44 #define WRITE 1
46 struct fp {
47 struct fp *link;
48 int omode;
49 int pid;
50 enum {
51 FP_RAW,
53 FP_IMAP = 1u<<3,
54 FP_MAILDIR = 1u<<4,
55 FP_HOOK = 1u<<5,
56 FP_PIPE = 1u<<6,
57 FP_MASK = (1u<<7) - 1,
58 /* TODO FP_UNLINK: should be in a separated process so that unlinking
59 * TODO the temporary "garbage" is "safe"(r than it is like that) */
60 FP_UNLINK = 1u<<9,
61 FP_TERMIOS = 1u<<10
62 } flags;
63 long offset;
64 FILE *fp;
65 char *realfile;
66 char *save_cmd;
67 struct termios *fp_tios;
68 n_sighdl_t fp_osigint; /* Only if FP_TERMIOS */
71 struct child {
72 int pid;
73 char done;
74 char free;
75 int status;
76 struct child *link;
79 static struct fp *fp_head;
80 static struct child *_popen_child;
82 /* TODO Rather temporary: deal with job control with FD_PASS */
83 static struct termios a_popen_tios;
84 static sighandler_type a_popen_otstp, a_popen_ottin, a_popen_ottou;
85 static volatile int a_popen_hadsig;
87 static int a_popen_scan_mode(char const *mode, int *omode);
88 static void register_file(FILE *fp, int omode, int pid,
89 int flags, char const *realfile, long offset,
90 char const *save_cmd, struct termios *tiosp,
91 n_sighdl_t osigint);
92 static enum okay _file_save(struct fp *fpp);
93 static int a_popen_file_load(int flags, int infd, int outfd,
94 char const *load_cmd);
95 static enum okay unregister_file(FILE *fp, struct termios **tiosp,
96 n_sighdl_t *osigint);
97 static int file_pid(FILE *fp);
99 /* TODO Rather temporary: deal with job control with FD_PASS */
100 static void a_popen_jobsigs_up(void);
101 static void a_popen_jobsigs_down(void);
102 static void a_popen_jobsig(int sig);
104 /* Handle SIGCHLD */
105 static void a_popen_sigchld(int signo);
107 static struct child *a_popen_child_find(int pid, bool_t create);
108 static void a_popen_child_del(struct child *cp);
110 static int
111 a_popen_scan_mode(char const *mode, int *omode){
112 static struct{
113 char const mode[4];
114 int omode;
115 } const maps[] = {
116 {"r", O_RDONLY},
117 {"w", O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC},
118 {"wx", O_WRONLY | O_CREAT | O_EXCL},
119 {"a", O_WRONLY | O_APPEND | O_CREAT | n_O_NOXY_BITS},
120 {"a+", O_RDWR | O_APPEND | O_CREAT | n_O_NOXY_BITS},
121 {"r+", O_RDWR},
122 {"w+", O_RDWR | O_CREAT | O_EXCL}
124 int i;
125 NYD2_ENTER;
127 for(i = 0; UICMP(z, i, <, n_NELEM(maps)); ++i)
128 if(!strcmp(maps[i].mode, mode)){
129 *omode = maps[i].omode;
130 i = 0;
131 goto jleave;
134 DBG( n_alert(_("Internal error: bad stdio open mode %s"), mode); )
135 n_err_no = n_ERR_INVAL;
136 *omode = 0; /* (silence CC) */
137 i = -1;
138 jleave:
139 NYD2_LEAVE;
140 return i;
143 static void
144 register_file(FILE *fp, int omode, int pid, int flags,
145 char const *realfile, long offset, char const *save_cmd,
146 struct termios *tiosp, n_sighdl_t osigint)
148 struct fp *fpp;
149 NYD_ENTER;
151 assert(!(flags & FP_UNLINK) || realfile != NULL);
152 assert(!(flags & FP_TERMIOS) || tiosp != NULL);
154 fpp = n_alloc(sizeof *fpp);
155 fpp->fp = fp;
156 fpp->omode = omode;
157 fpp->pid = pid;
158 fpp->link = fp_head;
159 fpp->flags = flags;
160 fpp->realfile = (realfile != NULL) ? sstrdup(realfile) : NULL;
161 fpp->save_cmd = (save_cmd != NULL) ? sstrdup(save_cmd) : NULL;
162 fpp->fp_tios = tiosp;
163 fpp->fp_osigint = osigint;
164 fpp->offset = offset;
165 fp_head = fpp;
166 NYD_LEAVE;
169 static enum okay
170 _file_save(struct fp *fpp)
172 char const *cmd[3];
173 int outfd;
174 enum okay rv;
175 NYD_ENTER;
177 if (fpp->omode == O_RDONLY) {
178 rv = OKAY;
179 goto jleave;
181 rv = STOP;
183 fflush(fpp->fp);
184 clearerr(fpp->fp);
186 /* Ensure the I/O library doesn't optimize the fseek(3) away! */
187 if(!n_real_seek(fpp->fp, fpp->offset, SEEK_SET)){
188 outfd = n_err_no;
189 n_err(_("Fatal: cannot restore file position and save %s: %s\n"),
190 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
191 goto jleave;
194 #ifdef HAVE_IMAP
195 if ((fpp->flags & FP_MASK) == FP_IMAP) {
196 rv = imap_append(fpp->realfile, fpp->fp, fpp->offset);
197 goto jleave;
199 #endif
201 #ifdef HAVE_MAILDIR
202 if ((fpp->flags & FP_MASK) == FP_MAILDIR) {
203 rv = maildir_append(fpp->realfile, fpp->fp, fpp->offset);
204 goto jleave;
206 #endif
208 outfd = open(fpp->realfile,
209 ((fpp->omode | O_CREAT | (fpp->omode & O_APPEND ? 0 : O_TRUNC) |
210 n_O_NOXY_BITS) & ~O_EXCL), 0666);
211 if (outfd == -1) {
212 outfd = n_err_no;
213 n_err(_("Fatal: cannot create %s: %s\n"),
214 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
215 goto jleave;
218 cmd[2] = NULL;
219 switch(fpp->flags & FP_MASK){
220 case FP_HOOK:
221 if(n_poption & n_PO_D_V)
222 n_err(_("Using `filetype' handler %s to save %s\n"),
223 n_shexp_quote_cp(fpp->save_cmd, FAL0),
224 n_shexp_quote_cp(fpp->realfile, FAL0));
225 cmd[0] = ok_vlook(SHELL);
226 cmd[1] = "-c";
227 cmd[2] = fpp->save_cmd;
228 break;
229 default:
230 cmd[0] = "cat";
231 cmd[1] = NULL;
232 break;
234 if (n_child_run(cmd[0], 0, fileno(fpp->fp), outfd,
235 cmd[1], cmd[2], NULL, NULL, NULL) >= 0)
236 rv = OKAY;
238 close(outfd);
239 jleave:
240 NYD_LEAVE;
241 return rv;
244 static int
245 a_popen_file_load(int flags, int infd, int outfd, char const *load_cmd){
246 char const *cmd[3];
247 int rv;
248 NYD2_ENTER;
250 cmd[2] = NULL;
251 switch(flags & FP_MASK){
252 case FP_IMAP:
253 case FP_MAILDIR:
254 rv = 0;
255 goto jleave;
256 case FP_HOOK:
257 cmd[0] = ok_vlook(SHELL);
258 cmd[1] = "-c";
259 cmd[2] = load_cmd;
260 break;
261 default:
262 cmd[0] = "cat";
263 cmd[1] = NULL;
264 break;
267 rv = n_child_run(cmd[0], 0, infd, outfd, cmd[1], cmd[2], NULL, NULL, NULL);
268 jleave:
269 NYD2_LEAVE;
270 return rv;
273 static enum okay
274 unregister_file(FILE *fp, struct termios **tiosp, n_sighdl_t *osigint)
276 struct fp **pp, *p;
277 enum okay rv = OKAY;
278 NYD_ENTER;
280 if (tiosp)
281 *tiosp = NULL;
283 for (pp = &fp_head; (p = *pp) != NULL; pp = &p->link)
284 if (p->fp == fp) {
285 switch (p->flags & FP_MASK) {
286 case FP_RAW:
287 case FP_PIPE:
288 break;
289 default:
290 rv = _file_save(p);
291 break;
293 if ((p->flags & FP_UNLINK) && unlink(p->realfile))
294 rv = STOP;
296 *pp = p->link;
297 if (p->save_cmd != NULL)
298 n_free(p->save_cmd);
299 if (p->realfile != NULL)
300 n_free(p->realfile);
301 if (p->flags & FP_TERMIOS) {
302 if (tiosp != NULL) {
303 *tiosp = p->fp_tios;
304 *osigint = p->fp_osigint;
305 } else
306 n_free(p->fp_tios);
308 n_free(p);
309 goto jleave;
311 DBGOR(n_panic, n_alert)(_("Invalid file pointer"));
312 rv = STOP;
313 jleave:
314 NYD_LEAVE;
315 return rv;
318 static int
319 file_pid(FILE *fp)
321 int rv;
322 struct fp *p;
323 NYD2_ENTER;
325 rv = -1;
326 for (p = fp_head; p; p = p->link)
327 if (p->fp == fp) {
328 rv = p->pid;
329 break;
331 NYD2_LEAVE;
332 return rv;
335 static void
336 a_popen_jobsigs_up(void){
337 sigset_t nset, oset;
338 NYD2_ENTER;
340 sigfillset(&nset);
342 sigprocmask(SIG_BLOCK, &nset, &oset);
343 a_popen_otstp = safe_signal(SIGTSTP, &a_popen_jobsig);
344 a_popen_ottin = safe_signal(SIGTTIN, &a_popen_jobsig);
345 a_popen_ottou = safe_signal(SIGTTOU, &a_popen_jobsig);
347 /* This assumes oset contains nothing but SIGCHLD, so to say */
348 sigdelset(&oset, SIGTSTP);
349 sigdelset(&oset, SIGTTIN);
350 sigdelset(&oset, SIGTTOU);
351 sigprocmask(SIG_SETMASK, &oset, NULL);
352 NYD2_LEAVE;
355 static void
356 a_popen_jobsigs_down(void){
357 sigset_t nset, oset;
358 NYD2_ENTER;
360 sigfillset(&nset);
362 sigprocmask(SIG_BLOCK, &nset, &oset);
363 safe_signal(SIGTSTP, a_popen_otstp);
364 safe_signal(SIGTTIN, a_popen_ottin);
365 safe_signal(SIGTTOU, a_popen_ottou);
367 sigaddset(&oset, SIGTSTP);
368 sigaddset(&oset, SIGTTIN);
369 sigaddset(&oset, SIGTTOU);
370 sigprocmask(SIG_SETMASK, &oset, NULL);
371 NYD2_LEAVE;
374 static void
375 a_popen_jobsig(int sig){
376 sighandler_type oldact;
377 sigset_t nset;
378 bool_t hadsig;
379 NYD_X; /* Signal handler */
381 hadsig = (a_popen_hadsig != 0);
382 a_popen_hadsig = 1;
383 if(!hadsig)
384 n_TERMCAP_SUSPEND(TRU1);
386 oldact = safe_signal(sig, SIG_DFL);
388 sigemptyset(&nset);
389 sigaddset(&nset, sig);
390 sigprocmask(SIG_UNBLOCK, &nset, NULL);
391 n_raise(sig);
392 sigprocmask(SIG_BLOCK, &nset, NULL);
394 safe_signal(sig, oldact);
397 static void
398 a_popen_sigchld(int signo){
399 pid_t pid;
400 int status;
401 struct child *cp;
402 NYD_X; /* Signal handler */
403 n_UNUSED(signo);
405 for (;;) {
406 pid = waitpid(-1, &status, WNOHANG);
407 if (pid <= 0) {
408 if (pid == -1 && n_err_no == n_ERR_INTR)
409 continue;
410 break;
413 if ((cp = a_popen_child_find(pid, FAL0)) != NULL) {
414 cp->done = 1;
415 if (cp->free)
416 cp->pid = -1; /* XXX Was _delchild(cp);# */
417 else {
418 cp->status = status;
424 static struct child *
425 a_popen_child_find(int pid, bool_t create){
426 struct child **cpp, *cp;
427 NYD2_ENTER;
429 for(cpp = &_popen_child; (cp = *cpp) != NULL && cp->pid != pid;
430 cpp = &(*cpp)->link)
433 if(cp == NULL && create)
434 (*cpp = cp = n_calloc(1, sizeof *cp))->pid = pid;
435 NYD2_LEAVE;
436 return cp;
439 static void
440 a_popen_child_del(struct child *cp){
441 struct child **cpp;
442 NYD2_ENTER;
444 cpp = &_popen_child;
446 for(;;){
447 if(*cpp == cp){
448 *cpp = cp->link;
449 n_free(cp);
450 break;
452 if(*(cpp = &(*cpp)->link) == NULL){
453 DBG( n_err("! a_popen_child_del(): implementation error\n"); )
454 break;
457 NYD2_LEAVE;
460 FL void
461 n_child_manager_start(void)
463 struct sigaction nact, oact;
464 NYD_ENTER;
466 nact.sa_handler = &a_popen_sigchld;
467 sigemptyset(&nact.sa_mask);
468 nact.sa_flags = SA_RESTART
469 #ifdef SA_NOCLDSTOP
470 | SA_NOCLDSTOP
471 #endif
473 if (sigaction(SIGCHLD, &nact, &oact) != 0)
474 n_panic(_("Cannot install signal handler for child process management"));
475 NYD_LEAVE;
478 FL FILE *
479 safe_fopen(char const *file, char const *oflags, int *xflags)
481 int osflags, fd;
482 FILE *fp = NULL;
483 NYD2_ENTER; /* (only for Fopen() and once in go.c) */
485 if (a_popen_scan_mode(oflags, &osflags) < 0)
486 goto jleave;
487 osflags |= _O_CLOEXEC;
488 if (xflags != NULL)
489 *xflags = osflags;
491 if ((fd = open(file, osflags, 0666)) == -1)
492 goto jleave;
493 _CLOEXEC_SET(fd);
495 fp = fdopen(fd, oflags);
496 jleave:
497 NYD2_LEAVE;
498 return fp;
501 FL FILE *
502 Fopen(char const *file, char const *oflags)
504 FILE *fp;
505 int osflags;
506 NYD_ENTER;
508 if ((fp = safe_fopen(file, oflags, &osflags)) != NULL)
509 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
510 NYD_LEAVE;
511 return fp;
514 FL FILE *
515 Fdopen(int fd, char const *oflags, bool_t nocloexec)
517 FILE *fp;
518 int osflags;
519 NYD_ENTER;
521 a_popen_scan_mode(oflags, &osflags);
522 if (!nocloexec)
523 osflags |= _O_CLOEXEC; /* Ensured to be set by caller as documented! */
525 if ((fp = fdopen(fd, oflags)) != NULL)
526 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
527 NYD_LEAVE;
528 return fp;
531 FL int
532 Fclose(FILE *fp)
534 int i = 0;
535 NYD_ENTER;
537 if (unregister_file(fp, NULL, NULL) == OKAY)
538 i |= 1;
539 if (fclose(fp) == 0)
540 i |= 2;
541 NYD_LEAVE;
542 return (i == 3 ? 0 : EOF);
545 FL FILE *
546 n_fopen_any(char const *file, char const *oflags, /* TODO should take flags */
547 enum n_fopen_state *fs_or_null){ /* TODO as bits, return state */
548 /* TODO Support file locking upon open time */
549 long offset;
550 enum protocol p;
551 enum oflags rof;
552 int osflags, flags, omode, infd;
553 char const *cload, *csave;
554 enum n_fopen_state fs;
555 FILE *rv;
556 NYD_ENTER;
558 rv = NULL;
559 fs = n_FOPEN_STATE_NONE;
560 cload = csave = NULL;
562 if(a_popen_scan_mode(oflags, &osflags) < 0)
563 goto jleave;
565 flags = 0;
566 rof = OF_RDWR | OF_UNLINK;
567 if(osflags & O_APPEND)
568 rof |= OF_APPEND;
569 omode = (osflags == O_RDONLY) ? R_OK : R_OK | W_OK;
571 /* We don't want to find mbox.bz2 when doing "copy * mbox", but only for
572 * "file mbox", so don't try hooks when writing */
573 p = which_protocol(csave = file, TRU1, ((omode & W_OK) == 0), &file);
574 fs = (enum n_fopen_state)p;
575 switch(p){
576 default:
577 goto jleave;
578 case n_PROTO_IMAP:
579 #ifdef HAVE_IMAP
580 file = csave;
581 flags |= FP_IMAP;
582 osflags = O_RDWR | O_APPEND | O_CREAT | n_O_NOXY_BITS;
583 infd = -1;
584 break;
585 #else
586 n_err_no = n_ERR_OPNOTSUPP;
587 goto jleave;
588 #endif
589 case n_PROTO_MAILDIR:
590 #ifdef HAVE_MAILDIR
591 if(fs_or_null != NULL && !access(file, F_OK))
592 fs |= n_FOPEN_STATE_EXISTS;
593 flags |= FP_MAILDIR;
594 osflags = O_RDWR | O_APPEND | O_CREAT | n_O_NOXY_BITS;
595 infd = -1;
596 break;
597 #else
598 n_err_no = n_ERR_OPNOTSUPP;
599 goto jleave;
600 #endif
601 case n_PROTO_FILE:{
602 struct n_file_type ft;
604 if(!(osflags & O_EXCL) && fs_or_null != NULL && !access(file, F_OK))
605 fs |= n_FOPEN_STATE_EXISTS;
607 if(n_filetype_exists(&ft, file)){
608 flags |= FP_HOOK;
609 cload = ft.ft_load_dat;
610 csave = ft.ft_save_dat;
611 /* Cause truncation for compressor/hook output files */
612 osflags &= ~O_APPEND;
613 rof &= ~OF_APPEND;
614 if((infd = open(file, (omode & W_OK ? O_RDWR : O_RDONLY))) != -1){
615 fs |= n_FOPEN_STATE_EXISTS;
616 if(n_poption & n_PO_D_V)
617 n_err(_("Using `filetype' handler %s to load %s\n"),
618 n_shexp_quote_cp(cload, FAL0), n_shexp_quote_cp(file, FAL0));
619 }else if(!(osflags & O_CREAT) || n_err_no != n_ERR_NOENT)
620 goto jleave;
621 }else{
622 /*flags |= FP_RAW;*/
623 rv = Fopen(file, oflags);
624 if((osflags & O_EXCL) && rv == NULL)
625 fs |= n_FOPEN_STATE_EXISTS;
626 goto jleave;
628 }break;
631 /* Note rv is not yet register_file()d, fclose() it in error path! */
632 if((rv = Ftmp(NULL, "fopenany", rof)) == NULL){
633 n_perr(_("tmpfile"), 0);
634 goto jerr;
637 if(flags & (FP_IMAP | FP_MAILDIR))
639 else if(infd >= 0){
640 if(a_popen_file_load(flags, infd, fileno(rv), cload) < 0){
641 jerr:
642 if(rv != NULL)
643 fclose(rv);
644 rv = NULL;
645 if(infd >= 0)
646 close(infd);
647 goto jleave;
649 }else{
650 if((infd = creat(file, 0666)) == -1){
651 fclose(rv);
652 rv = NULL;
653 goto jleave;
657 if(infd >= 0)
658 close(infd);
659 fflush(rv);
661 if(!(osflags & O_APPEND))
662 rewind(rv);
663 if((offset = ftell(rv)) == -1){
664 Fclose(rv);
665 rv = NULL;
666 goto jleave;
669 register_file(rv, osflags, 0, flags, file, offset, csave, NULL,NULL);
670 jleave:
671 if(fs_or_null != NULL)
672 *fs_or_null = fs;
673 NYD_LEAVE;
674 return rv;
677 FL FILE *
678 Ftmp(char **fn, char const *namehint, enum oflags oflags)
680 /* The 8 is arbitrary but leaves room for a six character suffix (the
681 * POSIX minimum path length is 14, though we don't check that XXX).
682 * 8 should be more than sufficient given that we use base64url encoding
683 * for our random string */
684 enum {_RANDCHARS = 8u};
686 char *cp_base, *cp;
687 size_t maxname, xlen, i;
688 char const *tmpdir;
689 int osoflags, fd, e;
690 bool_t relesigs;
691 FILE *fp;
692 NYD_ENTER;
694 assert(namehint != NULL);
695 assert((oflags & OF_WRONLY) || (oflags & OF_RDWR));
696 assert(!(oflags & OF_RDONLY));
697 assert(!(oflags & OF_REGISTER_UNLINK) || (oflags & OF_REGISTER));
699 fp = NULL;
700 relesigs = FAL0;
701 e = 0;
702 tmpdir = ok_vlook(TMPDIR);
703 maxname = NAME_MAX;
704 #ifdef HAVE_PATHCONF
705 { long pc;
707 if ((pc = pathconf(tmpdir, _PC_NAME_MAX)) != -1)
708 maxname = (size_t)pc;
710 #endif
712 if ((oflags & OF_SUFFIX) && *namehint != '\0') {
713 if ((xlen = strlen(namehint)) > maxname - _RANDCHARS) {
714 n_err_no = n_ERR_NAMETOOLONG;
715 goto jleave;
717 } else
718 xlen = 0;
720 /* Prepare the template string once, then iterate over the random range */
721 cp_base =
722 cp = n_lofi_alloc(strlen(tmpdir) + 1 + maxname +1);
723 cp = sstpcpy(cp, tmpdir);
724 *cp++ = '/';
726 char *x = sstpcpy(cp, VAL_UAGENT);
727 *x++ = '-';
728 if (!(oflags & OF_SUFFIX))
729 x = sstpcpy(x, namehint);
731 i = PTR2SIZE(x - cp);
732 if (i > maxname - xlen - _RANDCHARS) {
733 size_t j = maxname - xlen - _RANDCHARS;
734 x -= i - j;
737 if ((oflags & OF_SUFFIX) && xlen > 0)
738 memcpy(x + _RANDCHARS, namehint, xlen);
740 x[xlen + _RANDCHARS] = '\0';
741 cp = x;
744 osoflags = O_CREAT | O_EXCL | _O_CLOEXEC;
745 osoflags |= (oflags & OF_WRONLY) ? O_WRONLY : O_RDWR;
746 if (oflags & OF_APPEND)
747 osoflags |= O_APPEND;
749 for(relesigs = TRU1, i = 0;; ++i){
750 memcpy(cp, n_random_create_cp(_RANDCHARS, NULL), _RANDCHARS);
752 hold_all_sigs();
754 if((fd = open(cp_base, osoflags, 0600)) != -1){
755 _CLOEXEC_SET(fd);
756 break;
758 if(i >= FTMP_OPEN_TRIES){
759 e = n_err_no;
760 goto jfree;
762 rele_all_sigs();
765 if (oflags & OF_REGISTER) {
766 char const *osflags = (oflags & OF_RDWR ? "w+" : "w");
767 int osflagbits;
769 a_popen_scan_mode(osflags, &osflagbits); /* TODO osoflags&xy ?!!? */
770 if ((fp = fdopen(fd, osflags)) != NULL)
771 register_file(fp, osflagbits | _O_CLOEXEC, 0,
772 (FP_RAW | (oflags & OF_REGISTER_UNLINK ? FP_UNLINK : 0)),
773 cp_base, 0L, NULL, NULL,NULL);
774 } else
775 fp = fdopen(fd, (oflags & OF_RDWR ? "w+" : "w"));
777 if (fp == NULL || (oflags & OF_UNLINK)) {
778 e = n_err_no;
779 unlink(cp_base);
780 goto jfree;
781 }else if(fp != NULL){
782 /* We will succeed and keep the file around for further usage, likely
783 * another stream will be opened for pure reading purposes (this is true
784 * at the time of this writing. A restrictive umask(2) settings may have
785 * turned the path inaccessible, so ensure it may be read at least!
786 * TODO once ok_vlook() can return an integer, look up *umask* first! */
787 (void)fchmod(fd, S_IWUSR | S_IRUSR);
790 if(fn != NULL){
791 i = strlen(cp_base) +1;
792 cp = (oflags & OF_FN_AUTOREC) ? n_autorec_alloc(i) : n_alloc(i);
793 memcpy(cp, cp_base, i);
794 *fn = cp;
796 n_lofi_free(cp_base);
797 jleave:
798 if (relesigs && (fp == NULL || !(oflags & OF_HOLDSIGS)))
799 rele_all_sigs();
800 if (fp == NULL)
801 n_err_no = e;
802 NYD_LEAVE;
803 return fp;
804 jfree:
805 if((cp = cp_base) != NULL)
806 n_lofi_free(cp);
807 goto jleave;
810 FL void
811 Ftmp_release(char **fn)
813 char *cp;
814 NYD_ENTER;
816 cp = *fn;
817 *fn = NULL;
818 if (cp != NULL) {
819 unlink(cp);
820 rele_all_sigs();
821 n_free(cp);
823 NYD_LEAVE;
826 FL void
827 Ftmp_free(char **fn) /* TODO DROP: OF_REGISTER_FREEPATH! */
829 char *cp;
830 NYD_ENTER;
832 cp = *fn;
833 *fn = NULL;
834 if (cp != NULL)
835 n_free(cp);
836 NYD_LEAVE;
839 FL bool_t
840 pipe_cloexec(int fd[2]){
841 bool_t rv;
842 NYD_ENTER;
844 rv = FAL0;
846 #ifdef HAVE_PIPE2
847 if(pipe2(fd, O_CLOEXEC) != -1)
848 rv = TRU1;
849 #else
850 if(pipe(fd) != -1){
851 n_fd_cloexec_set(fd[0]);
852 n_fd_cloexec_set(fd[1]);
853 rv = TRU1;
855 #endif
856 NYD_LEAVE;
857 return rv;
860 FL FILE *
861 Popen(char const *cmd, char const *mode, char const *sh,
862 char const **env_addon, int newfd1)
864 int p[2], myside, hisside, fd0, fd1, pid;
865 sigset_t nset;
866 char mod[2];
867 n_sighdl_t osigint;
868 struct termios *tiosp;
869 FILE *rv;
870 NYD_ENTER;
872 /* First clean up child structures */
873 /* C99 */{
874 struct child **cpp, *cp;
876 hold_all_sigs();
877 for (cpp = &_popen_child; *cpp != NULL;) {
878 if ((*cpp)->pid == -1) {
879 cp = *cpp;
880 *cpp = cp->link;
881 n_free(cp);
882 } else
883 cpp = &(*cpp)->link;
885 rele_all_sigs();
888 rv = NULL;
889 tiosp = NULL;
890 n_UNINIT(osigint, SIG_ERR);
891 mod[0] = '0', mod[1] = '\0';
893 if (!pipe_cloexec(p))
894 goto jleave;
896 if (*mode == 'r') {
897 myside = p[READ];
898 fd0 = n_CHILD_FD_PASS;
899 hisside = fd1 = p[WRITE];
900 mod[0] = *mode;
901 } else if (*mode == 'W') {
902 myside = p[WRITE];
903 hisside = fd0 = p[READ];
904 fd1 = newfd1;
905 mod[0] = 'w';
906 } else {
907 myside = p[WRITE];
908 hisside = fd0 = p[READ];
909 fd1 = n_CHILD_FD_PASS;
910 mod[0] = 'w';
913 /* In interactive mode both STDIN and STDOUT point to the terminal. If we
914 * pass through the TTY restore terminal attributes after pipe returns.
915 * XXX It shouldn't matter which FD we actually use in this case */
916 if ((n_psonce & n_PSO_INTERACTIVE) && (fd0 == n_CHILD_FD_PASS ||
917 fd1 == n_CHILD_FD_PASS)) {
918 osigint = n_signal(SIGINT, SIG_IGN);
919 tiosp = n_alloc(sizeof *tiosp);
920 tcgetattr(STDIN_FILENO, tiosp);
921 n_TERMCAP_SUSPEND(TRU1);
924 sigemptyset(&nset);
926 if (cmd == (char*)-1) {
927 if ((pid = n_child_fork()) == -1)
928 n_perr(_("fork"), 0);
929 else if (pid == 0) {
930 union {char const *ccp; int (*ptf)(void); int es;} u;
931 n_child_prepare(&nset, fd0, fd1);
932 close(p[READ]);
933 close(p[WRITE]);
934 /* TODO should close all other open FDs except stds and reset memory */
935 /* Standard I/O drives me insane! All we need is a sync operation
936 * that causes n_stdin to forget about any read buffer it may have.
937 * We cannot use fflush(3), this works with Musl and Solaris, but not
938 * with GlibC. (For at least pipes.) We cannot use fdreopen(),
939 * because this function does not exist! Luckily (!!!) we only use
940 * n_stdin not stdin in our child, otherwise all bets were off!
941 * TODO (Unless we would fiddle around with FILE* directly:
942 * TODO #ifdef __GLIBC__
943 * TODO n_stdin->_IO_read_ptr = n_stdin->_IO_read_end;
944 * TODO #elif *BSD*
945 * TODO n_stdin->_r = 0;
946 * TODO #elif n_OS_SOLARIS || n_OS_SUNOS
947 * TODO n_stdin->_cnt = 0;
948 * TODO #endif
949 * TODO ) which should have additional config test for sure! */
950 n_stdin = fdopen(STDIN_FILENO, "r");
951 /*n_stdout = fdopen(STDOUT_FILENO, "w");*/
952 /*n_stderr = fdopen(STDERR_FILENO, "w");*/
953 u.ccp = sh;
954 u.es = (*u.ptf)();
955 /*fflush(NULL);*/
956 _exit(u.es);
958 } else if (sh == NULL) {
959 pid = n_child_start(cmd, &nset, fd0, fd1, NULL, NULL, NULL, env_addon);
960 } else {
961 pid = n_child_start(sh, &nset, fd0, fd1, "-c", cmd, NULL, env_addon);
963 if (pid < 0) {
964 close(p[READ]);
965 close(p[WRITE]);
966 goto jleave;
968 close(hisside);
969 if ((rv = fdopen(myside, mod)) != NULL)
970 register_file(rv, 0, pid,
971 (tiosp == NULL ? FP_PIPE : FP_PIPE | FP_TERMIOS),
972 NULL, 0L, NULL, tiosp, osigint);
973 else
974 close(myside);
975 jleave:
976 if(rv == NULL && tiosp != NULL){
977 n_TERMCAP_RESUME(TRU1);
978 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
979 n_free(tiosp);
980 n_signal(SIGINT, osigint);
982 NYD_LEAVE;
983 return rv;
986 FL bool_t
987 Pclose(FILE *ptr, bool_t dowait)
989 n_sighdl_t osigint;
990 struct termios *tiosp;
991 int pid;
992 bool_t rv = FAL0;
993 NYD_ENTER;
995 pid = file_pid(ptr);
996 if(pid < 0)
997 goto jleave;
999 unregister_file(ptr, &tiosp, &osigint);
1000 fclose(ptr);
1002 if(dowait){
1003 hold_all_sigs();
1004 rv = n_child_wait(pid, NULL);
1005 if(tiosp != NULL){
1006 n_TERMCAP_RESUME(TRU1);
1007 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
1008 n_signal(SIGINT, osigint);
1010 rele_all_sigs();
1011 }else{
1012 n_child_free(pid);
1013 rv = TRU1;
1016 if(tiosp != NULL)
1017 n_free(tiosp);
1018 jleave:
1019 NYD_LEAVE;
1020 return rv;
1023 VL int
1024 n_psignal(FILE *fp, int sig){
1025 int rv;
1026 NYD2_ENTER;
1028 if((rv = file_pid(fp)) >= 0){
1029 struct child *cp;
1031 if((cp = a_popen_child_find(rv, FAL0)) != NULL){
1032 if((rv = kill(rv, sig)) != 0)
1033 rv = n_err_no;
1034 }else
1035 rv = -1;
1037 NYD2_LEAVE;
1038 return rv;
1041 FL FILE *
1042 n_pager_open(void)
1044 char const *env_add[2], *pager;
1045 FILE *rv;
1046 NYD_ENTER;
1048 assert(n_psonce & n_PSO_INTERACTIVE);
1050 pager = n_pager_get(env_add + 0);
1051 env_add[1] = NULL;
1053 if ((rv = Popen(pager, "w", NULL, env_add, n_CHILD_FD_PASS)) == NULL)
1054 n_perr(pager, 0);
1055 NYD_LEAVE;
1056 return rv;
1059 FL bool_t
1060 n_pager_close(FILE *fp)
1062 sighandler_type sh;
1063 bool_t rv;
1064 NYD_ENTER;
1066 sh = safe_signal(SIGPIPE, SIG_IGN);
1067 rv = Pclose(fp, TRU1);
1068 safe_signal(SIGPIPE, sh);
1069 NYD_LEAVE;
1070 return rv;
1073 FL void
1074 close_all_files(void)
1076 NYD_ENTER;
1077 while (fp_head != NULL)
1078 if ((fp_head->flags & FP_MASK) == FP_PIPE)
1079 Pclose(fp_head->fp, TRU1);
1080 else
1081 Fclose(fp_head->fp);
1082 NYD_LEAVE;
1085 /* TODO The entire n_child_ series should be replaced with an object, but
1086 * TODO at least have carrier arguments. We anyway need a command manager
1087 * TODO that keeps track and knows how to handle job control ++++! */
1089 FL int
1090 n_child_run(char const *cmd, sigset_t *mask_or_null, int infd, int outfd,
1091 char const *a0_or_null, char const *a1_or_null, char const *a2_or_null,
1092 char const **env_addon_or_null, int *wait_status_or_null)
1094 sigset_t nset, oset;
1095 sighandler_type soldint;
1096 int rv, e;
1097 enum {a_NONE = 0, a_INTIGN = 1<<0, a_TTY = 1<<1} f;
1098 NYD_ENTER;
1100 f = a_NONE;
1101 n_UNINIT(soldint, SIG_ERR);
1103 /* TODO Of course this is a joke given that during a "p*" the PAGER may
1104 * TODO be up and running while we play around like this... but i guess
1105 * TODO this can't be helped at all unless we perform complete and true
1106 * TODO process group separation and ensure we don't deadlock us out
1107 * TODO via TTY jobcontrol signal storms (could this really happen?).
1108 * TODO Or have a built-in pager. Or query any necessity BEFORE we start
1109 * TODO any action, and shall we find we need to run programs dump it
1110 * TODO all into a temporary file which is then passed through to the
1111 * TODO PAGER. Ugh. That still won't help for "needsterminal" anyway */
1112 if(infd == n_CHILD_FD_PASS || outfd == n_CHILD_FD_PASS){
1113 soldint = safe_signal(SIGINT, SIG_IGN);
1114 f = a_INTIGN;
1116 if(n_psonce & n_PSO_INTERACTIVE){
1117 f |= a_TTY;
1118 tcgetattr((n_psonce & n_PSO_TTYIN ? STDIN_FILENO : STDOUT_FILENO),
1119 &a_popen_tios);
1120 n_TERMCAP_SUSPEND(FAL0);
1121 sigfillset(&nset);
1122 sigdelset(&nset, SIGCHLD);
1123 sigdelset(&nset, SIGINT);
1124 /* sigdelset(&nset, SIGPIPE); TODO would need a handler */
1125 sigprocmask(SIG_BLOCK, &nset, &oset);
1126 a_popen_hadsig = 0;
1127 a_popen_jobsigs_up();
1131 if((rv = n_child_start(cmd, mask_or_null, infd, outfd, a0_or_null,
1132 a1_or_null, a2_or_null, env_addon_or_null)) < 0){
1133 e = n_err_no;
1134 rv = -1;
1135 }else{
1136 int ws;
1138 e = 0;
1139 if(n_child_wait(rv, &ws))
1140 rv = 0;
1141 else if(wait_status_or_null == NULL || !WIFEXITED(ws)){
1142 if(ok_blook(bsdcompat) || ok_blook(bsdmsgs))
1143 n_err(_("Fatal error in process\n"));
1144 e = n_ERR_CHILD;
1145 rv = -1;
1147 if(wait_status_or_null != NULL)
1148 *wait_status_or_null = ws;
1151 if(f & a_TTY){
1152 a_popen_jobsigs_down();
1153 n_TERMCAP_RESUME(a_popen_hadsig ? TRU1 : FAL0);
1154 tcsetattr(((n_psonce & n_PSO_TTYIN) ? STDIN_FILENO : STDOUT_FILENO),
1155 ((n_psonce & n_PSO_TTYIN) ? TCSAFLUSH : TCSADRAIN), &a_popen_tios);
1156 sigprocmask(SIG_SETMASK, &oset, NULL);
1158 if(f & a_INTIGN){
1159 if(soldint != SIG_IGN)
1160 safe_signal(SIGINT, soldint);
1163 if(e != 0)
1164 n_err_no = e;
1165 NYD_LEAVE;
1166 return rv;
1169 FL int
1170 n_child_start(char const *cmd, sigset_t *mask_or_null, int infd, int outfd,
1171 char const *a0_or_null, char const *a1_or_null, char const *a2_or_null,
1172 char const **env_addon_or_null)
1174 int rv, e;
1175 NYD_ENTER;
1177 if ((rv = n_child_fork()) == -1) {
1178 e = n_err_no;
1179 n_perr(_("fork"), 0);
1180 n_err_no = e;
1181 rv = -1;
1182 } else if (rv == 0) {
1183 char *argv[128];
1184 int i;
1186 if (env_addon_or_null != NULL) {
1187 extern char **environ;
1188 size_t ei, ei_orig, ai, ai_orig;
1189 char **env;
1191 /* TODO note we don't check the POSIX limit:
1192 * the total space used to store the environment and the arguments to
1193 * the process is limited to {ARG_MAX} bytes */
1194 for (ei = 0; environ[ei] != NULL; ++ei)
1196 ei_orig = ei;
1197 for (ai = 0; env_addon_or_null[ai] != NULL; ++ai)
1199 ai_orig = ai;
1200 env = n_lofi_alloc(sizeof(*env) * (ei + ai +1));
1201 memcpy(env, environ, sizeof(*env) * ei);
1203 /* Replace all those keys that yet exist */
1204 while (ai-- > 0) {
1205 char const *ee, *kvs;
1206 size_t kl;
1208 ee = env_addon_or_null[ai];
1209 kvs = strchr(ee, '=');
1210 assert(kvs != NULL);
1211 kl = PTR2SIZE(kvs - ee);
1212 assert(kl > 0);
1213 for (ei = ei_orig; ei-- > 0;) {
1214 char const *ekvs = strchr(env[ei], '=');
1215 if (ekvs != NULL && kl == PTR2SIZE(ekvs - env[ei]) &&
1216 !memcmp(ee, env[ei], kl)) {
1217 env[ei] = n_UNCONST(ee);
1218 env_addon_or_null[ai] = NULL;
1219 break;
1224 /* And append the rest */
1225 for (ei = ei_orig, ai = ai_orig; ai-- > 0;)
1226 if (env_addon_or_null[ai] != NULL)
1227 env[ei++] = n_UNCONST(env_addon_or_null[ai]);
1229 env[ei] = NULL;
1230 environ = env;
1233 i = (int)getrawlist(TRU1, argv, n_NELEM(argv), cmd, strlen(cmd));
1234 if(i >= 0){
1235 if ((argv[i++] = n_UNCONST(a0_or_null)) != NULL &&
1236 (argv[i++] = n_UNCONST(a1_or_null)) != NULL &&
1237 (argv[i++] = n_UNCONST(a2_or_null)) != NULL)
1238 argv[i] = NULL;
1239 n_child_prepare(mask_or_null, infd, outfd);
1240 execvp(argv[0], argv);
1241 perror(argv[0]);
1243 _exit(n_EXIT_ERR);
1245 NYD_LEAVE;
1246 return rv;
1249 FL int
1250 n_child_fork(void){
1251 /* Strictly speaking we should do so in the waitpid(2) case too, but since
1252 * we explicitly waitpid(2) on the pid if just the structure exists, which
1253 * n_child_wait() does in the parent, all is fine */
1254 #if n_SIGSUSPEND_NOT_WAITPID
1255 sigset_t nset, oset;
1256 #endif
1257 struct child *cp;
1258 int pid;
1259 NYD2_ENTER;
1261 #if n_SIGSUSPEND_NOT_WAITPID
1262 sigfillset(&nset);
1263 sigprocmask(SIG_BLOCK, &nset, &oset);
1264 #endif
1266 cp = a_popen_child_find(0, TRU1);
1268 if((cp->pid = pid = fork()) == -1){
1269 a_popen_child_del(cp);
1270 n_perr(_("fork"), 0);
1273 #if n_SIGSUSPEND_NOT_WAITPID
1274 sigprocmask(SIG_SETMASK, &oset, NULL);
1275 #endif
1276 NYD2_LEAVE;
1277 return pid;
1280 FL void
1281 n_child_prepare(sigset_t *nset_or_null, int infd, int outfd)
1283 int i;
1284 sigset_t fset;
1285 NYD_ENTER;
1287 /* All file descriptors other than 0, 1, and 2 are supposed to be cloexec */
1288 /* TODO WHAT IS WITH STDERR_FILENO DAMN? */
1289 if ((i = (infd == n_CHILD_FD_NULL)))
1290 infd = open(n_path_devnull, O_RDONLY);
1291 if (infd >= 0) {
1292 dup2(infd, STDIN_FILENO);
1293 if (i)
1294 close(infd);
1297 if ((i = (outfd == n_CHILD_FD_NULL)))
1298 outfd = open(n_path_devnull, O_WRONLY);
1299 if (outfd >= 0) {
1300 dup2(outfd, STDOUT_FILENO);
1301 if (i)
1302 close(outfd);
1305 if (nset_or_null != NULL) {
1306 for (i = 1; i < NSIG; ++i)
1307 if (sigismember(nset_or_null, i))
1308 safe_signal(i, SIG_IGN);
1309 if (!sigismember(nset_or_null, SIGINT))
1310 safe_signal(SIGINT, SIG_DFL);
1313 sigemptyset(&fset);
1314 sigprocmask(SIG_SETMASK, &fset, NULL);
1315 NYD_LEAVE;
1318 FL void
1319 n_child_free(int pid){
1320 sigset_t nset, oset;
1321 struct child *cp;
1322 NYD2_ENTER;
1324 sigemptyset(&nset);
1325 sigaddset(&nset, SIGCHLD);
1326 sigprocmask(SIG_BLOCK, &nset, &oset);
1328 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1329 if(cp->done)
1330 a_popen_child_del(cp);
1331 else
1332 cp->free = TRU1;
1335 sigprocmask(SIG_SETMASK, &oset, NULL);
1336 NYD2_LEAVE;
1339 FL bool_t
1340 n_child_wait(int pid, int *wait_status_or_null){
1341 #if !n_SIGSUSPEND_NOT_WAITPID
1342 sigset_t oset;
1343 #endif
1344 sigset_t nset;
1345 struct child *cp;
1346 int ws;
1347 bool_t rv;
1348 NYD_ENTER;
1350 #if !n_SIGSUSPEND_NOT_WAITPID
1351 sigemptyset(&nset);
1352 sigaddset(&nset, SIGCHLD);
1353 sigprocmask(SIG_BLOCK, &nset, &oset);
1354 #endif
1356 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1357 #if n_SIGSUSPEND_NOT_WAITPID
1358 sigfillset(&nset);
1359 sigdelset(&nset, SIGCHLD);
1360 while(!cp->done)
1361 sigsuspend(&nset); /* TODO we should allow more than SIGCHLD!! */
1362 ws = cp->status;
1363 #else
1364 if(!cp->done)
1365 waitpid(pid, &ws, 0);
1366 else
1367 ws = cp->status;
1368 #endif
1369 a_popen_child_del(cp);
1370 }else
1371 ws = 0;
1373 #if !n_SIGSUSPEND_NOT_WAITPID
1374 sigprocmask(SIG_SETMASK, &oset, NULL);
1375 #endif
1377 if(wait_status_or_null != NULL)
1378 *wait_status_or_null = ws;
1379 rv = (WIFEXITED(ws) && WEXITSTATUS(ws) == 0);
1380 NYD_LEAVE;
1381 return rv;
1384 /* s-it-mode */