xref: /original-bsd/usr.bin/mail/fio.c (revision 7cab520e)
1 /*
2  * Copyright (c) 1980 Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that the above copyright notice and this paragraph are
7  * duplicated in all such forms and that any documentation,
8  * advertising materials, and other materials related to such
9  * distribution and use acknowledge that the software was developed
10  * by the University of California, Berkeley.  The name of the
11  * University may not be used to endorse or promote products derived
12  * from this software without specific prior written permission.
13  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16  */
17 
18 #ifndef lint
19 static char sccsid[] = "@(#)fio.c	5.18 (Berkeley) 08/09/88";
20 #endif /* not lint */
21 
22 #include "rcv.h"
23 #include <sys/stat.h>
24 #include <sys/file.h>
25 #include <sys/wait.h>
26 #include <errno.h>
27 
28 /*
29  * Mail -- a mail program
30  *
31  * File I/O.
32  */
33 
34 /*
35  * Set up the input pointers while copying the mail file into
36  * /tmp.
37  */
38 setptr(ibuf)
39 	register FILE *ibuf;
40 {
41 	register c;
42 	register char *cp, *cp2;
43 	register count;
44 	char linebuf[LINESIZE];
45 	int maybe, inhead;
46 	FILE *mestmp;
47 	off_t offset;
48 	struct message this;
49 	extern char tempSet[];
50 
51 	if ((c = opentemp(tempSet)) < 0)
52 		exit(1);
53 	if ((mestmp = fdopen(c, "r+")) == NULL)
54 		panic("Can't open temporary");
55 	msgCount = 0;
56 	maybe = 1;
57 	inhead = 0;
58 	offset = 0;
59 	this.m_flag = MUSED|MNEW;
60 	this.m_size = 0;
61 	this.m_lines = 0;
62 	this.m_block = 0;
63 	this.m_offset = 0;
64 	for (;;) {
65 		if (fgets(linebuf, LINESIZE, ibuf) == NULL) {
66 			if (append(&this, mestmp)) {
67 				perror(tempSet);
68 				exit(1);
69 			}
70 			fclose(ibuf);
71 			makemessage(mestmp);
72 			return;
73 		}
74 		count = strlen(linebuf);
75 		fwrite(linebuf, sizeof *linebuf, count, otf);
76 		if (ferror(otf)) {
77 			perror("/tmp");
78 			exit(1);
79 		}
80 		linebuf[count - 1] = 0;
81 		if (maybe && linebuf[0] == 'F' && ishead(linebuf)) {
82 			msgCount++;
83 			if (append(&this, mestmp)) {
84 				perror(tempSet);
85 				exit(1);
86 			}
87 			this.m_flag = MUSED|MNEW;
88 			this.m_size = 0;
89 			this.m_lines = 0;
90 			this.m_block = blockof(offset);
91 			this.m_offset = offsetof(offset);
92 			inhead = 1;
93 		} else if (linebuf[0] == 0) {
94 			inhead = 0;
95 		} else if (inhead) {
96 			for (cp = linebuf, cp2 = "status";; cp++) {
97 				if ((c = *cp2++) == 0) {
98 					while (isspace(*cp++))
99 						;
100 					if (cp[-1] != ':')
101 						break;
102 					while (c = *cp++)
103 						if (c == 'R')
104 							this.m_flag |= MREAD;
105 						else if (c == 'O')
106 							this.m_flag &= ~MNEW;
107 					inhead = 0;
108 					break;
109 				}
110 				if (*cp != c && *cp != toupper(c))
111 					break;
112 			}
113 		}
114 		offset += count;
115 		this.m_size += count;
116 		this.m_lines++;
117 		maybe = linebuf[0] == 0;
118 	}
119 }
120 
121 /*
122  * Drop the passed line onto the passed output buffer.
123  * If a write error occurs, return -1, else the count of
124  * characters written, including the newline.
125  */
126 putline(obuf, linebuf)
127 	FILE *obuf;
128 	char *linebuf;
129 {
130 	register int c;
131 
132 	c = strlen(linebuf);
133 	fwrite(linebuf, sizeof *linebuf, c, obuf);
134 	putc('\n', obuf);
135 	if (ferror(obuf))
136 		return (-1);
137 	return (c + 1);
138 }
139 
140 /*
141  * Read up a line from the specified input into the line
142  * buffer.  Return the number of characters read.  Do not
143  * include the newline at the end.
144  */
145 readline(ibuf, linebuf)
146 	FILE *ibuf;
147 	char *linebuf;
148 {
149 	register int n;
150 
151 	clearerr(ibuf);
152 	if (fgets(linebuf, LINESIZE, ibuf) == NULL)
153 		return -1;
154 	n = strlen(linebuf);
155 	if (n > 0 && linebuf[n - 1] == '\n')
156 		linebuf[--n] = '\0';
157 	return n;
158 }
159 
160 /*
161  * Return a file buffer all ready to read up the
162  * passed message pointer.
163  */
164 FILE *
165 setinput(mp)
166 	register struct message *mp;
167 {
168 
169 	fflush(otf);
170 	if (fseek(itf, positionof(mp->m_block, mp->m_offset), 0) < 0) {
171 		perror("fseek");
172 		panic("temporary file seek");
173 	}
174 	return (itf);
175 }
176 
177 /*
178  * Take the data out of the passed ghost file and toss it into
179  * a dynamically allocated message structure.
180  */
181 makemessage(f)
182 	FILE *f;
183 {
184 	register size = (msgCount + 1) * sizeof (struct message);
185 	off_t lseek();
186 
187 	if (message != 0)
188 		free((char *) message);
189 	if ((message = (struct message *) malloc((unsigned) size)) == 0)
190 		panic("Insufficient memory for %d messages", msgCount);
191 	dot = message;
192 	size -= sizeof (struct message);
193 	fflush(f);
194 	lseek(fileno(f), (long) sizeof *message, 0);
195 	if (read(fileno(f), (char *) message, size) != size)
196 		panic("Message temporary file corrupted");
197 	message[msgCount].m_size = 0;
198 	message[msgCount].m_lines = 0;
199 	fclose(f);
200 }
201 
202 /*
203  * Append the passed message descriptor onto the temp file.
204  * If the write fails, return 1, else 0
205  */
206 append(mp, f)
207 	struct message *mp;
208 	FILE *f;
209 {
210 	return fwrite((char *) mp, sizeof *mp, 1, f) != 1;
211 }
212 
213 /*
214  * Delete a file, but only if the file is a plain file.
215  */
216 remove(name)
217 	char name[];
218 {
219 	struct stat statb;
220 	extern int errno;
221 
222 	if (stat(name, &statb) < 0)
223 		return(-1);
224 	if ((statb.st_mode & S_IFMT) != S_IFREG) {
225 		errno = EISDIR;
226 		return(-1);
227 	}
228 	return unlink(name);
229 }
230 
231 /*
232  * Terminate an editing session by attempting to write out the user's
233  * file from the temporary.  Save any new stuff appended to the file.
234  */
235 edstop()
236 {
237 	register int gotcha, c;
238 	register struct message *mp;
239 	FILE *obuf, *ibuf, *readstat;
240 	struct stat statb;
241 	char tempname[30];
242 	char *mktemp();
243 
244 	if (readonly)
245 		return;
246 	holdsigs();
247 	if (Tflag != NOSTR) {
248 		if ((readstat = fopen(Tflag, "w")) == NULL)
249 			Tflag = NOSTR;
250 	}
251 	for (mp = &message[0], gotcha = 0; mp < &message[msgCount]; mp++) {
252 		if (mp->m_flag & MNEW) {
253 			mp->m_flag &= ~MNEW;
254 			mp->m_flag |= MSTATUS;
255 		}
256 		if (mp->m_flag & (MODIFY|MDELETED|MSTATUS))
257 			gotcha++;
258 		if (Tflag != NOSTR && (mp->m_flag & (MREAD|MDELETED)) != 0) {
259 			char *id;
260 
261 			if ((id = hfield("article-id", mp)) != NOSTR)
262 				fprintf(readstat, "%s\n", id);
263 		}
264 	}
265 	if (Tflag != NOSTR)
266 		fclose(readstat);
267 	if (!gotcha || Tflag != NOSTR)
268 		goto done;
269 	ibuf = NULL;
270 	if (stat(mailname, &statb) >= 0 && statb.st_size > mailsize) {
271 		strcpy(tempname, "/tmp/mboxXXXXXX");
272 		mktemp(tempname);
273 		if ((obuf = fopen(tempname, "w")) == NULL) {
274 			perror(tempname);
275 			relsesigs();
276 			reset(0);
277 		}
278 		if ((ibuf = fopen(mailname, "r")) == NULL) {
279 			perror(mailname);
280 			fclose(obuf);
281 			remove(tempname);
282 			relsesigs();
283 			reset(0);
284 		}
285 		fseek(ibuf, mailsize, 0);
286 		while ((c = getc(ibuf)) != EOF)
287 			putc(c, obuf);
288 		fclose(ibuf);
289 		fclose(obuf);
290 		if ((ibuf = fopen(tempname, "r")) == NULL) {
291 			perror(tempname);
292 			remove(tempname);
293 			relsesigs();
294 			reset(0);
295 		}
296 		remove(tempname);
297 	}
298 	printf("\"%s\" ", mailname);
299 	fflush(stdout);
300 	if ((obuf = fopen(mailname, "r+")) == NULL) {
301 		perror(mailname);
302 		relsesigs();
303 		reset(0);
304 	}
305 	trunc(obuf);
306 	c = 0;
307 	for (mp = &message[0]; mp < &message[msgCount]; mp++) {
308 		if ((mp->m_flag & MDELETED) != 0)
309 			continue;
310 		c++;
311 		if (send(mp, obuf, (struct ignoretab *) NULL, NOSTR) < 0) {
312 			perror(mailname);
313 			relsesigs();
314 			reset(0);
315 		}
316 	}
317 	gotcha = (c == 0 && ibuf == NULL);
318 	if (ibuf != NULL) {
319 		while ((c = getc(ibuf)) != EOF)
320 			putc(c, obuf);
321 		fclose(ibuf);
322 	}
323 	fflush(obuf);
324 	if (ferror(obuf)) {
325 		perror(mailname);
326 		relsesigs();
327 		reset(0);
328 	}
329 	fclose(obuf);
330 	if (gotcha) {
331 		remove(mailname);
332 		printf("removed\n");
333 	} else
334 		printf("complete\n");
335 	fflush(stdout);
336 
337 done:
338 	relsesigs();
339 }
340 
341 static int sigdepth;		/* depth of holdsigs() */
342 static int omask;
343 /*
344  * Hold signals SIGHUP, SIGINT, and SIGQUIT.
345  */
346 holdsigs()
347 {
348 
349 	if (sigdepth++ == 0)
350 		omask = sigblock(sigmask(SIGHUP)|sigmask(SIGINT)|sigmask(SIGQUIT));
351 }
352 
353 /*
354  * Release signals SIGHUP, SIGINT, and SIGQUIT.
355  */
356 relsesigs()
357 {
358 
359 	if (--sigdepth == 0)
360 		sigsetmask(omask);
361 }
362 
363 /*
364  * Open a temp file by creating and unlinking.
365  * Return the open file descriptor.
366  */
367 opentemp(file)
368 	char file[];
369 {
370 	int f;
371 
372 	if ((f = open(file, O_CREAT|O_EXCL|O_RDWR, 0600)) < 0)
373 		perror(file);
374 	remove(file);
375 	return (f);
376 }
377 
378 /*
379  * Determine the size of the file possessed by
380  * the passed buffer.
381  */
382 off_t
383 fsize(iob)
384 	FILE *iob;
385 {
386 	struct stat sbuf;
387 
388 	if (fstat(fileno(iob), &sbuf) < 0)
389 		return 0;
390 	return sbuf.st_size;
391 }
392 
393 /*
394  * Evaluate the string given as a new mailbox name.
395  * Supported meta characters:
396  *	%	for my system mail box
397  *	%user	for user's system mail box
398  *	#	for previous file
399  *	&	invoker's mbox file
400  *	+file	file in folder directory
401  *	any shell meta character
402  * Return the file name as a dynamic string.
403  */
404 char *
405 expand(name)
406 	register char *name;
407 {
408 	char xname[PATHSIZE];
409 	char cmdbuf[PATHSIZE];		/* also used for file names */
410 	register int pid, l;
411 	register char *cp, *shell;
412 	int pivec[2];
413 	struct stat sbuf;
414 	extern union wait wait_status;
415 
416 	/*
417 	 * The order of evaluation is "%" and "#" expand into constants.
418 	 * "&" can expand into "+".  "+" can expand into shell meta characters.
419 	 * Shell meta characters expand into constants.
420 	 * This way, we make no recursive expansion.
421 	 */
422 	switch (*name) {
423 	case '%':
424 		findmail(name[1] ? name + 1 : myname, xname);
425 		return savestr(xname);
426 	case '#':
427 		if (name[1] != 0)
428 			break;
429 		if (prevfile[0] == 0) {
430 			printf("No previous file\n");
431 			return NOSTR;
432 		}
433 		return savestr(prevfile);
434 	case '&':
435 		if (name[1] == 0 && (name = value("MBOX")) == NOSTR)
436 			name = "~/mbox";
437 		/* fall through */
438 	}
439 	if (name[0] == '+' && getfold(cmdbuf) >= 0) {
440 		sprintf(xname, "%s/%s", cmdbuf, name + 1);
441 		name = savestr(xname);
442 	}
443 	/* catch the most common shell meta character */
444 	if (name[0] == '~' && (name[1] == '/' || name[1] == '\0')) {
445 		sprintf(xname, "%s%s", homedir, name + 1);
446 		name = savestr(xname);
447 	}
448 	if (!anyof(name, "~{[*?$`'\"\\"))
449 		return name;
450 	if (pipe(pivec) < 0) {
451 		perror("pipe");
452 		return name;
453 	}
454 	sprintf(cmdbuf, "echo %s", name);
455 	if ((shell = value("SHELL")) == NOSTR)
456 		shell = SHELL;
457 	pid = start_command(shell, 0, -1, pivec[1], "-c", cmdbuf, NOSTR);
458 	if (pid < 0) {
459 		close(pivec[0]);
460 		close(pivec[1]);
461 		return NOSTR;
462 	}
463 	close(pivec[1]);
464 	l = read(pivec[0], xname, BUFSIZ);
465 	close(pivec[0]);
466 	if (wait_child(pid) < 0 && wait_status.w_termsig != SIGPIPE) {
467 		fprintf(stderr, "\"%s\": Expansion failed.\n", name);
468 		return NOSTR;
469 	}
470 	if (l < 0) {
471 		perror("read");
472 		return NOSTR;
473 	}
474 	if (l == 0) {
475 		fprintf(stderr, "\"%s\": No match.\n", name);
476 		return NOSTR;
477 	}
478 	if (l == BUFSIZ) {
479 		fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name);
480 		return NOSTR;
481 	}
482 	xname[l] = 0;
483 	for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
484 		;
485 	cp[1] = '\0';
486 	if (index(xname, ' ') && stat(xname, &sbuf) < 0) {
487 		fprintf(stderr, "\"%s\": Ambiguous.\n", name);
488 		return NOSTR;
489 	}
490 	return savestr(xname);
491 }
492 
493 /*
494  * Determine the current folder directory name.
495  */
496 getfold(name)
497 	char *name;
498 {
499 	char *folder;
500 
501 	if ((folder = value("folder")) == NOSTR)
502 		return (-1);
503 	if (*folder == '/')
504 		strcpy(name, folder);
505 	else
506 		sprintf(name, "%s/%s", homedir, folder);
507 	return (0);
508 }
509 
510 /*
511  * Return the name of the dead.letter file.
512  */
513 char *
514 getdeadletter()
515 {
516 	register char *cp;
517 
518 	if ((cp = value("DEAD")) == NOSTR || (cp = expand(cp)) == NOSTR)
519 		cp = expand("~/dead.letter");
520 	else if (*cp != '/') {
521 		char buf[PATHSIZE];
522 
523 		(void) sprintf(buf, "~/%s", cp);
524 		cp = expand(buf);
525 	}
526 	return cp;
527 }
528 
529 /*
530  * A nicer version of Fdopen, which allows us to fclose
531  * without losing the open file.
532  */
533 FILE *
534 Fdopen(fildes, mode)
535 	char *mode;
536 {
537 	int f;
538 
539 	if ((f = dup(fildes)) < 0) {
540 		perror("dup");
541 		return (NULL);
542 	}
543 	return fdopen(f, mode);
544 }
545