popen.c: some cleanup, use other wait_child() from now on
[s-mailx.git] / popen.c
blobb8584fdc2f8d17a0cc9d00eb4378acaff03cd9fd
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 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
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. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 #include <sys/wait.h>
46 #include <fcntl.h>
48 #define READ 0
49 #define WRITE 1
51 struct fp {
52 FILE *fp;
53 struct fp *link;
54 char *realfile;
55 long offset;
56 int omode;
57 int pipe;
58 int pid;
59 enum {
60 FP_UNCOMPRESSED = 00,
61 FP_GZIPPED = 01,
62 FP_BZIP2ED = 02,
63 FP_IMAP = 03,
64 FP_MAILDIR = 04,
65 FP_MASK = 0177,
66 FP_READONLY = 0200
67 } compressed;
69 static struct fp *fp_head;
71 struct child {
72 int pid;
73 char done;
74 char free;
75 int status;
76 struct child *link;
78 static struct child *_popen_child;
80 static int scan_mode(const char *mode, int *omode);
81 static void register_file(FILE *fp, int omode, int ispipe, int pid,
82 int compressed, const char *realfile, long offset);
83 static enum okay compress(struct fp *fpp);
84 static int decompress(int compression, int infd, int outfd);
85 static enum okay unregister_file(FILE *fp);
86 static int file_pid(FILE *fp);
87 static int wait_command(int pid);
88 static struct child *findchild(int pid);
89 static void delchild(struct child *cp);
91 static int
92 scan_mode(const char *mode, int *omode)
95 if (!strcmp(mode, "r")) {
96 *omode = O_RDONLY;
97 } else if (!strcmp(mode, "w")) {
98 *omode = O_WRONLY | O_CREAT | O_TRUNC;
99 } else if (!strcmp(mode, "wx")) {
100 *omode = O_WRONLY | O_CREAT | O_EXCL;
101 } else if (!strcmp(mode, "a")) {
102 *omode = O_WRONLY | O_APPEND | O_CREAT;
103 } else if (!strcmp(mode, "a+")) {
104 *omode = O_RDWR | O_APPEND;
105 } else if (!strcmp(mode, "r+")) {
106 *omode = O_RDWR;
107 } else if (!strcmp(mode, "w+")) {
108 *omode = O_RDWR | O_CREAT | O_EXCL;
109 } else {
110 fprintf(stderr, tr(152,
111 "Internal error: bad stdio open mode %s\n"), mode);
112 errno = EINVAL;
113 *omode = 0; /* (silence CC) */
114 return -1;
116 return 0;
119 FL FILE *
120 safe_fopen(const char *file, const char *mode, int *omode)
122 int fd;
124 if (scan_mode(mode, omode) < 0)
125 return NULL;
126 if ((fd = open(file, *omode, 0666)) < 0)
127 return NULL;
128 return fdopen(fd, mode);
131 FL FILE *
132 Fopen(const char *file, const char *mode)
134 FILE *fp;
135 int omode;
137 if ((fp = safe_fopen(file, mode, &omode)) != NULL) {
138 (void)fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
139 register_file(fp, omode, 0, 0, FP_UNCOMPRESSED, NULL, 0L);
141 return fp;
144 FL FILE *
145 Fdopen(int fd, const char *mode)
147 FILE *fp;
148 int omode;
150 scan_mode(mode, &omode);
151 if ((fp = fdopen(fd, mode)) != NULL) {
152 (void)fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
153 register_file(fp, omode, 0, 0, FP_UNCOMPRESSED, NULL, 0L);
155 return fp;
158 FL int
159 Fclose(FILE *fp)
161 int i = 0;
162 if (unregister_file(fp) == OKAY)
163 i |= 1;
164 if (fclose(fp) == 0)
165 i |= 2;
166 return i == 3 ? 0 : EOF;
169 FL FILE *
170 Zopen(const char *file, const char *mode, int *compression) /* TODO MESS!
171 * TODO maybe we shouldn't be simple and run commands but instead
172 * TODO links against any of available zlib.h, bzlib.h, lzma.h!
173 * TODO even libzip? Much faster, not that much more work, and we're
174 * TODO *so* fixed anyway!!
175 * TODO *or*: make it all hookable via BOXEXTENSION/DECOMPRESS etc.
176 * TODO by the user; but not like this, Coverity went grazy about it */
178 int infd;
179 FILE *outf;
180 const char *rp;
181 int omode;
182 char *tempfn;
183 int bits;
184 int _compression;
185 long offset;
186 char *extension;
187 enum protocol p;
189 if (scan_mode(mode, &omode) < 0)
190 return NULL;
191 if (compression == NULL)
192 compression = &_compression;
193 bits = R_OK | (omode == O_RDONLY ? 0 : W_OK);
194 if (omode & O_APPEND && ((p = which_protocol(file)) == PROTO_IMAP ||
195 p == PROTO_MAILDIR)) {
196 *compression = p == PROTO_IMAP ? FP_IMAP : FP_MAILDIR;
197 omode = O_RDWR | O_APPEND | O_CREAT;
198 rp = file;
199 infd = -1;
200 goto open;
202 if ((extension = strrchr(file, '.')) != NULL) {
203 rp = file;
204 if (strcmp(extension, ".gz") == 0)
205 goto gzip;
206 if (strcmp(extension, ".bz2") == 0)
207 goto bz2;
210 if (access(file, F_OK) == 0) {
211 *compression = FP_UNCOMPRESSED;
212 return Fopen(file, mode);
213 } else if (access(rp=savecat(file, ".gz"), bits) == 0) {
214 gzip: *compression = FP_GZIPPED;
215 } else if (access(rp=savecat(file, ".bz2"), bits) == 0) {
216 bz2: *compression = FP_BZIP2ED;
217 } else {
218 *compression = FP_UNCOMPRESSED;
219 return Fopen(file, mode);
221 if (access(rp, W_OK) < 0)
222 *compression |= FP_READONLY;
224 if ((infd = open(rp, bits & W_OK ? O_RDWR : O_RDONLY)) < 0
225 && ((omode&O_CREAT) == 0 || errno != ENOENT))
226 return NULL;
227 open: if ((outf = Ftemp(&tempfn, "Rz", "w+", 0600, 0)) == NULL) {
228 perror(tr(167, "tmpfile"));
229 if (infd >= 0)
230 close(infd);
231 return NULL;
233 unlink(tempfn);
234 Ftfree(&tempfn);
236 if (infd >= 0 || (*compression&FP_MASK) == FP_IMAP ||
237 (*compression&FP_MASK) == FP_MAILDIR) {
238 if (decompress(*compression, infd, fileno(outf)) < 0) {
239 if (infd >= 0)
240 close(infd);
241 Fclose(outf);
242 return NULL;
244 } else {
245 if ((infd = creat(rp, 0666)) < 0) {
246 Fclose(outf);
247 return NULL;
250 if (infd >= 0)
251 close(infd);
252 fflush(outf);
254 if (omode & O_APPEND) {
255 int flags;
257 if ((flags = fcntl(fileno(outf), F_GETFL)) < 0 ||
258 fcntl(fileno(outf), F_SETFL, flags|O_APPEND)
259 < 0 || (offset = ftell(outf)) < 0) {
260 Fclose(outf);
261 return NULL;
263 } else {
264 rewind(outf);
265 offset = 0;
268 register_file(outf, omode, 0, 0, *compression, rp, offset);
269 return outf;
272 FL FILE *
273 Ftemp(char **fn, char const *prefix, char const *mode, int bits,
274 int doregfile)
276 FILE *fp = NULL;
277 char *cp;
278 int fd;
280 *fn =
281 cp = smalloc(strlen(tempdir) + 1 + sizeof("mail") + strlen(prefix) +
282 + 7 + 1);
283 cp = sstpcpy(cp, tempdir);
284 *cp++ = '/';
285 cp = sstpcpy(cp, "mail");
286 if (*prefix) {
287 *cp++ = '-';
288 cp = sstpcpy(cp, prefix);
290 sstpcpy(cp, ".XXXXXX");
292 #ifdef HAVE_MKSTEMP
293 if ((fd = mkstemp(*fn)) < 0)
294 goto jtemperr;
295 if (bits != (S_IRUSR|S_IWUSR) && fchmod(fd, bits) < 0)
296 goto jtemperr;
297 #else
298 if (mktemp(*fn) == NULL)
299 goto Ftemperr;
300 if ((fd = open(*fn, O_CREAT|O_EXCL|O_RDWR, bits)) < 0)
301 goto jtemperr;
302 #endif
304 if (doregfile)
305 fp = Fdopen(fd, mode);
306 else {
307 (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
308 fp = fdopen(fd, mode);
310 jleave:
311 return fp;
312 jtemperr:
313 Ftfree(fn);
314 goto jleave;
317 FL void
318 Ftfree(char **fn)
320 char *cp = *fn;
322 *fn = NULL;
323 free(cp);
326 FL bool_t
327 pipe_cloexec(int fd[2])
329 bool_t rv = FAL0;
331 if (pipe(fd) < 0)
332 goto jleave;
333 fcntl(fd[0], F_SETFD, FD_CLOEXEC);
334 fcntl(fd[1], F_SETFD, FD_CLOEXEC);
335 rv = TRU1;
336 jleave:
337 return rv;
340 FL FILE *
341 Popen(const char *cmd, const char *mode, const char *sh, int newfd1)
343 int p[2];
344 int myside, hisside, fd0, fd1;
345 int pid;
346 char mod[2] = { '0', '\0' };
347 sigset_t nset;
348 FILE *fp;
350 if (! pipe_cloexec(p))
351 return NULL;
353 if (*mode == 'r') {
354 myside = p[READ];
355 fd0 = -1;
356 hisside = fd1 = p[WRITE];
357 mod[0] = *mode;
358 } else if (*mode == 'W') {
359 myside = p[WRITE];
360 hisside = fd0 = p[READ];
361 fd1 = newfd1;
362 mod[0] = 'w';
363 } else {
364 myside = p[WRITE];
365 hisside = fd0 = p[READ];
366 fd1 = -1;
367 mod[0] = 'w';
369 sigemptyset(&nset);
370 if (sh == NULL) {
371 pid = start_command(cmd, &nset, fd0, fd1, NULL, NULL, NULL);
372 } else {
373 pid = start_command(sh, &nset, fd0, fd1, "-c", cmd, NULL);
375 if (pid < 0) {
376 close(p[READ]);
377 close(p[WRITE]);
378 return NULL;
380 close(hisside);
381 if ((fp = fdopen(myside, mod)) != NULL)
382 register_file(fp, 0, 1, pid, FP_UNCOMPRESSED, NULL, 0L);
383 return fp;
386 FL int
387 Pclose(FILE *ptr, bool_t dowait)
389 int pid;
390 sigset_t nset, oset;
392 pid = file_pid(ptr);
393 if (pid < 0)
394 return 0;
395 unregister_file(ptr);
396 fclose(ptr);
397 if (dowait) {
398 sigemptyset(&nset);
399 sigaddset(&nset, SIGINT);
400 sigaddset(&nset, SIGHUP);
401 sigprocmask(SIG_BLOCK, &nset, &oset);
402 pid = wait_child(pid);
403 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
404 } else
405 free_child(pid);
406 return pid;
409 FL void
410 close_all_files(void)
412 while (fp_head)
413 if (fp_head->pipe)
414 Pclose(fp_head->fp, TRU1);
415 else
416 Fclose(fp_head->fp);
419 static void
420 register_file(FILE *fp, int omode, int ispipe, int pid, int compressed,
421 const char *realfile, long offset)
423 struct fp *fpp;
425 fpp = (struct fp*)smalloc(sizeof *fpp);
426 fpp->fp = fp;
427 fpp->omode = omode;
428 fpp->pipe = ispipe;
429 fpp->pid = pid;
430 fpp->link = fp_head;
431 fpp->compressed = compressed;
432 fpp->realfile = realfile ? sstrdup(realfile) : NULL;
433 fpp->offset = offset;
434 fp_head = fpp;
437 static enum okay
438 compress(struct fp *fpp)
440 int outfd;
441 char const *command[2];
442 enum okay ok;
444 if (fpp->omode == O_RDONLY)
445 return OKAY;
446 fflush(fpp->fp);
447 clearerr(fpp->fp);
448 if (fseek(fpp->fp, fpp->offset, SEEK_SET) < 0)
449 return STOP;
450 #ifdef HAVE_IMAP
451 if ((fpp->compressed&FP_MASK) == FP_IMAP) {
452 return imap_append(fpp->realfile, fpp->fp);
454 #endif
455 if ((fpp->compressed&FP_MASK) == FP_MAILDIR) {
456 return maildir_append(fpp->realfile, fpp->fp);
458 if ((outfd = open(fpp->realfile,
459 (fpp->omode|O_CREAT)&~O_EXCL,
460 0666)) < 0) {
461 fprintf(stderr, "Fatal: cannot create ");
462 perror(fpp->realfile);
463 return STOP;
465 if ((fpp->omode & O_APPEND) == 0)
466 ftruncate(outfd, 0);
467 switch (fpp->compressed & FP_MASK) {
468 case FP_GZIPPED:
469 command[0] = "gzip"; command[1] = "-c"; break;
470 case FP_BZIP2ED:
471 command[0] = "bzip2"; command[1] = "-c"; break;
472 default:
473 command[0] = "cat"; command[1] = NULL; break;
475 if (run_command(command[0], 0, fileno(fpp->fp), outfd,
476 command[1], NULL, NULL) < 0)
477 ok = STOP;
478 else
479 ok = OKAY;
480 close(outfd);
481 return ok;
484 static int
485 decompress(int compression, int infd, int outfd)
487 char const *command[2];
490 * Note that it is not possible to handle 'pack' or 'compress'
491 * formats because appending data does not work with them.
493 switch (compression & FP_MASK) {
494 case FP_GZIPPED: command[0] = "gzip"; command[1] = "-cd"; break;
495 case FP_BZIP2ED: command[0] = "bzip2"; command[1] = "-cd"; break;
496 case FP_IMAP: return 0;
497 case FP_MAILDIR: return 0;
498 default: command[0] = "cat"; command[1] = NULL;
500 return run_command(command[0], 0, infd, outfd, command[1], NULL, NULL);
503 static enum okay
504 unregister_file(FILE *fp)
506 struct fp **pp, *p;
507 enum okay ok = OKAY;
509 for (pp = &fp_head; (p = *pp) != (struct fp *)NULL; pp = &p->link)
510 if (p->fp == fp) {
511 if ((p->compressed&FP_MASK) != FP_UNCOMPRESSED)
512 ok = compress(p);
513 *pp = p->link;
514 free(p);
515 return ok;
517 panic(tr(153, "Invalid file pointer"));
518 /*NOTREACHED*/
519 return STOP;
522 static int
523 file_pid(FILE *fp)
525 struct fp *p;
527 for (p = fp_head; p; p = p->link)
528 if (p->fp == fp)
529 return (p->pid);
530 return -1;
534 * Run a command without a shell, with optional arguments and splicing
535 * of stdin and stdout. The command name can be a sequence of words.
536 * Signals must be handled by the caller.
537 * "Mask" contains the signals to ignore in the new process.
538 * SIGINT is enabled unless it's in the mask.
540 /*VARARGS4*/
541 FL int
542 run_command(char const *cmd, sigset_t *mask, int infd, int outfd,
543 char const *a0, char const *a1, char const *a2)
545 int pid;
547 if ((pid = start_command(cmd, mask, infd, outfd, a0, a1, a2)) < 0)
548 return -1;
549 return wait_command(pid);
552 /*VARARGS4*/
553 FL int
554 start_command(const char *cmd, sigset_t *mask, int infd, int outfd,
555 const char *a0, const char *a1, const char *a2)
557 int pid;
559 if ((pid = fork()) < 0) {
560 perror("fork");
561 return -1;
563 if (pid == 0) {
564 char *argv[100];
565 int i = getrawlist(cmd, strlen(cmd),
566 argv, sizeof argv / sizeof *argv, 0);
568 if ((argv[i++] = UNCONST(a0)) != NULL &&
569 (argv[i++] = UNCONST(a1)) != NULL &&
570 (argv[i++] = UNCONST(a2)) != NULL)
571 argv[i] = NULL;
572 prepare_child(mask, infd, outfd);
573 execvp(argv[0], argv);
574 perror(argv[0]);
575 _exit(1);
577 return pid;
580 FL void
581 prepare_child(sigset_t *nset, int infd, int outfd)
583 int i;
584 sigset_t fset;
586 /* All file descriptors other than 0, 1, and 2 are supposed to be
587 * close-on-exec */
588 if (infd >= 0)
589 dup2(infd, 0);
590 if (outfd >= 0)
591 dup2(outfd, 1);
593 if (nset) {
594 for (i = 1; i < NSIG; i++)
595 if (sigismember(nset, i))
596 safe_signal(i, SIG_IGN);
597 if (!sigismember(nset, SIGINT))
598 safe_signal(SIGINT, SIG_DFL);
601 sigemptyset(&fset);
602 sigprocmask(SIG_SETMASK, &fset, NULL);
605 static int
606 wait_command(int pid)
608 int rv = 0;
610 if (wait_child(pid) < 0) {
611 if (boption("bsdcompat") || boption("bsdmsgs"))
612 fprintf(stderr, tr(154, "Fatal error in process.\n"));
613 rv = -1;
615 return rv;
618 static struct child *
619 findchild(int pid)
621 struct child **cpp;
623 for (cpp = &_popen_child; *cpp != NULL && (*cpp)->pid != pid;
624 cpp = &(*cpp)->link)
626 if (*cpp == NULL) {
627 *cpp = smalloc(sizeof (struct child));
628 (*cpp)->pid = pid;
629 (*cpp)->done = (*cpp)->free = 0;
630 (*cpp)->link = NULL;
632 return *cpp;
635 static void
636 delchild(struct child *cp)
638 struct child **cpp;
640 for (cpp = &_popen_child; *cpp != cp; cpp = &(*cpp)->link)
642 *cpp = cp->link;
643 free(cp);
646 /*ARGSUSED*/
647 FL void
648 sigchild(int signo)
650 int pid, status;
651 struct child *cp;
652 UNUSED(signo);
654 jagain:
655 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
656 cp = findchild(pid);
657 if (cp->free)
658 delchild(cp);
659 else {
660 cp->done = 1;
661 cp->status = status;
664 if (pid == -1 && errno == EINTR)
665 goto jagain;
668 int wait_status;
671 * Mark a child as don't care.
673 FL void
674 free_child(int pid)
676 sigset_t nset, oset;
677 struct child *cp;
679 sigemptyset(&nset);
680 sigaddset(&nset, SIGCHLD);
681 sigprocmask(SIG_BLOCK, &nset, &oset);
683 cp = findchild(pid);
684 if (cp->done)
685 delchild(cp);
686 else
687 cp->free = 1;
689 sigprocmask(SIG_SETMASK, &oset, NULL);
692 /* Wait for a specific child to die */
693 FL int
694 wait_child(int pid)
696 sigset_t nset, oset;
697 struct child *cp;
699 sigemptyset(&nset);
700 sigaddset(&nset, SIGCHLD);
701 sigprocmask(SIG_BLOCK, &nset, &oset);
703 cp = findchild(pid);
704 while (!cp->done)
705 sigsuspend(&oset);
706 wait_status = cp->status;
707 delchild(cp);
709 sigprocmask(SIG_SETMASK, &oset, NULL);
711 return ((!WIFEXITED(wait_status) || WEXITSTATUS(wait_status)) ? -1 : 0);