xref: /illumos-gate/usr/src/cmd/mailx/fio.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 1999 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 #pragma	ident	"%Z%%M%	%I%	%E% SMI"
32 
33 #include "rcv.h"
34 #include <locale.h>
35 #include <wordexp.h>
36 
37 /*
38  * mailx -- a modified version of a University of California at Berkeley
39  *	mail program
40  *
41  * File I/O.
42  */
43 
44 static int getln(char *line, int max, FILE *f);
45 static int linecount(char *lp, long size);
46 
47 /*
48  * Set up the input pointers while copying the mail file into
49  * /tmp.
50  */
51 
52 void
53 setptr(register FILE *ibuf)
54 {
55 	int n, newline = 1, blankline = 1;
56 	int StartNewMsg = TRUE;
57 	int ToldUser = FALSE;
58 	long clen = -1L;
59 	int hdr = 0;
60 	int cflg = 0;			/* found Content-length in header */
61 	register char *cp;
62 	register int l;
63 	register long s;
64 	off_t offset;
65 	char linebuf[LINESIZE];
66 	int inhead, newmail, Odot;
67 	short flag;
68 
69 	if (!space) {
70 		msgCount = 0;
71 		offset = 0;
72 		space = 32;
73 		newmail = 0;
74 		message =
75 		    (struct message *)calloc(space, sizeof (struct message));
76 		if (message == NULL) {
77 			fprintf(stderr, gettext(
78 			    "calloc: insufficient memory for %d messages\n"),
79 			    space);
80 			exit(1);
81 			/* NOTREACHED */
82 		}
83 		dot = message;
84 	} else {
85 		newmail = 1;
86 		offset = fsize(otf);
87 	}
88 	s = 0L;
89 	l = 0;
90 	/*
91 	 * Set default flags.  When reading from
92 	 * a folder, assume the message has been
93 	 * previously read.
94 	 */
95 	if (edit)
96 		flag = MUSED|MREAD;
97 	else
98 		flag = MUSED|MNEW;
99 
100 	inhead = 0;
101 	while ((n = getln(linebuf, sizeof (linebuf), ibuf)) > 0) {
102 		if (!newline) {
103 			goto putout;
104 		}
105 	top:
106 		hdr = inhead && (headerp(linebuf) ||
107 		    (linebuf[0] == ' ' || linebuf[0] == '\t'));
108 		if (!hdr && cflg) {	/* nonheader, Content-length seen */
109 			if (clen > 0 && clen < n) {	/* read too much */
110 				/*
111 				 * NB: this only can happen if there is a
112 				 * small content that is NOT \n terminated
113 				 * and has no leading blank line, i.e., never.
114 				 */
115 				if (fwrite(linebuf, 1, (int)clen, otf) !=
116 					clen) {
117 					fclose(ibuf);
118 					fflush(otf);
119 				} else {
120 					l += linecount(linebuf, clen);
121 				}
122 				offset += clen;
123 				s += clen;
124 				n -= (int)clen;
125 				/* shift line to the left, copy null as well */
126 				memcpy(linebuf, linebuf+clen, n+1);
127 				cflg = 0;
128 				message[msgCount-1].m_clen = clen + 1;
129 				blankline = 1;
130 				StartNewMsg = TRUE;
131 				goto top;
132 			}
133 			/* here, clen == 0 or clen >= n */
134 			if (n == 1 && linebuf[0] == '\n') {
135 				/* leading empty line */
136 				clen++;		/* cheat */
137 				inhead = 0;
138 			}
139 			offset += clen;
140 			s += (long)clen;
141 			message[msgCount-1].m_clen = clen;
142 			for (;;) {
143 				if (fwrite(linebuf, 1, n, otf) != n) {
144 					fclose(ibuf);
145 					fflush(otf);
146 				} else {
147 					l += linecount(linebuf, n);
148 				}
149 				clen -= n;
150 				if (clen <= 0) {
151 					break;
152 				}
153 				n = clen < sizeof (linebuf) ?
154 				    (int)clen : (int)sizeof (linebuf);
155 				if ((n = fread(linebuf, 1, n, ibuf)) <= 0) {
156 					fprintf(stderr, gettext(
157 			"%s:\tYour mailfile was found to be corrupted.\n"),
158 					    progname);
159 					fprintf(stderr, gettext(
160 					    "\t(Unexpected end-of-file).\n"));
161 					fprintf(stderr, gettext(
162 					"\tMessage #%d may be truncated.\n\n"),
163 					    msgCount);
164 					offset -= clen;
165 					s -= clen;
166 					clen = 0; /* stop the loop */
167 				}
168 			}
169 			/* All done, go to top for next message */
170 			cflg = 0;
171 			blankline = 1;
172 			StartNewMsg = TRUE;
173 			continue;
174 		}
175 
176 		/* Look for a From line that starts a new message */
177 		if (blankline && linebuf[0] == 'F' && ishead(linebuf)) {
178 			if (msgCount > 0 && !newmail) {
179 				message[msgCount-1].m_size = s;
180 				message[msgCount-1].m_lines = l;
181 				message[msgCount-1].m_flag = flag;
182 			}
183 			if (msgCount >= space) {
184 				/*
185 				 * Limit the speed at which the
186 				 * allocated space grows.
187 				 */
188 				if (space < 512)
189 					space = space*2;
190 				else
191 					space += 512;
192 				errno = 0;
193 				Odot = dot - &(message[0]);
194 				message = (struct message *)
195 				    realloc(message,
196 					space*(sizeof (struct message)));
197 				if (message == NULL) {
198 					perror("realloc failed");
199 					fprintf(stderr, gettext(
200 			"realloc: insufficient memory for %d messages\n"),
201 					    space);
202 					exit(1);
203 				}
204 				dot = &message[Odot];
205 			}
206 			message[msgCount].m_offset = offset;
207 			message[msgCount].m_text = TRUE;
208 			message[msgCount].m_clen = 0;
209 			newmail = 0;
210 			msgCount++;
211 			if (edit)
212 				flag = MUSED|MREAD;
213 			else
214 				flag = MUSED|MNEW;
215 			inhead = 1;
216 			s = 0L;
217 			l = 0;
218 			StartNewMsg = FALSE;
219 			ToldUser = FALSE;
220 			goto putout;
221 		}
222 
223 		/* if didn't get a header line, we're no longer in the header */
224 		if (!hdr)
225 			inhead = 0;
226 		if (!inhead)
227 			goto putout;
228 
229 		/*
230 		 * Look for Status: line.  Do quick check for second character,
231 		 * many headers start with "S" but few have "t" as second char.
232 		 */
233 		if ((linebuf[1] == 't' || linebuf[1] == 'T') &&
234 		    ishfield(linebuf, "status")) {
235 			cp = hcontents(linebuf);
236 			flag = MUSED|MNEW;
237 			if (strchr(cp, 'R'))
238 				flag |= MREAD;
239 			if (strchr(cp, 'O'))
240 				flag &= ~MNEW;
241 		}
242 		/*
243 		 * Look for Content-Length and Content-Type headers.  Like
244 		 * above, do a quick check for the "-", which is rare.
245 		 */
246 		if (linebuf[7] == '-') {
247 			if (ishfield(linebuf, "content-length")) {
248 				if (!cflg) {
249 					clen = atol(hcontents(linebuf));
250 					cflg = clen >= 0;
251 				}
252 			} else if (ishfield(linebuf, "content-type")) {
253 				char word[LINESIZE];
254 				char *cp2;
255 
256 				cp = hcontents(linebuf);
257 				cp2 = word;
258 				while (!isspace(*cp))
259 					*cp2++ = *cp++;
260 				*cp2 = '\0';
261 				if (icequal(word, "binary"))
262 					message[msgCount-1].m_text = FALSE;
263 			}
264 		}
265 putout:
266 		offset += n;
267 		s += (long)n;
268 		if (fwrite(linebuf, 1, n, otf) != n) {
269 			fclose(ibuf);
270 			fflush(otf);
271 		} else {
272 			l++;
273 		}
274 		if (ferror(otf)) {
275 			perror("/tmp");
276 			exit(1);
277 		}
278 		if (msgCount == 0) {
279 			fclose(ibuf);
280 			fflush(otf);
281 		}
282 		if (linebuf[n-1] == '\n') {
283 			blankline = newline && n == 1;
284 			newline = 1;
285 			if (n == 1) {
286 				/* Blank line. Skip StartNewMsg check below */
287 				continue;
288 			}
289 		} else {
290 			newline = 0;
291 		}
292 		if (StartNewMsg && !ToldUser) {
293 			fprintf(stderr, gettext(
294 			    "%s:\tYour mailfile was found to be corrupted\n"),
295 			    progname);
296 			fprintf(stderr,
297 			    gettext("\t(Content-length mismatch).\n"));
298 			fprintf(stderr, gettext(
299 			    "\tMessage #%d may be truncated,\n"), msgCount);
300 			fprintf(stderr, gettext(
301 			    "\twith another message concatenated to it.\n\n"));
302 			ToldUser = TRUE;
303 		}
304 	}
305 
306 	if (n == 0) {
307 		fflush(otf);
308 		if (fferror(otf)) {
309 			perror("/tmp");
310 			exit(1);
311 		}
312 		if (msgCount) {
313 			message[msgCount-1].m_size = s;
314 			message[msgCount-1].m_lines = l;
315 			message[msgCount-1].m_flag = flag;
316 		}
317 		fclose(ibuf);
318 	}
319 }
320 
321 /*
322  * Compute the content length of a message and set it into m_clen.
323  */
324 
325 void
326 setclen(register struct message *mp)
327 {
328 	long c;
329 	FILE *ibuf;
330 	char line[LINESIZE];
331 	int fline, nread;
332 
333 	ibuf = setinput(mp);
334 	c = mp->m_size;
335 	fline = 1;
336 	while (c > 0L) {
337 		nread = getln(line, sizeof (line), ibuf);
338 		c -= nread;
339 		/*
340 		 * First line is the From line, so no headers
341 		 * there to worry about.
342 		 */
343 		if (fline) {
344 			fline = 0;
345 			continue;
346 		}
347 		/*
348 		 * If line is blank, we've reached end of headers.
349 		 */
350 		if (line[0] == '\n')
351 			break;
352 		/*
353 		 * If this line is a continuation
354 		 * of a previous header field, keep going.
355 		 */
356 		if (isspace(line[0]))
357 			continue;
358 		/*
359 		 * If we are no longer looking at real
360 		 * header lines, we're done.
361 		 * This happens in uucp style mail where
362 		 * there are no headers at all.
363 		 */
364 		if (!headerp(line)) {
365 			c += nread;
366 			break;
367 		}
368 	}
369 	if (c == 0)
370 		c = 1;
371 	mp->m_clen = c;
372 }
373 
374 static int
375 getln(char *line, int max, FILE *f)
376 {
377 	register int c;
378 	register char *cp, *ecp;
379 
380 	cp = line;
381 	ecp = cp + max - 1;
382 	while (cp < ecp && (c = getc(f)) != EOF)
383 		if ((*cp++ = (char)c) == '\n')
384 			break;
385 	*cp = '\0';
386 	return (cp - line);
387 }
388 
389 /*
390  * Read up a line from the specified input into the line
391  * buffer.  Return the number of characters read.  Do not
392  * include the newline at the end.
393  */
394 
395 int
396 readline(FILE *ibuf, char *linebuf)
397 {
398 	register char *cp;
399 	register int c;
400 	int seennulls = 0;
401 
402 	clearerr(ibuf);
403 	c = getc(ibuf);
404 	for (cp = linebuf; c != '\n' && c != EOF; c = getc(ibuf)) {
405 		if (c == 0) {
406 			if (!seennulls) {
407 				fprintf(stderr,
408 				    gettext("mailx: NUL changed to @\n"));
409 				seennulls++;
410 			}
411 			c = '@';
412 		}
413 		if (cp - linebuf < LINESIZE-2)
414 			*cp++ = (char)c;
415 	}
416 	*cp = 0;
417 	if (c == EOF && cp == linebuf)
418 		return (0);
419 	return (cp - linebuf + 1);
420 }
421 
422 /*
423  * linecount - determine the number of lines in a printable file.
424  */
425 
426 static int
427 linecount(char *lp, long size)
428 {
429 	register char *cp, *ecp;
430 	register int count;
431 
432 	count = 0;
433 	cp = lp;
434 	ecp = cp + size;
435 	while (cp < ecp)
436 		if (*cp++ == '\n')
437 			count++;
438 	return (count);
439 }
440 
441 /*
442  * Return a file buffer all ready to read up the
443  * passed message pointer.
444  */
445 
446 FILE *
447 setinput(register struct message *mp)
448 {
449 	fflush(otf);
450 	if (fseek(itf, mp->m_offset, 0) < 0) {
451 		perror("fseek");
452 		panic("temporary file seek");
453 	}
454 	return (itf);
455 }
456 
457 
458 /*
459  * Delete a file, but only if the file is a plain file.
460  */
461 
462 int
463 removefile(char name[])
464 {
465 	struct stat statb;
466 	extern int errno;
467 
468 	if (stat(name, &statb) < 0)
469 		if (errno == ENOENT)
470 			return (0);	/* it's already gone, no error */
471 		else
472 			return (-1);
473 	if ((statb.st_mode & S_IFMT) != S_IFREG) {
474 		errno = EISDIR;
475 		return (-1);
476 	}
477 	return (unlink(name));
478 }
479 
480 /*
481  * Terminate an editing session by attempting to write out the user's
482  * file from the temporary.  Save any new stuff appended to the file.
483  */
484 int
485 edstop(
486     int noremove	/* don't allow the file to be removed, trunc instead */
487 )
488 {
489 	register int gotcha, c;
490 	register struct message *mp;
491 	FILE *obuf, *ibuf, *tbuf = 0, *readstat;
492 	struct stat statb;
493 	char tempname[STSIZ], *id;
494 	int tmpfd = -1;
495 
496 	if (readonly)
497 		return (0);
498 	holdsigs();
499 	if (Tflag != NOSTR) {
500 		if ((readstat = fopen(Tflag, "w")) == NULL)
501 			Tflag = NOSTR;
502 	}
503 	for (mp = &message[0], gotcha = 0; mp < &message[msgCount]; mp++) {
504 		if (mp->m_flag & MNEW) {
505 			mp->m_flag &= ~MNEW;
506 			mp->m_flag |= MSTATUS;
507 		}
508 		if (mp->m_flag & (MODIFY|MDELETED|MSTATUS))
509 			gotcha++;
510 		if (Tflag != NOSTR && (mp->m_flag & (MREAD|MDELETED)) != 0) {
511 			if ((id = hfield("article-id", mp, addone)) != NOSTR)
512 				fprintf(readstat, "%s\n", id);
513 		}
514 	}
515 	if (Tflag != NOSTR)
516 		fclose(readstat);
517 	if (!gotcha || Tflag != NOSTR)
518 		goto done;
519 	if ((ibuf = fopen(editfile, "r+")) == NULL) {
520 		perror(editfile);
521 		relsesigs();
522 		longjmp(srbuf, 1);
523 	}
524 	lock(ibuf, "r+", 1);
525 	if (fstat(fileno(ibuf), &statb) >= 0 && statb.st_size > mailsize) {
526 		nstrcpy(tempname, STSIZ, "/tmp/mboxXXXXXX");
527 		if ((tmpfd = mkstemp(tempname)) == -1) {
528 			perror(tempname);
529 			fclose(ibuf);
530 			relsesigs();
531 			longjmp(srbuf, 1);
532 		}
533 		if ((obuf = fdopen(tmpfd, "w")) == NULL) {
534 			perror(tempname);
535 			fclose(ibuf);
536 			removefile(tempname);
537 			relsesigs();
538 			(void) close(tmpfd);
539 			longjmp(srbuf, 1);
540 		}
541 		fseek(ibuf, mailsize, 0);
542 		while ((c = getc(ibuf)) != EOF)
543 			putc(c, obuf);
544 		fclose(obuf);
545 		if ((tbuf = fopen(tempname, "r")) == NULL) {
546 			perror(tempname);
547 			fclose(ibuf);
548 			removefile(tempname);
549 			relsesigs();
550 			longjmp(srbuf, 1);
551 		}
552 		removefile(tempname);
553 	}
554 	if ((obuf = fopen(editfile, "r+")) == NULL) {
555 		if ((obuf = fopen(editfile, "w")) == NULL) {
556 			perror(editfile);
557 			fclose(ibuf);
558 			if (tbuf)
559 				fclose(tbuf);
560 			relsesigs();
561 			longjmp(srbuf, 1);
562 		}
563 	}
564 	printf("\"%s\" ", editfile);
565 	flush();
566 	c = 0;
567 	for (mp = &message[0]; mp < &message[msgCount]; mp++) {
568 		if ((mp->m_flag & MDELETED) != 0)
569 			continue;
570 		c++;
571 		if (msend(mp, obuf, 0, fputs) < 0) {
572 			perror(editfile);
573 			fclose(ibuf);
574 			fclose(obuf);
575 			if (tbuf)
576 				fclose(tbuf);
577 			relsesigs();
578 			longjmp(srbuf, 1);
579 		}
580 	}
581 	gotcha = (c == 0 && tbuf == NULL);
582 	if (tbuf != NULL) {
583 		while ((c = getc(tbuf)) != EOF)
584 			putc(c, obuf);
585 		fclose(tbuf);
586 	}
587 	fflush(obuf);
588 	if (fferror(obuf)) {
589 		perror(editfile);
590 		fclose(ibuf);
591 		fclose(obuf);
592 		relsesigs();
593 		longjmp(srbuf, 1);
594 	}
595 	if (gotcha && !noremove && (value("keep") == NOSTR)) {
596 		removefile(editfile);
597 		printf(gettext("removed.\n"));
598 	}
599 	else
600 		printf(gettext("updated.\n"));
601 	fclose(ibuf);
602 	trunc(obuf);
603 	fclose(obuf);
604 	flush();
605 
606 done:
607 	relsesigs();
608 	return (1);
609 }
610 
611 #ifndef OLD_BSD_SIGS
612 static int sigdepth = 0;		/* depth of holdsigs() */
613 #ifdef VMUNIX
614 static int omask = 0;
615 #else
616 static	sigset_t mask, omask;
617 #endif
618 #endif
619 /*
620  * Hold signals SIGHUP - SIGQUIT.
621  */
622 void
623 holdsigs(void)
624 {
625 #ifndef OLD_BSD_SIGS
626 	if (sigdepth++ == 0) {
627 #ifdef VMUNIX
628 		omask = sigblock(sigmask(SIGHUP) |
629 		    sigmask(SIGINT)|sigmask(SIGQUIT));
630 #else
631 		sigemptyset(&mask);
632 		sigaddset(&mask, SIGHUP);
633 		sigaddset(&mask, SIGINT);
634 		sigaddset(&mask, SIGQUIT);
635 		sigprocmask(SIG_BLOCK, &mask, &omask);
636 #endif
637 	}
638 #else
639 	sighold(SIGHUP);
640 	sighold(SIGINT);
641 	sighold(SIGQUIT);
642 #endif
643 }
644 
645 /*
646  * Release signals SIGHUP - SIGQUIT
647  */
648 void
649 relsesigs(void)
650 {
651 #ifndef OLD_BSD_SIGS
652 	if (--sigdepth == 0)
653 #ifdef VMUNIX
654 		sigsetmask(omask);
655 #else
656 		sigprocmask(SIG_SETMASK, &omask, NULL);
657 #endif
658 #else
659 	sigrelse(SIGHUP);
660 	sigrelse(SIGINT);
661 	sigrelse(SIGQUIT);
662 #endif
663 }
664 
665 #if !defined(OLD_BSD_SIGS) && !defined(VMUNIX)
666 void
667 (*sigset(int sig, void (*act)(int)))(int)
668 {
669 	struct sigaction sa, osa;
670 
671 	sa.sa_handler = (void (*)())act;
672 	sigemptyset(&sa.sa_mask);
673 	sa.sa_flags = SA_RESTART;
674 	if (sigaction(sig, &sa, &osa) < 0)
675 		return ((void (*)(int))-1);
676 	return ((void (*)(int))osa.sa_handler);
677 }
678 #endif
679 
680 /*
681  * Flush the standard output.
682  */
683 
684 void
685 flush(void)
686 {
687 	fflush(stdout);
688 	fflush(stderr);
689 }
690 
691 /*
692  * Determine the size of the file possessed by
693  * the passed buffer.
694  */
695 
696 off_t
697 fsize(FILE *iob)
698 {
699 	register int f;
700 	struct stat sbuf;
701 
702 	f = fileno(iob);
703 	if (fstat(f, &sbuf) < 0)
704 		return (0);
705 	return (sbuf.st_size);
706 }
707 
708 /*
709  * Check for either a stdio recognized error, or
710  * a possibly delayed write error (in case it's
711  * an NFS file, for instance).
712  */
713 
714 int
715 fferror(FILE *iob)
716 {
717 	return (ferror(iob) || fsync(fileno(iob)) < 0);
718 }
719 
720 /*
721  * Take a file name, possibly with shell meta characters
722  * in it and expand it by using wordexp().
723  * Return the file name as a dynamic string.
724  * If the name cannot be expanded (for whatever reason)
725  * return NULL.
726  */
727 
728 char *
729 expand(char *name)
730 {
731 	char xname[BUFSIZ];
732 	char foldbuf[BUFSIZ];
733 	register char *cp;
734 	register int l;
735 	wordexp_t wrdexp_buf;
736 
737 	if (debug) fprintf(stderr, "expand(%s)=", name);
738 	cp = strchr(name, '\0') - 1;	/* pointer to last char of name */
739 	if (isspace(*cp)) {
740 		/* strip off trailing blanks */
741 		while (cp > name && isspace(*cp))
742 			cp--;
743 		l = *++cp;	/* save char */
744 		*cp = '\0';
745 		name = savestr(name);
746 		*cp = (char)l;	/* restore char */
747 	}
748 	if (name[0] == '+' && getfold(foldbuf) >= 0) {
749 		snprintf(xname, sizeof (xname), "%s/%s", foldbuf, name + 1);
750 		cp = safeexpand(savestr(xname));
751 		if (debug) fprintf(stderr, "%s\n", cp);
752 		return (cp);
753 	}
754 	if (!anyof(name, "~{[*?$`'\"\\")) {
755 		if (debug) fprintf(stderr, "%s\n", name);
756 		return (name);
757 	}
758 	if (wordexp(name, &wrdexp_buf, 0) != 0) {
759 		fprintf(stderr, gettext("Syntax error in \"%s\"\n"), name);
760 		fflush(stderr);
761 		return (NOSTR);
762 	}
763 	if (wrdexp_buf.we_wordc > 1) {
764 		fprintf(stderr, gettext("\"%s\": Ambiguous\n"), name);
765 		fflush(stderr);
766 		return (NOSTR);
767 	}
768 	if (debug) fprintf(stderr, "%s\n", wrdexp_buf.we_wordv[0]);
769 	return (savestr(wrdexp_buf.we_wordv[0]));
770 }
771 
772 /*
773  * Take a file name, possibly with shell meta characters
774  * in it and expand it by using "sh -c echo filename"
775  * Return the file name as a dynamic string.
776  * If the name cannot be expanded (for whatever reason)
777  * return the original file name.
778  */
779 
780 char *
781 safeexpand(char name[])
782 {
783 	char *t = expand(name);
784 	return (t) ? t : savestr(name);
785 }
786 
787 /*
788  * Determine the current folder directory name.
789  */
790 int
791 getfold(char *name)
792 {
793 	char *folder;
794 
795 	if ((folder = value("folder")) == NOSTR || *folder == '\0')
796 		return (-1);
797 	/*
798 	 * If name looks like a folder name, don't try
799 	 * to expand it, to prevent infinite recursion.
800 	 */
801 	if (*folder != '+' && (folder = expand(folder)) == NOSTR ||
802 	    *folder == '\0')
803 		return (-1);
804 	if (*folder == '/') {
805 		nstrcpy(name, BUFSIZ, folder);
806 	} else
807 		snprintf(name, BUFSIZ, "%s/%s", homedir, folder);
808 	return (0);
809 }
810 
811 /*
812  * A nicer version of Fdopen, which allows us to fclose
813  * without losing the open file.
814  */
815 
816 FILE *
817 Fdopen(int fildes, char *mode)
818 {
819 	register int f;
820 
821 	f = dup(fildes);
822 	if (f < 0) {
823 		perror("dup");
824 		return (NULL);
825 	}
826 	return (fdopen(f, mode));
827 }
828 
829 /*
830  * return the filename associated with "s".  This function always
831  * returns a non-null string (no error checking is done on the receiving end)
832  */
833 char *
834 Getf(register char *s)
835 {
836 	register char *cp;
837 	static char defbuf[PATHSIZE];
838 
839 	if (((cp = value(s)) != 0) && *cp) {
840 		return (safeexpand(cp));
841 	} else if (strcmp(s, "MBOX") == 0) {
842 		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
843 			"mbox");
844 		return (defbuf);
845 	} else if (strcmp(s, "DEAD") == 0) {
846 		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
847 			"dead.letter");
848 		return (defbuf);
849 	} else if (strcmp(s, "MAILRC") == 0) {
850 		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
851 			".mailrc");
852 		return (defbuf);
853 	} else if (strcmp(s, "HOME") == 0) {
854 		/* no recursion allowed! */
855 		return (".");
856 	}
857 	return ("DEAD");	/* "cannot happen" */
858 }
859