Add *forward-inject-tail*, *quote-inject-{head,tail}*, `~Q'..
[s-mailx.git] / popen.c
blobb027d2662c359cf8425aceb53ca86e3b63db61cd
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 = n_alloc(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 #ifdef HAVE_MAILDIR
201 if ((fpp->flags & FP_MASK) == FP_MAILDIR) {
202 rv = maildir_append(fpp->realfile, fpp->fp, fpp->offset);
203 goto jleave;
205 #endif
207 outfd = open(fpp->realfile,
208 ((fpp->omode | O_CREAT | (fpp->omode & O_APPEND ? 0 : O_TRUNC) |
209 n_O_NOXY_BITS) & ~O_EXCL), 0666);
210 if (outfd == -1) {
211 outfd = n_err_no;
212 n_err(_("Fatal: cannot create %s: %s\n"),
213 n_shexp_quote_cp(fpp->realfile, FAL0), n_err_to_doc(outfd));
214 goto jleave;
217 cmd[2] = NULL;
218 switch(fpp->flags & FP_MASK){
219 case FP_HOOK:
220 if(n_poption & n_PO_D_V)
221 n_err(_("Using `filetype' handler %s to save %s\n"),
222 n_shexp_quote_cp(fpp->save_cmd, FAL0),
223 n_shexp_quote_cp(fpp->realfile, FAL0));
224 cmd[0] = ok_vlook(SHELL);
225 cmd[1] = "-c";
226 cmd[2] = fpp->save_cmd;
227 break;
228 default:
229 cmd[0] = "cat";
230 cmd[1] = NULL;
231 break;
233 if (n_child_run(cmd[0], 0, fileno(fpp->fp), outfd,
234 cmd[1], cmd[2], NULL, NULL, NULL) >= 0)
235 rv = OKAY;
237 close(outfd);
238 jleave:
239 NYD_LEAVE;
240 return rv;
243 static int
244 a_popen_file_load(int flags, int infd, int outfd, char const *load_cmd){
245 char const *cmd[3];
246 int rv;
247 NYD2_ENTER;
249 cmd[2] = NULL;
250 switch(flags & FP_MASK){
251 case FP_IMAP:
252 case FP_MAILDIR:
253 rv = 0;
254 goto jleave;
255 case FP_HOOK:
256 cmd[0] = ok_vlook(SHELL);
257 cmd[1] = "-c";
258 cmd[2] = load_cmd;
259 break;
260 default:
261 cmd[0] = "cat";
262 cmd[1] = NULL;
263 break;
266 rv = n_child_run(cmd[0], 0, infd, outfd, cmd[1], cmd[2], NULL, NULL, NULL);
267 jleave:
268 NYD2_LEAVE;
269 return rv;
272 static enum okay
273 unregister_file(FILE *fp, struct termios **tiosp, n_sighdl_t *osigint)
275 struct fp **pp, *p;
276 enum okay rv = OKAY;
277 NYD_ENTER;
279 if (tiosp)
280 *tiosp = NULL;
282 for (pp = &fp_head; (p = *pp) != NULL; pp = &p->link)
283 if (p->fp == fp) {
284 switch (p->flags & FP_MASK) {
285 case FP_RAW:
286 case FP_PIPE:
287 break;
288 default:
289 rv = _file_save(p);
290 break;
292 if ((p->flags & FP_UNLINK) && unlink(p->realfile))
293 rv = STOP;
295 *pp = p->link;
296 if (p->save_cmd != NULL)
297 n_free(p->save_cmd);
298 if (p->realfile != NULL)
299 n_free(p->realfile);
300 if (p->flags & FP_TERMIOS) {
301 if (tiosp != NULL) {
302 *tiosp = p->fp_tios;
303 *osigint = p->fp_osigint;
304 } else
305 n_free(p->fp_tios);
307 n_free(p);
308 goto jleave;
310 DBGOR(n_panic, n_alert)(_("Invalid file pointer"));
311 rv = STOP;
312 jleave:
313 NYD_LEAVE;
314 return rv;
317 static int
318 file_pid(FILE *fp)
320 int rv;
321 struct fp *p;
322 NYD2_ENTER;
324 rv = -1;
325 for (p = fp_head; p; p = p->link)
326 if (p->fp == fp) {
327 rv = p->pid;
328 break;
330 NYD2_LEAVE;
331 return rv;
334 static void
335 a_popen_jobsigs_up(void){
336 sigset_t nset, oset;
337 NYD2_ENTER;
339 sigfillset(&nset);
341 sigprocmask(SIG_BLOCK, &nset, &oset);
342 a_popen_otstp = safe_signal(SIGTSTP, &a_popen_jobsig);
343 a_popen_ottin = safe_signal(SIGTTIN, &a_popen_jobsig);
344 a_popen_ottou = safe_signal(SIGTTOU, &a_popen_jobsig);
346 /* This assumes oset contains nothing but SIGCHLD, so to say */
347 sigdelset(&oset, SIGTSTP);
348 sigdelset(&oset, SIGTTIN);
349 sigdelset(&oset, SIGTTOU);
350 sigprocmask(SIG_SETMASK, &oset, NULL);
351 NYD2_LEAVE;
354 static void
355 a_popen_jobsigs_down(void){
356 sigset_t nset, oset;
357 NYD2_ENTER;
359 sigfillset(&nset);
361 sigprocmask(SIG_BLOCK, &nset, &oset);
362 safe_signal(SIGTSTP, a_popen_otstp);
363 safe_signal(SIGTTIN, a_popen_ottin);
364 safe_signal(SIGTTOU, a_popen_ottou);
366 sigaddset(&oset, SIGTSTP);
367 sigaddset(&oset, SIGTTIN);
368 sigaddset(&oset, SIGTTOU);
369 sigprocmask(SIG_SETMASK, &oset, NULL);
370 NYD2_LEAVE;
373 static void
374 a_popen_jobsig(int sig){
375 sighandler_type oldact;
376 sigset_t nset;
377 bool_t hadsig;
378 NYD_X; /* Signal handler */
380 hadsig = (a_popen_hadsig != 0);
381 a_popen_hadsig = 1;
382 if(!hadsig)
383 n_TERMCAP_SUSPEND(TRU1);
385 oldact = safe_signal(sig, SIG_DFL);
387 sigemptyset(&nset);
388 sigaddset(&nset, sig);
389 sigprocmask(SIG_UNBLOCK, &nset, NULL);
390 n_raise(sig);
391 sigprocmask(SIG_BLOCK, &nset, NULL);
393 safe_signal(sig, oldact);
396 static void
397 a_popen_sigchld(int signo){
398 pid_t pid;
399 int status;
400 struct child *cp;
401 NYD_X; /* Signal handler */
402 n_UNUSED(signo);
404 for (;;) {
405 pid = waitpid(-1, &status, WNOHANG);
406 if (pid <= 0) {
407 if (pid == -1 && n_err_no == n_ERR_INTR)
408 continue;
409 break;
412 if ((cp = a_popen_child_find(pid, FAL0)) != NULL) {
413 cp->done = 1;
414 if (cp->free)
415 cp->pid = -1; /* XXX Was _delchild(cp);# */
416 else {
417 cp->status = status;
423 static struct child *
424 a_popen_child_find(int pid, bool_t create){
425 struct child **cpp, *cp;
426 NYD2_ENTER;
428 for(cpp = &_popen_child; (cp = *cpp) != NULL && cp->pid != pid;
429 cpp = &(*cpp)->link)
432 if(cp == NULL && create)
433 (*cpp = cp = n_calloc(1, sizeof *cp))->pid = pid;
434 NYD2_LEAVE;
435 return cp;
438 static void
439 a_popen_child_del(struct child *cp){
440 struct child **cpp;
441 NYD2_ENTER;
443 cpp = &_popen_child;
445 for(;;){
446 if(*cpp == cp){
447 *cpp = cp->link;
448 n_free(cp);
449 break;
451 if(*(cpp = &(*cpp)->link) == NULL){
452 DBG( n_err("! a_popen_child_del(): implementation error\n"); )
453 break;
456 NYD2_LEAVE;
459 FL void
460 n_child_manager_start(void)
462 struct sigaction nact, oact;
463 NYD_ENTER;
465 nact.sa_handler = &a_popen_sigchld;
466 sigemptyset(&nact.sa_mask);
467 nact.sa_flags = SA_RESTART
468 #ifdef SA_NOCLDSTOP
469 | SA_NOCLDSTOP
470 #endif
472 if (sigaction(SIGCHLD, &nact, &oact) != 0)
473 n_panic(_("Cannot install signal handler for child process management"));
474 NYD_LEAVE;
477 FL FILE *
478 safe_fopen(char const *file, char const *oflags, int *xflags)
480 int osflags, fd;
481 FILE *fp = NULL;
482 NYD2_ENTER; /* (only for Fopen() and once in go.c) */
484 if (a_popen_scan_mode(oflags, &osflags) < 0)
485 goto jleave;
486 osflags |= _O_CLOEXEC;
487 if (xflags != NULL)
488 *xflags = osflags;
490 if ((fd = open(file, osflags, 0666)) == -1)
491 goto jleave;
492 _CLOEXEC_SET(fd);
494 fp = fdopen(fd, oflags);
495 jleave:
496 NYD2_LEAVE;
497 return fp;
500 FL FILE *
501 Fopen(char const *file, char const *oflags)
503 FILE *fp;
504 int osflags;
505 NYD_ENTER;
507 if ((fp = safe_fopen(file, oflags, &osflags)) != NULL)
508 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
509 NYD_LEAVE;
510 return fp;
513 FL FILE *
514 Fdopen(int fd, char const *oflags, bool_t nocloexec)
516 FILE *fp;
517 int osflags;
518 NYD_ENTER;
520 a_popen_scan_mode(oflags, &osflags);
521 if (!nocloexec)
522 osflags |= _O_CLOEXEC; /* Ensured to be set by caller as documented! */
524 if ((fp = fdopen(fd, oflags)) != NULL)
525 register_file(fp, osflags, 0, FP_RAW, NULL, 0L, NULL, NULL,NULL);
526 NYD_LEAVE;
527 return fp;
530 FL int
531 Fclose(FILE *fp)
533 int i = 0;
534 NYD_ENTER;
536 if (unregister_file(fp, NULL, NULL) == OKAY)
537 i |= 1;
538 if (fclose(fp) == 0)
539 i |= 2;
540 NYD_LEAVE;
541 return (i == 3 ? 0 : EOF);
544 FL FILE *
545 n_fopen_any(char const *file, char const *oflags, /* TODO should take flags */
546 enum n_fopen_state *fs_or_null){ /* TODO as bits, return state */
547 /* TODO Support file locking upon open time */
548 long offset;
549 enum protocol p;
550 enum oflags rof;
551 int osflags, flags, omode, infd;
552 char const *cload, *csave;
553 enum n_fopen_state fs;
554 FILE *rv;
555 NYD_ENTER;
557 rv = NULL;
558 fs = n_FOPEN_STATE_NONE;
559 cload = csave = NULL;
561 if(a_popen_scan_mode(oflags, &osflags) < 0)
562 goto jleave;
564 flags = 0;
565 rof = OF_RDWR | OF_UNLINK;
566 if(osflags & O_APPEND)
567 rof |= OF_APPEND;
568 omode = (osflags == O_RDONLY) ? R_OK : R_OK | W_OK;
570 /* We don't want to find mbox.bz2 when doing "copy * mbox", but only for
571 * "file mbox", so don't try hooks when writing */
572 p = which_protocol(csave = file, TRU1, ((omode & W_OK) == 0), &file);
573 fs = (enum n_fopen_state)p;
574 switch(p){
575 default:
576 goto jleave;
577 case n_PROTO_IMAP:
578 #ifdef HAVE_IMAP
579 file = csave;
580 flags |= FP_IMAP;
581 osflags = O_RDWR | O_APPEND | O_CREAT | n_O_NOXY_BITS;
582 infd = -1;
583 break;
584 #else
585 n_err_no = n_ERR_OPNOTSUPP;
586 goto jleave;
587 #endif
588 case n_PROTO_MAILDIR:
589 #ifdef HAVE_MAILDIR
590 if(fs_or_null != NULL && !access(file, F_OK))
591 fs |= n_FOPEN_STATE_EXISTS;
592 flags |= FP_MAILDIR;
593 osflags = O_RDWR | O_APPEND | O_CREAT | n_O_NOXY_BITS;
594 infd = -1;
595 break;
596 #else
597 n_err_no = n_ERR_OPNOTSUPP;
598 goto jleave;
599 #endif
600 case n_PROTO_FILE:{
601 struct n_file_type ft;
603 if(!(osflags & O_EXCL) && fs_or_null != NULL && !access(file, F_OK))
604 fs |= n_FOPEN_STATE_EXISTS;
606 if(n_filetype_exists(&ft, file)){
607 flags |= FP_HOOK;
608 cload = ft.ft_load_dat;
609 csave = ft.ft_save_dat;
610 /* Cause truncation for compressor/hook output files */
611 osflags &= ~O_APPEND;
612 rof &= ~OF_APPEND;
613 if((infd = open(file, (omode & W_OK ? O_RDWR : O_RDONLY))) != -1){
614 fs |= n_FOPEN_STATE_EXISTS;
615 if(n_poption & n_PO_D_V)
616 n_err(_("Using `filetype' handler %s to load %s\n"),
617 n_shexp_quote_cp(cload, FAL0), n_shexp_quote_cp(file, FAL0));
618 }else if(!(osflags & O_CREAT) || n_err_no != n_ERR_NOENT)
619 goto jleave;
620 }else{
621 /*flags |= FP_RAW;*/
622 rv = Fopen(file, oflags);
623 if((osflags & O_EXCL) && rv == NULL)
624 fs |= n_FOPEN_STATE_EXISTS;
625 goto jleave;
627 }break;
630 /* Note rv is not yet register_file()d, fclose() it in error path! */
631 if((rv = Ftmp(NULL, "fopenany", rof)) == NULL){
632 n_perr(_("tmpfile"), 0);
633 goto jerr;
636 if(flags & (FP_IMAP | FP_MAILDIR))
638 else if(infd >= 0){
639 if(a_popen_file_load(flags, infd, fileno(rv), cload) < 0){
640 jerr:
641 if(rv != NULL)
642 fclose(rv);
643 rv = NULL;
644 if(infd >= 0)
645 close(infd);
646 goto jleave;
648 }else{
649 if((infd = creat(file, 0666)) == -1){
650 fclose(rv);
651 rv = NULL;
652 goto jleave;
656 if(infd >= 0)
657 close(infd);
658 fflush(rv);
660 if(!(osflags & O_APPEND))
661 rewind(rv);
662 if((offset = ftell(rv)) == -1){
663 Fclose(rv);
664 rv = NULL;
665 goto jleave;
668 register_file(rv, osflags, 0, flags, file, offset, csave, NULL,NULL);
669 jleave:
670 if(fs_or_null != NULL)
671 *fs_or_null = fs;
672 NYD_LEAVE;
673 return rv;
676 FL FILE *
677 Ftmp(char **fn, char const *namehint, enum oflags oflags)
679 /* The 8 is arbitrary but leaves room for a six character suffix (the
680 * POSIX minimum path length is 14, though we don't check that XXX).
681 * 8 should be more than sufficient given that we use base64url encoding
682 * for our random string */
683 enum {_RANDCHARS = 8u};
685 char *cp_base, *cp;
686 size_t maxname, xlen, i;
687 char const *tmpdir;
688 int osoflags, fd, e;
689 bool_t relesigs;
690 FILE *fp;
691 NYD_ENTER;
693 assert(namehint != NULL);
694 assert((oflags & OF_WRONLY) || (oflags & OF_RDWR));
695 assert(!(oflags & OF_RDONLY));
696 assert(!(oflags & OF_REGISTER_UNLINK) || (oflags & OF_REGISTER));
698 fp = NULL;
699 relesigs = FAL0;
700 e = 0;
701 tmpdir = ok_vlook(TMPDIR);
702 maxname = NAME_MAX;
703 #ifdef HAVE_PATHCONF
704 { long pc;
706 if ((pc = pathconf(tmpdir, _PC_NAME_MAX)) != -1)
707 maxname = (size_t)pc;
709 #endif
711 if ((oflags & OF_SUFFIX) && *namehint != '\0') {
712 if ((xlen = strlen(namehint)) > maxname - _RANDCHARS) {
713 n_err_no = n_ERR_NAMETOOLONG;
714 goto jleave;
716 } else
717 xlen = 0;
719 /* Prepare the template string once, then iterate over the random range */
720 cp_base =
721 cp = n_alloc(strlen(tmpdir) + 1 + maxname +1);
722 cp = sstpcpy(cp, tmpdir);
723 *cp++ = '/';
725 char *x = sstpcpy(cp, VAL_UAGENT);
726 *x++ = '-';
727 if (!(oflags & OF_SUFFIX))
728 x = sstpcpy(x, namehint);
730 i = PTR2SIZE(x - cp);
731 if (i > maxname - xlen - _RANDCHARS) {
732 size_t j = maxname - xlen - _RANDCHARS;
733 x -= i - j;
736 if ((oflags & OF_SUFFIX) && xlen > 0)
737 memcpy(x + _RANDCHARS, namehint, xlen);
739 x[xlen + _RANDCHARS] = '\0';
740 cp = x;
743 osoflags = O_CREAT | O_EXCL | _O_CLOEXEC;
744 osoflags |= (oflags & OF_WRONLY) ? O_WRONLY : O_RDWR;
745 if (oflags & OF_APPEND)
746 osoflags |= O_APPEND;
748 for(relesigs = TRU1, i = 0;; ++i){
749 memcpy(cp, n_random_create_cp(_RANDCHARS, NULL), _RANDCHARS);
751 hold_all_sigs();
753 if((fd = open(cp_base, osoflags, 0600)) != -1){
754 _CLOEXEC_SET(fd);
755 break;
757 if(i >= FTMP_OPEN_TRIES){
758 e = n_err_no;
759 goto jfree;
761 rele_all_sigs();
764 if (oflags & OF_REGISTER) {
765 char const *osflags = (oflags & OF_RDWR ? "w+" : "w");
766 int osflagbits;
768 a_popen_scan_mode(osflags, &osflagbits); /* TODO osoflags&xy ?!!? */
769 if ((fp = fdopen(fd, osflags)) != NULL)
770 register_file(fp, osflagbits | _O_CLOEXEC, 0,
771 (FP_RAW | (oflags & OF_REGISTER_UNLINK ? FP_UNLINK : 0)),
772 cp_base, 0L, NULL, NULL,NULL);
773 } else
774 fp = fdopen(fd, (oflags & OF_RDWR ? "w+" : "w"));
776 if (fp == NULL || (oflags & OF_UNLINK)) {
777 e = n_err_no;
778 unlink(cp_base);
779 goto jfree;
780 }else if(fp != NULL){
781 /* We will succeed and keep the file around for further usage, likely
782 * another stream will be opened for pure reading purposes (this is true
783 * at the time of this writing. A restrictive umask(2) settings may have
784 * turned the path inaccessible, so ensure it may be read at least!
785 * TODO once ok_vlook() can return an integer, look up *umask* first! */
786 (void)fchmod(fd, S_IWUSR | S_IRUSR);
789 if (fn != NULL)
790 *fn = cp_base;
791 else
792 n_free(cp_base);
793 jleave:
794 if (relesigs && (fp == NULL || !(oflags & OF_HOLDSIGS)))
795 rele_all_sigs();
796 if (fp == NULL)
797 n_err_no = e;
798 NYD_LEAVE;
799 return fp;
800 jfree:
801 if ((cp = cp_base) != NULL)
802 n_free(cp);
803 goto jleave;
806 FL void
807 Ftmp_release(char **fn)
809 char *cp;
810 NYD_ENTER;
812 cp = *fn;
813 *fn = NULL;
814 if (cp != NULL) {
815 unlink(cp);
816 rele_all_sigs();
817 n_free(cp);
819 NYD_LEAVE;
822 FL void
823 Ftmp_free(char **fn) /* TODO DROP: OF_REGISTER_FREEPATH! */
825 char *cp;
826 NYD_ENTER;
828 cp = *fn;
829 *fn = NULL;
830 if (cp != NULL)
831 n_free(cp);
832 NYD_LEAVE;
835 FL bool_t
836 pipe_cloexec(int fd[2]){
837 bool_t rv;
838 NYD_ENTER;
840 rv = FAL0;
842 #ifdef HAVE_PIPE2
843 if(pipe2(fd, O_CLOEXEC) != -1)
844 rv = TRU1;
845 #else
846 if(pipe(fd) != -1){
847 n_fd_cloexec_set(fd[0]);
848 n_fd_cloexec_set(fd[1]);
849 rv = TRU1;
851 #endif
852 NYD_LEAVE;
853 return rv;
856 FL FILE *
857 Popen(char const *cmd, char const *mode, char const *sh,
858 char const **env_addon, int newfd1)
860 int p[2], myside, hisside, fd0, fd1, pid;
861 sigset_t nset;
862 char mod[2];
863 n_sighdl_t osigint;
864 struct termios *tiosp;
865 FILE *rv;
866 NYD_ENTER;
868 /* First clean up child structures */
869 /* C99 */{
870 struct child **cpp, *cp;
872 hold_all_sigs();
873 for (cpp = &_popen_child; *cpp != NULL;) {
874 if ((*cpp)->pid == -1) {
875 cp = *cpp;
876 *cpp = cp->link;
877 n_free(cp);
878 } else
879 cpp = &(*cpp)->link;
881 rele_all_sigs();
884 rv = NULL;
885 tiosp = NULL;
886 n_UNINIT(osigint, SIG_ERR);
887 mod[0] = '0', mod[1] = '\0';
889 if (!pipe_cloexec(p))
890 goto jleave;
892 if (*mode == 'r') {
893 myside = p[READ];
894 fd0 = n_CHILD_FD_PASS;
895 hisside = fd1 = p[WRITE];
896 mod[0] = *mode;
897 } else if (*mode == 'W') {
898 myside = p[WRITE];
899 hisside = fd0 = p[READ];
900 fd1 = newfd1;
901 mod[0] = 'w';
902 } else {
903 myside = p[WRITE];
904 hisside = fd0 = p[READ];
905 fd1 = n_CHILD_FD_PASS;
906 mod[0] = 'w';
909 /* In interactive mode both STDIN and STDOUT point to the terminal. If we
910 * pass through the TTY restore terminal attributes after pipe returns.
911 * XXX It shouldn't matter which FD we actually use in this case */
912 if ((n_psonce & n_PSO_INTERACTIVE) && (fd0 == n_CHILD_FD_PASS ||
913 fd1 == n_CHILD_FD_PASS)) {
914 osigint = n_signal(SIGINT, SIG_IGN);
915 tiosp = n_alloc(sizeof *tiosp);
916 tcgetattr(STDIN_FILENO, tiosp);
917 n_TERMCAP_SUSPEND(TRU1);
920 sigemptyset(&nset);
922 if (cmd == (char*)-1) {
923 if ((pid = n_child_fork()) == -1)
924 n_perr(_("fork"), 0);
925 else if (pid == 0) {
926 union {char const *ccp; int (*ptf)(void); int es;} u;
927 n_child_prepare(&nset, fd0, fd1);
928 close(p[READ]);
929 close(p[WRITE]);
930 /* TODO should close all other open FDs except stds and reset memory */
931 /* Standard I/O drives me insane! All we need is a sync operation
932 * that causes n_stdin to forget about any read buffer it may have.
933 * We cannot use fflush(3), this works with Musl and Solaris, but not
934 * with GlibC. (For at least pipes.) We cannot use fdreopen(),
935 * because this function does not exist! Luckily (!!!) we only use
936 * n_stdin not stdin in our child, otherwise all bets were off!
937 * TODO (Unless we would fiddle around with FILE* directly:
938 * TODO #ifdef __GLIBC__
939 * TODO n_stdin->_IO_read_ptr = n_stdin->_IO_read_end;
940 * TODO #elif *BSD*
941 * TODO n_stdin->_r = 0;
942 * TODO #elif n_OS_SOLARIS || n_OS_SUNOS
943 * TODO n_stdin->_cnt = 0;
944 * TODO #endif
945 * TODO ) which should have additional config test for sure! */
946 n_stdin = fdopen(STDIN_FILENO, "r");
947 /*n_stdout = fdopen(STDOUT_FILENO, "w");*/
948 /*n_stderr = fdopen(STDERR_FILENO, "w");*/
949 u.ccp = sh;
950 u.es = (*u.ptf)();
951 /*fflush(NULL);*/
952 _exit(u.es);
954 } else if (sh == NULL) {
955 pid = n_child_start(cmd, &nset, fd0, fd1, NULL, NULL, NULL, env_addon);
956 } else {
957 pid = n_child_start(sh, &nset, fd0, fd1, "-c", cmd, NULL, env_addon);
959 if (pid < 0) {
960 close(p[READ]);
961 close(p[WRITE]);
962 goto jleave;
964 close(hisside);
965 if ((rv = fdopen(myside, mod)) != NULL)
966 register_file(rv, 0, pid,
967 (tiosp == NULL ? FP_PIPE : FP_PIPE | FP_TERMIOS),
968 NULL, 0L, NULL, tiosp, osigint);
969 else
970 close(myside);
971 jleave:
972 if(rv == NULL && tiosp != NULL){
973 n_TERMCAP_RESUME(TRU1);
974 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
975 n_free(tiosp);
976 n_signal(SIGINT, osigint);
978 NYD_LEAVE;
979 return rv;
982 FL bool_t
983 Pclose(FILE *ptr, bool_t dowait)
985 n_sighdl_t osigint;
986 struct termios *tiosp;
987 int pid;
988 bool_t rv = FAL0;
989 NYD_ENTER;
991 pid = file_pid(ptr);
992 if(pid < 0)
993 goto jleave;
995 unregister_file(ptr, &tiosp, &osigint);
996 fclose(ptr);
998 if(dowait){
999 hold_all_sigs();
1000 rv = n_child_wait(pid, NULL);
1001 if(tiosp != NULL){
1002 n_TERMCAP_RESUME(TRU1);
1003 tcsetattr(STDIN_FILENO, TCSAFLUSH, tiosp);
1004 n_signal(SIGINT, osigint);
1006 rele_all_sigs();
1007 }else{
1008 n_child_free(pid);
1009 rv = TRU1;
1012 if(tiosp != NULL)
1013 n_free(tiosp);
1014 jleave:
1015 NYD_LEAVE;
1016 return rv;
1019 VL int
1020 n_psignal(FILE *fp, int sig){
1021 int rv;
1022 NYD2_ENTER;
1024 if((rv = file_pid(fp)) >= 0){
1025 struct child *cp;
1027 if((cp = a_popen_child_find(rv, FAL0)) != NULL){
1028 if((rv = kill(rv, sig)) != 0)
1029 rv = n_err_no;
1030 }else
1031 rv = -1;
1033 NYD2_LEAVE;
1034 return rv;
1037 FL FILE *
1038 n_pager_open(void)
1040 char const *env_add[2], *pager;
1041 FILE *rv;
1042 NYD_ENTER;
1044 assert(n_psonce & n_PSO_INTERACTIVE);
1046 pager = n_pager_get(env_add + 0);
1047 env_add[1] = NULL;
1049 if ((rv = Popen(pager, "w", NULL, env_add, n_CHILD_FD_PASS)) == NULL)
1050 n_perr(pager, 0);
1051 NYD_LEAVE;
1052 return rv;
1055 FL bool_t
1056 n_pager_close(FILE *fp)
1058 sighandler_type sh;
1059 bool_t rv;
1060 NYD_ENTER;
1062 sh = safe_signal(SIGPIPE, SIG_IGN);
1063 rv = Pclose(fp, TRU1);
1064 safe_signal(SIGPIPE, sh);
1065 NYD_LEAVE;
1066 return rv;
1069 FL void
1070 close_all_files(void)
1072 NYD_ENTER;
1073 while (fp_head != NULL)
1074 if ((fp_head->flags & FP_MASK) == FP_PIPE)
1075 Pclose(fp_head->fp, TRU1);
1076 else
1077 Fclose(fp_head->fp);
1078 NYD_LEAVE;
1081 /* TODO The entire n_child_ series should be replaced with an object, but
1082 * TODO at least have carrier arguments. We anyway need a command manager
1083 * TODO that keeps track and knows how to handle job control ++++! */
1085 FL int
1086 n_child_run(char const *cmd, sigset_t *mask_or_null, int infd, int outfd,
1087 char const *a0_or_null, char const *a1_or_null, char const *a2_or_null,
1088 char const **env_addon_or_null, int *wait_status_or_null)
1090 sigset_t nset, oset;
1091 sighandler_type soldint;
1092 int rv, e;
1093 enum {a_NONE = 0, a_INTIGN = 1<<0, a_TTY = 1<<1} f;
1094 NYD_ENTER;
1096 f = a_NONE;
1097 n_UNINIT(soldint, SIG_ERR);
1099 /* TODO Of course this is a joke given that during a "p*" the PAGER may
1100 * TODO be up and running while we play around like this... but i guess
1101 * TODO this can't be helped at all unless we perform complete and true
1102 * TODO process group separation and ensure we don't deadlock us out
1103 * TODO via TTY jobcontrol signal storms (could this really happen?).
1104 * TODO Or have a built-in pager. Or query any necessity BEFORE we start
1105 * TODO any action, and shall we find we need to run programs dump it
1106 * TODO all into a temporary file which is then passed through to the
1107 * TODO PAGER. Ugh. That still won't help for "needsterminal" anyway */
1108 if(infd == n_CHILD_FD_PASS || outfd == n_CHILD_FD_PASS){
1109 soldint = safe_signal(SIGINT, SIG_IGN);
1110 f = a_INTIGN;
1112 if(n_psonce & n_PSO_INTERACTIVE){
1113 f |= a_TTY;
1114 tcgetattr((n_psonce & n_PSO_TTYIN ? STDIN_FILENO : STDOUT_FILENO),
1115 &a_popen_tios);
1116 n_TERMCAP_SUSPEND(FAL0);
1117 sigfillset(&nset);
1118 sigdelset(&nset, SIGCHLD);
1119 sigdelset(&nset, SIGINT);
1120 /* sigdelset(&nset, SIGPIPE); TODO would need a handler */
1121 sigprocmask(SIG_BLOCK, &nset, &oset);
1122 a_popen_hadsig = 0;
1123 a_popen_jobsigs_up();
1127 if((rv = n_child_start(cmd, mask_or_null, infd, outfd, a0_or_null,
1128 a1_or_null, a2_or_null, env_addon_or_null)) < 0){
1129 e = n_err_no;
1130 rv = -1;
1131 }else{
1132 int ws;
1134 e = 0;
1135 if(n_child_wait(rv, &ws))
1136 rv = 0;
1137 else if(wait_status_or_null == NULL || !WIFEXITED(ws)){
1138 if(ok_blook(bsdcompat) || ok_blook(bsdmsgs))
1139 n_err(_("Fatal error in process\n"));
1140 e = n_ERR_CHILD;
1141 rv = -1;
1143 if(wait_status_or_null != NULL)
1144 *wait_status_or_null = ws;
1147 if(f & a_TTY){
1148 a_popen_jobsigs_down();
1149 n_TERMCAP_RESUME(a_popen_hadsig ? TRU1 : FAL0);
1150 tcsetattr(((n_psonce & n_PSO_TTYIN) ? STDIN_FILENO : STDOUT_FILENO),
1151 ((n_psonce & n_PSO_TTYIN) ? TCSAFLUSH : TCSADRAIN), &a_popen_tios);
1152 sigprocmask(SIG_SETMASK, &oset, NULL);
1154 if(f & a_INTIGN){
1155 if(soldint != SIG_IGN)
1156 safe_signal(SIGINT, soldint);
1159 if(e != 0)
1160 n_err_no = e;
1161 NYD_LEAVE;
1162 return rv;
1165 FL int
1166 n_child_start(char const *cmd, sigset_t *mask_or_null, int infd, int outfd,
1167 char const *a0_or_null, char const *a1_or_null, char const *a2_or_null,
1168 char const **env_addon_or_null)
1170 int rv, e;
1171 NYD_ENTER;
1173 if ((rv = n_child_fork()) == -1) {
1174 e = n_err_no;
1175 n_perr(_("fork"), 0);
1176 n_err_no = e;
1177 rv = -1;
1178 } else if (rv == 0) {
1179 char *argv[128];
1180 int i;
1182 if (env_addon_or_null != NULL) {
1183 extern char **environ;
1184 size_t ei, ei_orig, ai, ai_orig;
1185 char **env;
1187 /* TODO note we don't check the POSIX limit:
1188 * the total space used to store the environment and the arguments to
1189 * the process is limited to {ARG_MAX} bytes */
1190 for (ei = 0; environ[ei] != NULL; ++ei)
1192 ei_orig = ei;
1193 for (ai = 0; env_addon_or_null[ai] != NULL; ++ai)
1195 ai_orig = ai;
1196 env = n_lofi_alloc(sizeof(*env) * (ei + ai +1));
1197 memcpy(env, environ, sizeof(*env) * ei);
1199 /* Replace all those keys that yet exist */
1200 while (ai-- > 0) {
1201 char const *ee, *kvs;
1202 size_t kl;
1204 ee = env_addon_or_null[ai];
1205 kvs = strchr(ee, '=');
1206 assert(kvs != NULL);
1207 kl = PTR2SIZE(kvs - ee);
1208 assert(kl > 0);
1209 for (ei = ei_orig; ei-- > 0;) {
1210 char const *ekvs = strchr(env[ei], '=');
1211 if (ekvs != NULL && kl == PTR2SIZE(ekvs - env[ei]) &&
1212 !memcmp(ee, env[ei], kl)) {
1213 env[ei] = n_UNCONST(ee);
1214 env_addon_or_null[ai] = NULL;
1215 break;
1220 /* And append the rest */
1221 for (ei = ei_orig, ai = ai_orig; ai-- > 0;)
1222 if (env_addon_or_null[ai] != NULL)
1223 env[ei++] = n_UNCONST(env_addon_or_null[ai]);
1225 env[ei] = NULL;
1226 environ = env;
1229 i = (int)getrawlist(TRU1, argv, n_NELEM(argv), cmd, strlen(cmd));
1230 if(i >= 0){
1231 if ((argv[i++] = n_UNCONST(a0_or_null)) != NULL &&
1232 (argv[i++] = n_UNCONST(a1_or_null)) != NULL &&
1233 (argv[i++] = n_UNCONST(a2_or_null)) != NULL)
1234 argv[i] = NULL;
1235 n_child_prepare(mask_or_null, infd, outfd);
1236 execvp(argv[0], argv);
1237 perror(argv[0]);
1239 _exit(n_EXIT_ERR);
1241 NYD_LEAVE;
1242 return rv;
1245 FL int
1246 n_child_fork(void){
1247 /* Strictly speaking we should do so in the waitpid(2) case too, but since
1248 * we explicitly waitpid(2) on the pid if just the structure exists, which
1249 * n_child_wait() does in the parent, all is fine */
1250 #if n_SIGSUSPEND_NOT_WAITPID
1251 sigset_t nset, oset;
1252 #endif
1253 struct child *cp;
1254 int pid;
1255 NYD2_ENTER;
1257 #if n_SIGSUSPEND_NOT_WAITPID
1258 sigfillset(&nset);
1259 sigprocmask(SIG_BLOCK, &nset, &oset);
1260 #endif
1262 cp = a_popen_child_find(0, TRU1);
1264 if((cp->pid = pid = fork()) == -1){
1265 a_popen_child_del(cp);
1266 n_perr(_("fork"), 0);
1269 #if n_SIGSUSPEND_NOT_WAITPID
1270 sigprocmask(SIG_SETMASK, &oset, NULL);
1271 #endif
1272 NYD2_LEAVE;
1273 return pid;
1276 FL void
1277 n_child_prepare(sigset_t *nset_or_null, int infd, int outfd)
1279 int i;
1280 sigset_t fset;
1281 NYD_ENTER;
1283 /* All file descriptors other than 0, 1, and 2 are supposed to be cloexec */
1284 /* TODO WHAT IS WITH STDERR_FILENO DAMN? */
1285 if ((i = (infd == n_CHILD_FD_NULL)))
1286 infd = open(n_path_devnull, O_RDONLY);
1287 if (infd >= 0) {
1288 dup2(infd, STDIN_FILENO);
1289 if (i)
1290 close(infd);
1293 if ((i = (outfd == n_CHILD_FD_NULL)))
1294 outfd = open(n_path_devnull, O_WRONLY);
1295 if (outfd >= 0) {
1296 dup2(outfd, STDOUT_FILENO);
1297 if (i)
1298 close(outfd);
1301 if (nset_or_null != NULL) {
1302 for (i = 1; i < NSIG; ++i)
1303 if (sigismember(nset_or_null, i))
1304 safe_signal(i, SIG_IGN);
1305 if (!sigismember(nset_or_null, SIGINT))
1306 safe_signal(SIGINT, SIG_DFL);
1309 sigemptyset(&fset);
1310 sigprocmask(SIG_SETMASK, &fset, NULL);
1311 NYD_LEAVE;
1314 FL void
1315 n_child_free(int pid){
1316 sigset_t nset, oset;
1317 struct child *cp;
1318 NYD2_ENTER;
1320 sigemptyset(&nset);
1321 sigaddset(&nset, SIGCHLD);
1322 sigprocmask(SIG_BLOCK, &nset, &oset);
1324 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1325 if(cp->done)
1326 a_popen_child_del(cp);
1327 else
1328 cp->free = TRU1;
1331 sigprocmask(SIG_SETMASK, &oset, NULL);
1332 NYD2_LEAVE;
1335 FL bool_t
1336 n_child_wait(int pid, int *wait_status_or_null){
1337 #if !n_SIGSUSPEND_NOT_WAITPID
1338 sigset_t oset;
1339 #endif
1340 sigset_t nset;
1341 struct child *cp;
1342 int ws;
1343 bool_t rv;
1344 NYD_ENTER;
1346 #if !n_SIGSUSPEND_NOT_WAITPID
1347 sigemptyset(&nset);
1348 sigaddset(&nset, SIGCHLD);
1349 sigprocmask(SIG_BLOCK, &nset, &oset);
1350 #endif
1352 if((cp = a_popen_child_find(pid, FAL0)) != NULL){
1353 #if n_SIGSUSPEND_NOT_WAITPID
1354 sigfillset(&nset);
1355 sigdelset(&nset, SIGCHLD);
1356 while(!cp->done)
1357 sigsuspend(&nset); /* TODO we should allow more than SIGCHLD!! */
1358 ws = cp->status;
1359 #else
1360 if(!cp->done)
1361 waitpid(pid, &ws, 0);
1362 else
1363 ws = cp->status;
1364 #endif
1365 a_popen_child_del(cp);
1366 }else
1367 ws = 0;
1369 #if !n_SIGSUSPEND_NOT_WAITPID
1370 sigprocmask(SIG_SETMASK, &oset, NULL);
1371 #endif
1373 if(wait_status_or_null != NULL)
1374 *wait_status_or_null = ws;
1375 rv = (WIFEXITED(ws) && WEXITSTATUS(ws) == 0);
1376 NYD_LEAVE;
1377 return rv;
1380 /* s-it-mode */