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