Offer a simple coloured display..
[s-mailx.git] / popen.c
blob5cc4209944ed9d6cc780f4e60fa53bd062e96816
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 bool_t
387 Pclose(FILE *ptr, bool_t dowait)
389 sigset_t nset, oset;
390 bool_t rv = FAL0;
391 int pid;
393 pid = file_pid(ptr);
394 if (pid < 0)
395 goto jleave;
396 unregister_file(ptr);
397 fclose(ptr);
398 if (dowait) {
399 sigemptyset(&nset);
400 sigaddset(&nset, SIGINT);
401 sigaddset(&nset, SIGHUP);
402 sigprocmask(SIG_BLOCK, &nset, &oset);
403 rv = wait_child(pid, NULL);
404 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
405 } else {
406 free_child(pid);
407 rv = TRU1;
409 jleave:
410 return rv;
413 FL void
414 close_all_files(void)
416 while (fp_head)
417 if (fp_head->pipe)
418 Pclose(fp_head->fp, TRU1);
419 else
420 Fclose(fp_head->fp);
423 static void
424 register_file(FILE *fp, int omode, int ispipe, int pid, int compressed,
425 const char *realfile, long offset)
427 struct fp *fpp;
429 fpp = (struct fp*)smalloc(sizeof *fpp);
430 fpp->fp = fp;
431 fpp->omode = omode;
432 fpp->pipe = ispipe;
433 fpp->pid = pid;
434 fpp->link = fp_head;
435 fpp->compressed = compressed;
436 fpp->realfile = realfile ? sstrdup(realfile) : NULL;
437 fpp->offset = offset;
438 fp_head = fpp;
441 static enum okay
442 compress(struct fp *fpp)
444 int outfd;
445 char const *command[2];
446 enum okay ok;
448 if (fpp->omode == O_RDONLY)
449 return OKAY;
450 fflush(fpp->fp);
451 clearerr(fpp->fp);
452 if (fseek(fpp->fp, fpp->offset, SEEK_SET) < 0)
453 return STOP;
454 #ifdef HAVE_IMAP
455 if ((fpp->compressed&FP_MASK) == FP_IMAP) {
456 return imap_append(fpp->realfile, fpp->fp);
458 #endif
459 if ((fpp->compressed&FP_MASK) == FP_MAILDIR) {
460 return maildir_append(fpp->realfile, fpp->fp);
462 if ((outfd = open(fpp->realfile,
463 (fpp->omode|O_CREAT)&~O_EXCL,
464 0666)) < 0) {
465 fprintf(stderr, "Fatal: cannot create ");
466 perror(fpp->realfile);
467 return STOP;
469 if ((fpp->omode & O_APPEND) == 0)
470 ftruncate(outfd, 0);
471 switch (fpp->compressed & FP_MASK) {
472 case FP_GZIPPED:
473 command[0] = "gzip"; command[1] = "-c"; break;
474 case FP_BZIP2ED:
475 command[0] = "bzip2"; command[1] = "-c"; break;
476 default:
477 command[0] = "cat"; command[1] = NULL; break;
479 if (run_command(command[0], 0, fileno(fpp->fp), outfd,
480 command[1], NULL, NULL) < 0)
481 ok = STOP;
482 else
483 ok = OKAY;
484 close(outfd);
485 return ok;
488 static int
489 decompress(int compression, int infd, int outfd)
491 char const *command[2];
494 * Note that it is not possible to handle 'pack' or 'compress'
495 * formats because appending data does not work with them.
497 switch (compression & FP_MASK) {
498 case FP_GZIPPED: command[0] = "gzip"; command[1] = "-cd"; break;
499 case FP_BZIP2ED: command[0] = "bzip2"; command[1] = "-cd"; break;
500 case FP_IMAP: return 0;
501 case FP_MAILDIR: return 0;
502 default: command[0] = "cat"; command[1] = NULL;
504 return run_command(command[0], 0, infd, outfd, command[1], NULL, NULL);
507 static enum okay
508 unregister_file(FILE *fp)
510 struct fp **pp, *p;
511 enum okay ok = OKAY;
513 for (pp = &fp_head; (p = *pp) != (struct fp *)NULL; pp = &p->link)
514 if (p->fp == fp) {
515 if ((p->compressed&FP_MASK) != FP_UNCOMPRESSED)
516 ok = compress(p);
517 *pp = p->link;
518 free(p);
519 return ok;
521 panic(tr(153, "Invalid file pointer"));
522 /*NOTREACHED*/
523 return STOP;
526 static int
527 file_pid(FILE *fp)
529 struct fp *p;
531 for (p = fp_head; p; p = p->link)
532 if (p->fp == fp)
533 return (p->pid);
534 return -1;
538 * Run a command without a shell, with optional arguments and splicing
539 * of stdin and stdout. The command name can be a sequence of words.
540 * Signals must be handled by the caller.
541 * "Mask" contains the signals to ignore in the new process.
542 * SIGINT is enabled unless it's in the mask.
544 /*VARARGS4*/
545 FL int
546 run_command(char const *cmd, sigset_t *mask, int infd, int outfd,
547 char const *a0, char const *a1, char const *a2)
549 int pid;
551 if ((pid = start_command(cmd, mask, infd, outfd, a0, a1, a2)) < 0)
552 return -1;
553 return wait_command(pid);
556 /*VARARGS4*/
557 FL int
558 start_command(const char *cmd, sigset_t *mask, int infd, int outfd,
559 const char *a0, const char *a1, const char *a2)
561 int pid;
563 if ((pid = fork()) < 0) {
564 perror("fork");
565 return -1;
567 if (pid == 0) {
568 char *argv[100];
569 int i = getrawlist(cmd, strlen(cmd),
570 argv, sizeof argv / sizeof *argv, 0);
572 if ((argv[i++] = UNCONST(a0)) != NULL &&
573 (argv[i++] = UNCONST(a1)) != NULL &&
574 (argv[i++] = UNCONST(a2)) != NULL)
575 argv[i] = NULL;
576 prepare_child(mask, infd, outfd);
577 execvp(argv[0], argv);
578 perror(argv[0]);
579 _exit(1);
581 return pid;
584 FL void
585 prepare_child(sigset_t *nset, int infd, int outfd)
587 int i;
588 sigset_t fset;
590 /* All file descriptors other than 0, 1, and 2 are supposed to be
591 * close-on-exec */
592 if (infd >= 0)
593 dup2(infd, 0);
594 if (outfd >= 0)
595 dup2(outfd, 1);
597 if (nset) {
598 for (i = 1; i < NSIG; i++)
599 if (sigismember(nset, i))
600 safe_signal(i, SIG_IGN);
601 if (!sigismember(nset, SIGINT))
602 safe_signal(SIGINT, SIG_DFL);
605 sigemptyset(&fset);
606 sigprocmask(SIG_SETMASK, &fset, NULL);
609 static int
610 wait_command(int pid)
612 int rv = 0;
614 if (!wait_child(pid, NULL)) {
615 if (ok_blook(bsdcompat) || ok_blook(bsdmsgs))
616 fprintf(stderr, tr(154, "Fatal error in process.\n"));
617 rv = -1;
619 return rv;
622 static struct child *
623 findchild(int pid)
625 struct child **cpp;
627 for (cpp = &_popen_child; *cpp != NULL && (*cpp)->pid != pid;
628 cpp = &(*cpp)->link)
630 if (*cpp == NULL) {
631 *cpp = smalloc(sizeof (struct child));
632 (*cpp)->pid = pid;
633 (*cpp)->done = (*cpp)->free = 0;
634 (*cpp)->link = NULL;
636 return *cpp;
639 static void
640 delchild(struct child *cp)
642 struct child **cpp;
644 for (cpp = &_popen_child; *cpp != cp; cpp = &(*cpp)->link)
646 *cpp = cp->link;
647 free(cp);
650 /*ARGSUSED*/
651 FL void
652 sigchild(int signo)
654 int pid, status;
655 struct child *cp;
656 UNUSED(signo);
658 jagain:
659 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
660 cp = findchild(pid);
661 if (cp->free)
662 delchild(cp);
663 else {
664 cp->done = 1;
665 cp->status = status;
668 if (pid == -1 && errno == EINTR)
669 goto jagain;
673 * Mark a child as don't care.
675 FL void
676 free_child(int pid)
678 sigset_t nset, oset;
679 struct child *cp;
681 sigemptyset(&nset);
682 sigaddset(&nset, SIGCHLD);
683 sigprocmask(SIG_BLOCK, &nset, &oset);
685 cp = findchild(pid);
686 if (cp->done)
687 delchild(cp);
688 else
689 cp->free = 1;
691 sigprocmask(SIG_SETMASK, &oset, NULL);
694 FL bool_t
695 wait_child(int pid, int *wait_status)
697 sigset_t nset, oset;
698 struct child *cp;
699 int ws;
701 sigemptyset(&nset);
702 sigaddset(&nset, SIGCHLD);
703 sigprocmask(SIG_BLOCK, &nset, &oset);
705 cp = findchild(pid);
706 while (!cp->done)
707 sigsuspend(&oset);
708 ws = cp->status;
709 delchild(cp);
711 sigprocmask(SIG_SETMASK, &oset, NULL);
713 if (wait_status != NULL)
714 *wait_status = ws;
715 return (WIFEXITED(ws) && WEXITSTATUS(ws) == 0);