xref: /openbsd-src/usr.bin/mail/popen.c (revision 8445c53715e7030056b779e8ab40efb7820981f2)
1 /*	$OpenBSD: popen.c,v 1.27 2001/09/04 23:16:11 millert Exp $	*/
2 /*	$NetBSD: popen.c,v 1.6 1997/05/13 06:48:42 mikel Exp $	*/
3 
4 /*
5  * Copyright (c) 1980, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. 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.
23  *
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.
35  */
36 
37 #ifndef lint
38 #if 0
39 static char sccsid[] = "@(#)popen.c	8.1 (Berkeley) 6/6/93";
40 #else
41 static char rcsid[] = "$OpenBSD: popen.c,v 1.27 2001/09/04 23:16:11 millert Exp $";
42 #endif
43 #endif /* not lint */
44 
45 #include "rcv.h"
46 #include <sys/wait.h>
47 #include <fcntl.h>
48 #include <errno.h>
49 #ifdef __STDC__
50 #include <stdarg.h>
51 #else
52 #include <varargs.h>
53 #endif
54 #include "extern.h"
55 
56 #define READ 0
57 #define WRITE 1
58 
59 struct fp {
60 	FILE *fp;
61 	int pipe;
62 	int pid;
63 	struct fp *link;
64 };
65 static struct fp *fp_head;
66 
67 struct child {
68 	int pid;
69 	char done;
70 	char free;
71 	int status;
72 	struct child *link;
73 };
74 static struct child *child, *child_freelist = NULL;
75 static struct child *findchild __P((int, int));
76 static void delchild __P((struct child *));
77 static int file_pid __P((FILE *));
78 static int handle_spool_locks __P((int));
79 
80 FILE *
81 Fopen(file, mode)
82 	char *file, *mode;
83 {
84 	FILE *fp;
85 
86 	if ((fp = fopen(file, mode)) != NULL) {
87 		register_file(fp, 0, 0);
88 		(void)fcntl(fileno(fp), F_SETFD, 1);
89 	}
90 	return(fp);
91 }
92 
93 FILE *
94 Fdopen(fd, mode)
95 	int fd;
96 	char *mode;
97 {
98 	FILE *fp;
99 
100 	if ((fp = fdopen(fd, mode)) != NULL) {
101 		register_file(fp, 0, 0);
102 		(void)fcntl(fileno(fp), F_SETFD, 1);
103 	}
104 	return(fp);
105 }
106 
107 int
108 Fclose(fp)
109 	FILE *fp;
110 {
111 	unregister_file(fp);
112 	return(fclose(fp));
113 }
114 
115 FILE *
116 Popen(cmd, mode)
117 	char *cmd;
118 	char *mode;
119 {
120 	int p[2];
121 	int myside, hisside, fd0, fd1;
122 	int pid;
123 	sigset_t nset;
124 	FILE *fp;
125 
126 	if (pipe(p) < 0)
127 		return(NULL);
128 	(void)fcntl(p[READ], F_SETFD, 1);
129 	(void)fcntl(p[WRITE], F_SETFD, 1);
130 	if (*mode == 'r') {
131 		myside = p[READ];
132 		hisside = fd0 = fd1 = p[WRITE];
133 	} else {
134 		myside = p[WRITE];
135 		hisside = fd0 = p[READ];
136 		fd1 = -1;
137 	}
138 	sigemptyset(&nset);
139 	if ((pid = start_command(value("SHELL"), &nset, fd0, fd1,
140 						"-c", cmd, NULL)) < 0) {
141 		(void)close(p[READ]);
142 		(void)close(p[WRITE]);
143 		return(NULL);
144 	}
145 	(void)close(hisside);
146 	if ((fp = fdopen(myside, mode)) != NULL)
147 		register_file(fp, 1, pid);
148 	return(fp);
149 }
150 
151 int
152 Pclose(ptr)
153 	FILE *ptr;
154 {
155 	int i;
156 	sigset_t nset, oset;
157 
158 	i = file_pid(ptr);
159 	unregister_file(ptr);
160 	(void)fclose(ptr);
161 	sigemptyset(&nset);
162 	sigaddset(&nset, SIGINT);
163 	sigaddset(&nset, SIGHUP);
164 	sigprocmask(SIG_BLOCK, &nset, &oset);
165 	i = wait_child(i);
166 	sigprocmask(SIG_SETMASK, &oset, NULL);
167 	return(i);
168 }
169 
170 void
171 close_all_files()
172 {
173 
174 	while (fp_head)
175 		if (fp_head->pipe)
176 			(void)Pclose(fp_head->fp);
177 		else
178 			(void)Fclose(fp_head->fp);
179 }
180 
181 void
182 register_file(fp, pipe, pid)
183 	FILE *fp;
184 	int pipe, pid;
185 {
186 	struct fp *fpp;
187 
188 	if ((fpp = (struct fp *)malloc(sizeof(*fpp))) == NULL)
189 		errx(1, "Out of memory");
190 	fpp->fp = fp;
191 	fpp->pipe = pipe;
192 	fpp->pid = pid;
193 	fpp->link = fp_head;
194 	fp_head = fpp;
195 }
196 
197 void
198 unregister_file(fp)
199 	FILE *fp;
200 {
201 	struct fp **pp, *p;
202 
203 	for (pp = &fp_head; (p = *pp) != NULL; pp = &p->link)
204 		if (p->fp == fp) {
205 			*pp = p->link;
206 			(void)free(p);
207 			return;
208 		}
209 	errx(1, "Invalid file pointer");
210 }
211 
212 static int
213 file_pid(fp)
214 	FILE *fp;
215 {
216 	struct fp *p;
217 
218 	for (p = fp_head; p; p = p->link)
219 		if (p->fp == fp)
220 			return(p->pid);
221 	errx(1, "Invalid file pointer");
222 	/*NOTREACHED*/
223 }
224 
225 /*
226  * Run a command without a shell, with optional arguments and splicing
227  * of stdin (-1 means none) and stdout.  The command name can be a sequence
228  * of words.
229  * Signals must be handled by the caller.
230  * "nset" contains the signals to ignore in the new process.
231  * SIGINT is enabled unless it's in "nset".
232  */
233 int
234 start_commandv(cmd, nset, infd, outfd, args)
235 	char *cmd;
236 	sigset_t *nset;
237 	int infd, outfd;
238 	va_list args;
239 {
240 	int pid;
241 
242 	if ((pid = fork()) < 0) {
243 		warn("fork");
244 		return(-1);
245 	}
246 	if (pid == 0) {
247 		char *argv[100];
248 		int i = getrawlist(cmd, argv, sizeof(argv)/ sizeof(*argv));
249 
250 		while ((argv[i++] = va_arg(args, char *)))
251 			;
252 		argv[i] = NULL;
253 		prepare_child(nset, infd, outfd);
254 		execvp(argv[0], argv);
255 		warn("%s", argv[0]);
256 		_exit(1);
257 	}
258 	return(pid);
259 }
260 
261 int
262 #ifdef __STDC__
263 run_command(char *cmd, sigset_t *nset, int infd, int outfd, ...)
264 #else
265 run_command(cmd, nset, infd, outfd, va_alist)
266 	char *cmd;
267 	sigset_t *nset;
268 	int infd;
269 	int outfd;
270 	va_dcl
271 #endif
272 {
273 	int pid;
274 	va_list args;
275 
276 #ifdef __STDC__
277 	va_start(args, outfd);
278 #else
279 	va_start(args);
280 #endif
281 	pid = start_commandv(cmd, nset, infd, outfd, args);
282 	va_end(args);
283 	if (pid < 0)
284 		return(-1);
285 	return(wait_command(pid));
286 }
287 
288 int
289 #ifdef __STDC__
290 start_command(char *cmd, sigset_t *nset, int infd, int outfd, ...)
291 #else
292 start_command(cmd, nset, infd, outfd, va_alist)
293 	char *cmd;
294 	sigset_t *nset;
295 	int infd;
296 	int outfd;
297 	va_dcl
298 #endif
299 {
300 	va_list args;
301 	int r;
302 
303 #ifdef __STDC__
304 	va_start(args, outfd);
305 #else
306 	va_start(args);
307 #endif
308 	r = start_commandv(cmd, nset, infd, outfd, args);
309 	va_end(args);
310 	return(r);
311 }
312 
313 void
314 prepare_child(nset, infd, outfd)
315 	sigset_t *nset;
316 	int infd, outfd;
317 {
318 	int i;
319 	sigset_t eset;
320 
321 	/*
322 	 * All file descriptors other than 0, 1, and 2 are supposed to be
323 	 * close-on-exec.
324 	 */
325 	if (infd > 0) {
326 		dup2(infd, 0);
327 	} else if (infd != 0) {
328 		/* we don't want the child stealing my stdin input */
329 		close(0);
330 		open(_PATH_DEVNULL, O_RDONLY, 0);
331 	}
332 	if (outfd >= 0 && outfd != 1)
333 		dup2(outfd, 1);
334 	if (nset == NULL)
335 		return;
336 	if (nset != NULL) {
337 		for (i = 1; i < NSIG; i++)
338 			if (sigismember(nset, i))
339 				(void)signal(i, SIG_IGN);
340 	}
341 	if (nset == NULL || !sigismember(nset, SIGINT))
342 		(void)signal(SIGINT, SIG_DFL);
343 	sigemptyset(&eset);
344 	(void)sigprocmask(SIG_SETMASK, &eset, NULL);
345 }
346 
347 int
348 wait_command(pid)
349 	int pid;
350 {
351 
352 	if (wait_child(pid) < 0) {
353 		puts("Fatal error in process.");
354 		return(-1);
355 	}
356 	return(0);
357 }
358 
359 static struct child *
360 findchild(pid, dont_alloc)
361 	int pid;
362 	int dont_alloc;
363 {
364 	struct child **cpp;
365 
366 	for (cpp = &child; *cpp != NULL && (*cpp)->pid != pid;
367 	     cpp = &(*cpp)->link)
368 			;
369 	if (*cpp == NULL) {
370 		if (dont_alloc)
371 			return(NULL);
372 		if (child_freelist) {
373 			*cpp = child_freelist;
374 			child_freelist = (*cpp)->link;
375 		} else
376 			*cpp = (struct child *)malloc(sizeof(struct child));
377 		(*cpp)->pid = pid;
378 		(*cpp)->done = (*cpp)->free = 0;
379 		(*cpp)->link = NULL;
380 	}
381 	return(*cpp);
382 }
383 
384 static void
385 delchild(cp)
386 	struct child *cp;
387 {
388 	struct child **cpp;
389 
390 	for (cpp = &child; *cpp != cp; cpp = &(*cpp)->link)
391 		;
392 	*cpp = cp->link;
393 	cp->link = child_freelist;
394 	child_freelist = cp;
395 }
396 
397 void
398 sigchild(signo)
399 	int signo;
400 {
401 	int pid;
402 	int status;
403 	struct child *cp;
404 	int save_errno = errno;
405 
406 	while ((pid =
407 	    waitpid((pid_t)-1, &status, WNOHANG)) > 0) {
408 		cp = findchild(pid, 1);
409 		if (!cp)
410 			continue;
411 		if (cp->free)
412 			delchild(cp);
413 		else {
414 			cp->done = 1;
415 			cp->status = status;
416 		}
417 	}
418 	errno = save_errno;
419 }
420 
421 int wait_status;
422 
423 /*
424  * Wait for a specific child to die.
425  */
426 int
427 wait_child(pid)
428 	int pid;
429 {
430 	struct child *cp;
431 	sigset_t nset, oset;
432 	int rv = 0;
433 
434 	sigemptyset(&nset);
435 	sigaddset(&nset, SIGCHLD);
436 	sigprocmask(SIG_BLOCK, &nset, &oset);
437 	/*
438 	 * If we have not already waited on the pid (via sigchild)
439 	 * wait on it now.  Otherwise, use the wait status stashed
440 	 * by sigchild.
441 	 */
442 	cp = findchild(pid, 1);
443 	if (cp == NULL || !cp->done)
444 		rv = waitpid(pid, &wait_status, 0);
445 	else
446 		wait_status = cp->status;
447 	if (cp != NULL)
448 		delchild(cp);
449 	sigprocmask(SIG_SETMASK, &oset, NULL);
450 	if (rv == -1 || (WIFEXITED(wait_status) && WEXITSTATUS(wait_status)))
451 		return(-1);
452 	else
453 		return(0);
454 }
455 
456 /*
457  * Mark a child as don't care.
458  */
459 void
460 free_child(pid)
461 	int pid;
462 {
463 	struct child *cp;
464 	sigset_t nset, oset;
465 
466 	sigemptyset(&nset);
467 	sigaddset(&nset, SIGCHLD);
468 	sigprocmask(SIG_BLOCK, &nset, &oset);
469 	if ((cp = findchild(pid, 0)) != NULL) {
470 		if (cp->done)
471 			delchild(cp);
472 		else
473 			cp->free = 1;
474 	}
475 	sigprocmask(SIG_SETMASK, &oset, NULL);
476 }
477 
478 /*
479  * Lock(1)/unlock(0) mail spool using lockspool(1).
480  * Returns 1 for success, 0 for failure, -1 for bad usage.
481  */
482 static int
483 handle_spool_locks(action)
484 	int action;
485 {
486 	static FILE *lockfp = NULL;
487 	static int lock_pid;
488 
489 	if (action == 0) {
490 		/* Clear the lock */
491 		if (lockfp == NULL) {
492 			fputs("handle_spool_locks: no spool lock to remove.\n",
493 			    stderr);
494 			return(-1);
495 		}
496 		(void)kill(lock_pid, SIGTERM);
497 		(void)Pclose(lockfp);
498 		lockfp = NULL;
499 	} else if (action == 1) {
500 		char *cmd;
501 		char buf[sizeof(_PATH_LOCKSPOOL) + MAXLOGNAME + 1];
502 
503 		/* XXX - lockspool requires root for user arg, we do not */
504 		if (uflag) {
505 			snprintf(buf, sizeof(buf), "%s %s", _PATH_LOCKSPOOL,
506 			    myname);
507 			cmd = buf;
508 		} else
509 			cmd = _PATH_LOCKSPOOL;
510 
511 		/* Create the lock */
512 		lockfp = Popen(cmd, "r");
513 		if (lockfp == NULL)
514 			return(0);
515 		if (getc(lockfp) != '1') {
516 			Pclose(lockfp);
517 			lockfp = NULL;
518 			return(0);
519 		}
520 		lock_pid = fp_head->pid;	/* new entries added at head */
521 	} else {
522 		(void)fprintf(stderr, "handle_spool_locks: unknown action %d\n",
523 		    action);
524 		return(-1);
525 	}
526 
527 	return(1);
528 }
529 
530 int
531 spool_lock()
532 {
533 	return(handle_spool_locks(1));
534 }
535 
536 int
537 spool_unlock()
538 {
539 	return(handle_spool_locks(0));
540 }
541