OpenSolaris_b135/cmd/mailx/fio.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 1999 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

#pragma	ident	"%Z%%M%	%I%	%E% SMI"

#include "rcv.h"
#include <locale.h>
#include <wordexp.h>

/*
 * mailx -- a modified version of a University of California at Berkeley
 *	mail program
 *
 * File I/O.
 */

static int getln(char *line, int max, FILE *f);
static int linecount(char *lp, long size);

/*
 * Set up the input pointers while copying the mail file into
 * /tmp.
 */

void
setptr(register FILE *ibuf)
{
	int n, newline = 1, blankline = 1;
	int StartNewMsg = TRUE;
	int ToldUser = FALSE;
	long clen = -1L;
	int hdr = 0;
	int cflg = 0;			/* found Content-length in header */
	register char *cp;
	register int l;
	register long s;
	off_t offset;
	char linebuf[LINESIZE];
	int inhead, newmail, Odot;
	short flag;

	if (!space) {
		msgCount = 0;
		offset = 0;
		space = 32;
		newmail = 0;
		message =
		    (struct message *)calloc(space, sizeof (struct message));
		if (message == NULL) {
			fprintf(stderr, gettext(
			    "calloc: insufficient memory for %d messages\n"),
			    space);
			exit(1);
			/* NOTREACHED */
		}
		dot = message;
	} else {
		newmail = 1;
		offset = fsize(otf);
	}
	s = 0L;
	l = 0;
	/*
	 * Set default flags.  When reading from
	 * a folder, assume the message has been
	 * previously read.
	 */
	if (edit)
		flag = MUSED|MREAD;
	else
		flag = MUSED|MNEW;

	inhead = 0;
	while ((n = getln(linebuf, sizeof (linebuf), ibuf)) > 0) {
		if (!newline) {
			goto putout;
		}
	top:
		hdr = inhead && (headerp(linebuf) ||
		    (linebuf[0] == ' ' || linebuf[0] == '\t'));
		if (!hdr && cflg) {	/* nonheader, Content-length seen */
			if (clen > 0 && clen < n) {	/* read too much */
				/*
				 * NB: this only can happen if there is a
				 * small content that is NOT \n terminated
				 * and has no leading blank line, i.e., never.
				 */
				if (fwrite(linebuf, 1, (int)clen, otf) !=
					clen) {
					fclose(ibuf);
					fflush(otf);
				} else {
					l += linecount(linebuf, clen);
				}
				offset += clen;
				s += clen;
				n -= (int)clen;
				/* shift line to the left, copy null as well */
				memcpy(linebuf, linebuf+clen, n+1);
				cflg = 0;
				message[msgCount-1].m_clen = clen + 1;
				blankline = 1;
				StartNewMsg = TRUE;
				goto top;
			}
			/* here, clen == 0 or clen >= n */
			if (n == 1 && linebuf[0] == '\n') {
				/* leading empty line */
				clen++;		/* cheat */
				inhead = 0;
			}
			offset += clen;
			s += (long)clen;
			message[msgCount-1].m_clen = clen;
			for (;;) {
				if (fwrite(linebuf, 1, n, otf) != n) {
					fclose(ibuf);
					fflush(otf);
				} else {
					l += linecount(linebuf, n);
				}
				clen -= n;
				if (clen <= 0) {
					break;
				}
				n = clen < sizeof (linebuf) ?
				    (int)clen : (int)sizeof (linebuf);
				if ((n = fread(linebuf, 1, n, ibuf)) <= 0) {
					fprintf(stderr, gettext(
			"%s:\tYour mailfile was found to be corrupted.\n"),
					    progname);
					fprintf(stderr, gettext(
					    "\t(Unexpected end-of-file).\n"));
					fprintf(stderr, gettext(
					"\tMessage #%d may be truncated.\n\n"),
					    msgCount);
					offset -= clen;
					s -= clen;
					clen = 0; /* stop the loop */
				}
			}
			/* All done, go to top for next message */
			cflg = 0;
			blankline = 1;
			StartNewMsg = TRUE;
			continue;
		}

		/* Look for a From line that starts a new message */
		if (blankline && linebuf[0] == 'F' && ishead(linebuf)) {
			if (msgCount > 0 && !newmail) {
				message[msgCount-1].m_size = s;
				message[msgCount-1].m_lines = l;
				message[msgCount-1].m_flag = flag;
			}
			if (msgCount >= space) {
				/*
				 * Limit the speed at which the
				 * allocated space grows.
				 */
				if (space < 512)
					space = space*2;
				else
					space += 512;
				errno = 0;
				Odot = dot - &(message[0]);
				message = (struct message *)
				    realloc(message,
					space*(sizeof (struct message)));
				if (message == NULL) {
					perror("realloc failed");
					fprintf(stderr, gettext(
			"realloc: insufficient memory for %d messages\n"),
					    space);
					exit(1);
				}
				dot = &message[Odot];
			}
			message[msgCount].m_offset = offset;
			message[msgCount].m_text = TRUE;
			message[msgCount].m_clen = 0;
			newmail = 0;
			msgCount++;
			if (edit)
				flag = MUSED|MREAD;
			else
				flag = MUSED|MNEW;
			inhead = 1;
			s = 0L;
			l = 0;
			StartNewMsg = FALSE;
			ToldUser = FALSE;
			goto putout;
		}

		/* if didn't get a header line, we're no longer in the header */
		if (!hdr)
			inhead = 0;
		if (!inhead)
			goto putout;

		/*
		 * Look for Status: line.  Do quick check for second character,
		 * many headers start with "S" but few have "t" as second char.
		 */
		if ((linebuf[1] == 't' || linebuf[1] == 'T') &&
		    ishfield(linebuf, "status")) {
			cp = hcontents(linebuf);
			flag = MUSED|MNEW;
			if (strchr(cp, 'R'))
				flag |= MREAD;
			if (strchr(cp, 'O'))
				flag &= ~MNEW;
		}
		/*
		 * Look for Content-Length and Content-Type headers.  Like
		 * above, do a quick check for the "-", which is rare.
		 */
		if (linebuf[7] == '-') {
			if (ishfield(linebuf, "content-length")) {
				if (!cflg) {
					clen = atol(hcontents(linebuf));
					cflg = clen >= 0;
				}
			} else if (ishfield(linebuf, "content-type")) {
				char word[LINESIZE];
				char *cp2;

				cp = hcontents(linebuf);
				cp2 = word;
				while (!isspace(*cp))
					*cp2++ = *cp++;
				*cp2 = '\0';
				if (icequal(word, "binary"))
					message[msgCount-1].m_text = FALSE;
			}
		}
putout:
		offset += n;
		s += (long)n;
		if (fwrite(linebuf, 1, n, otf) != n) {
			fclose(ibuf);
			fflush(otf);
		} else {
			l++;
		}
		if (ferror(otf)) {
			perror("/tmp");
			exit(1);
		}
		if (msgCount == 0) {
			fclose(ibuf);
			fflush(otf);
		}
		if (linebuf[n-1] == '\n') {
			blankline = newline && n == 1;
			newline = 1;
			if (n == 1) {
				/* Blank line. Skip StartNewMsg check below */
				continue;
			}
		} else {
			newline = 0;
		}
		if (StartNewMsg && !ToldUser) {
			fprintf(stderr, gettext(
			    "%s:\tYour mailfile was found to be corrupted\n"),
			    progname);
			fprintf(stderr,
			    gettext("\t(Content-length mismatch).\n"));
			fprintf(stderr, gettext(
			    "\tMessage #%d may be truncated,\n"), msgCount);
			fprintf(stderr, gettext(
			    "\twith another message concatenated to it.\n\n"));
			ToldUser = TRUE;
		}
	}

	if (n == 0) {
		fflush(otf);
		if (fferror(otf)) {
			perror("/tmp");
			exit(1);
		}
		if (msgCount) {
			message[msgCount-1].m_size = s;
			message[msgCount-1].m_lines = l;
			message[msgCount-1].m_flag = flag;
		}
		fclose(ibuf);
	}
}

/*
 * Compute the content length of a message and set it into m_clen.
 */

void
setclen(register struct message *mp)
{
	long c;
	FILE *ibuf;
	char line[LINESIZE];
	int fline, nread;

	ibuf = setinput(mp);
	c = mp->m_size;
	fline = 1;
	while (c > 0L) {
		nread = getln(line, sizeof (line), ibuf);
		c -= nread;
		/*
		 * First line is the From line, so no headers
		 * there to worry about.
		 */
		if (fline) {
			fline = 0;
			continue;
		}
		/*
		 * If line is blank, we've reached end of headers.
		 */
		if (line[0] == '\n')
			break;
		/*
		 * If this line is a continuation
		 * of a previous header field, keep going.
		 */
		if (isspace(line[0]))
			continue;
		/*
		 * If we are no longer looking at real
		 * header lines, we're done.
		 * This happens in uucp style mail where
		 * there are no headers at all.
		 */
		if (!headerp(line)) {
			c += nread;
			break;
		}
	}
	if (c == 0)
		c = 1;
	mp->m_clen = c;
}

static int
getln(char *line, int max, FILE *f)
{
	register int c;
	register char *cp, *ecp;

	cp = line;
	ecp = cp + max - 1;
	while (cp < ecp && (c = getc(f)) != EOF)
		if ((*cp++ = (char)c) == '\n')
			break;
	*cp = '\0';
	return (cp - line);
}

/*
 * Read up a line from the specified input into the line
 * buffer.  Return the number of characters read.  Do not
 * include the newline at the end.
 */

int
readline(FILE *ibuf, char *linebuf)
{
	register char *cp;
	register int c;
	int seennulls = 0;

	clearerr(ibuf);
	c = getc(ibuf);
	for (cp = linebuf; c != '\n' && c != EOF; c = getc(ibuf)) {
		if (c == 0) {
			if (!seennulls) {
				fprintf(stderr,
				    gettext("mailx: NUL changed to @\n"));
				seennulls++;
			}
			c = '@';
		}
		if (cp - linebuf < LINESIZE-2)
			*cp++ = (char)c;
	}
	*cp = 0;
	if (c == EOF && cp == linebuf)
		return (0);
	return (cp - linebuf + 1);
}

/*
 * linecount - determine the number of lines in a printable file.
 */

static int
linecount(char *lp, long size)
{
	register char *cp, *ecp;
	register int count;

	count = 0;
	cp = lp;
	ecp = cp + size;
	while (cp < ecp)
		if (*cp++ == '\n')
			count++;
	return (count);
}

/*
 * Return a file buffer all ready to read up the
 * passed message pointer.
 */

FILE *
setinput(register struct message *mp)
{
	fflush(otf);
	if (fseek(itf, mp->m_offset, 0) < 0) {
		perror("fseek");
		panic("temporary file seek");
	}
	return (itf);
}


/*
 * Delete a file, but only if the file is a plain file.
 */

int
removefile(char name[])
{
	struct stat statb;
	extern int errno;

	if (stat(name, &statb) < 0)
		if (errno == ENOENT)
			return (0);	/* it's already gone, no error */
		else
			return (-1);
	if ((statb.st_mode & S_IFMT) != S_IFREG) {
		errno = EISDIR;
		return (-1);
	}
	return (unlink(name));
}

/*
 * Terminate an editing session by attempting to write out the user's
 * file from the temporary.  Save any new stuff appended to the file.
 */
int
edstop(
    int noremove	/* don't allow the file to be removed, trunc instead */
)
{
	register int gotcha, c;
	register struct message *mp;
	FILE *obuf, *ibuf, *tbuf = 0, *readstat;
	struct stat statb;
	char tempname[STSIZ], *id;
	int tmpfd = -1;

	if (readonly)
		return (0);
	holdsigs();
	if (Tflag != NOSTR) {
		if ((readstat = fopen(Tflag, "w")) == NULL)
			Tflag = NOSTR;
	}
	for (mp = &message[0], gotcha = 0; mp < &message[msgCount]; mp++) {
		if (mp->m_flag & MNEW) {
			mp->m_flag &= ~MNEW;
			mp->m_flag |= MSTATUS;
		}
		if (mp->m_flag & (MODIFY|MDELETED|MSTATUS))
			gotcha++;
		if (Tflag != NOSTR && (mp->m_flag & (MREAD|MDELETED)) != 0) {
			if ((id = hfield("article-id", mp, addone)) != NOSTR)
				fprintf(readstat, "%s\n", id);
		}
	}
	if (Tflag != NOSTR)
		fclose(readstat);
	if (!gotcha || Tflag != NOSTR)
		goto done;
	if ((ibuf = fopen(editfile, "r+")) == NULL) {
		perror(editfile);
		relsesigs();
		longjmp(srbuf, 1);
	}
	lock(ibuf, "r+", 1);
	if (fstat(fileno(ibuf), &statb) >= 0 && statb.st_size > mailsize) {
		nstrcpy(tempname, STSIZ, "/tmp/mboxXXXXXX");
		if ((tmpfd = mkstemp(tempname)) == -1) {
			perror(tempname);
			fclose(ibuf);
			relsesigs();
			longjmp(srbuf, 1);
		}
		if ((obuf = fdopen(tmpfd, "w")) == NULL) {
			perror(tempname);
			fclose(ibuf);
			removefile(tempname);
			relsesigs();
			(void) close(tmpfd);
			longjmp(srbuf, 1);
		}
		fseek(ibuf, mailsize, 0);
		while ((c = getc(ibuf)) != EOF)
			putc(c, obuf);
		fclose(obuf);
		if ((tbuf = fopen(tempname, "r")) == NULL) {
			perror(tempname);
			fclose(ibuf);
			removefile(tempname);
			relsesigs();
			longjmp(srbuf, 1);
		}
		removefile(tempname);
	}
	if ((obuf = fopen(editfile, "r+")) == NULL) {
		if ((obuf = fopen(editfile, "w")) == NULL) {
			perror(editfile);
			fclose(ibuf);
			if (tbuf)
				fclose(tbuf);
			relsesigs();
			longjmp(srbuf, 1);
		}
	}
	printf("\"%s\" ", editfile);
	flush();
	c = 0;
	for (mp = &message[0]; mp < &message[msgCount]; mp++) {
		if ((mp->m_flag & MDELETED) != 0)
			continue;
		c++;
		if (msend(mp, obuf, 0, fputs) < 0) {
			perror(editfile);
			fclose(ibuf);
			fclose(obuf);
			if (tbuf)
				fclose(tbuf);
			relsesigs();
			longjmp(srbuf, 1);
		}
	}
	gotcha = (c == 0 && tbuf == NULL);
	if (tbuf != NULL) {
		while ((c = getc(tbuf)) != EOF)
			putc(c, obuf);
		fclose(tbuf);
	}
	fflush(obuf);
	if (fferror(obuf)) {
		perror(editfile);
		fclose(ibuf);
		fclose(obuf);
		relsesigs();
		longjmp(srbuf, 1);
	}
	if (gotcha && !noremove && (value("keep") == NOSTR)) {
		removefile(editfile);
		printf(gettext("removed.\n"));
	}
	else
		printf(gettext("updated.\n"));
	fclose(ibuf);
	trunc(obuf);
	fclose(obuf);
	flush();

done:
	relsesigs();
	return (1);
}

#ifndef OLD_BSD_SIGS
static int sigdepth = 0;		/* depth of holdsigs() */
#ifdef VMUNIX
static int omask = 0;
#else
static	sigset_t mask, omask;
#endif
#endif
/*
 * Hold signals SIGHUP - SIGQUIT.
 */
void
holdsigs(void)
{
#ifndef OLD_BSD_SIGS
	if (sigdepth++ == 0) {
#ifdef VMUNIX
		omask = sigblock(sigmask(SIGHUP) |
		    sigmask(SIGINT)|sigmask(SIGQUIT));
#else
		sigemptyset(&mask);
		sigaddset(&mask, SIGHUP);
		sigaddset(&mask, SIGINT);
		sigaddset(&mask, SIGQUIT);
		sigprocmask(SIG_BLOCK, &mask, &omask);
#endif
	}
#else
	sighold(SIGHUP);
	sighold(SIGINT);
	sighold(SIGQUIT);
#endif
}

/*
 * Release signals SIGHUP - SIGQUIT
 */
void
relsesigs(void)
{
#ifndef OLD_BSD_SIGS
	if (--sigdepth == 0)
#ifdef VMUNIX
		sigsetmask(omask);
#else
		sigprocmask(SIG_SETMASK, &omask, NULL);
#endif
#else
	sigrelse(SIGHUP);
	sigrelse(SIGINT);
	sigrelse(SIGQUIT);
#endif
}

#if !defined(OLD_BSD_SIGS) && !defined(VMUNIX)
void
(*sigset(int sig, void (*act)(int)))(int)
{
	struct sigaction sa, osa;

	sa.sa_handler = (void (*)())act;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if (sigaction(sig, &sa, &osa) < 0)
		return ((void (*)(int))-1);
	return ((void (*)(int))osa.sa_handler);
}
#endif

/*
 * Flush the standard output.
 */

void
flush(void)
{
	fflush(stdout);
	fflush(stderr);
}

/*
 * Determine the size of the file possessed by
 * the passed buffer.
 */

off_t
fsize(FILE *iob)
{
	register int f;
	struct stat sbuf;

	f = fileno(iob);
	if (fstat(f, &sbuf) < 0)
		return (0);
	return (sbuf.st_size);
}

/*
 * Check for either a stdio recognized error, or
 * a possibly delayed write error (in case it's
 * an NFS file, for instance).
 */

int
fferror(FILE *iob)
{
	return (ferror(iob) || fsync(fileno(iob)) < 0);
}

/*
 * Take a file name, possibly with shell meta characters
 * in it and expand it by using wordexp().
 * Return the file name as a dynamic string.
 * If the name cannot be expanded (for whatever reason)
 * return NULL.
 */

char *
expand(char *name)
{
	char xname[BUFSIZ];
	char foldbuf[BUFSIZ];
	register char *cp;
	register int l;
	wordexp_t wrdexp_buf;

	if (debug) fprintf(stderr, "expand(%s)=", name);
	cp = strchr(name, '\0') - 1;	/* pointer to last char of name */
	if (isspace(*cp)) {
		/* strip off trailing blanks */
		while (cp > name && isspace(*cp))
			cp--;
		l = *++cp;	/* save char */
		*cp = '\0';
		name = savestr(name);
		*cp = (char)l;	/* restore char */
	}
	if (name[0] == '+' && getfold(foldbuf) >= 0) {
		snprintf(xname, sizeof (xname), "%s/%s", foldbuf, name + 1);
		cp = safeexpand(savestr(xname));
		if (debug) fprintf(stderr, "%s\n", cp);
		return (cp);
	}
	if (!anyof(name, "~{[*?$`'\"\\")) {
		if (debug) fprintf(stderr, "%s\n", name);
		return (name);
	}
	if (wordexp(name, &wrdexp_buf, 0) != 0) {
		fprintf(stderr, gettext("Syntax error in \"%s\"\n"), name);
		fflush(stderr);
		return (NOSTR);
	}
	if (wrdexp_buf.we_wordc > 1) {
		fprintf(stderr, gettext("\"%s\": Ambiguous\n"), name);
		fflush(stderr);
		return (NOSTR);
	}
	if (debug) fprintf(stderr, "%s\n", wrdexp_buf.we_wordv[0]);
	return (savestr(wrdexp_buf.we_wordv[0]));
}

/*
 * Take a file name, possibly with shell meta characters
 * in it and expand it by using "sh -c echo filename"
 * Return the file name as a dynamic string.
 * If the name cannot be expanded (for whatever reason)
 * return the original file name.
 */

char *
safeexpand(char name[])
{
	char *t = expand(name);
	return (t) ? t : savestr(name);
}

/*
 * Determine the current folder directory name.
 */
int
getfold(char *name)
{
	char *folder;

	if ((folder = value("folder")) == NOSTR || *folder == '\0')
		return (-1);
	/*
	 * If name looks like a folder name, don't try
	 * to expand it, to prevent infinite recursion.
	 */
	if (*folder != '+' && (folder = expand(folder)) == NOSTR ||
	    *folder == '\0')
		return (-1);
	if (*folder == '/') {
		nstrcpy(name, BUFSIZ, folder);
	} else
		snprintf(name, BUFSIZ, "%s/%s", homedir, folder);
	return (0);
}

/*
 * A nicer version of Fdopen, which allows us to fclose
 * without losing the open file.
 */

FILE *
Fdopen(int fildes, char *mode)
{
	register int f;

	f = dup(fildes);
	if (f < 0) {
		perror("dup");
		return (NULL);
	}
	return (fdopen(f, mode));
}

/*
 * return the filename associated with "s".  This function always
 * returns a non-null string (no error checking is done on the receiving end)
 */
char *
Getf(register char *s)
{
	register char *cp;
	static char defbuf[PATHSIZE];

	if (((cp = value(s)) != 0) && *cp) {
		return (safeexpand(cp));
	} else if (strcmp(s, "MBOX") == 0) {
		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
			"mbox");
		return (defbuf);
	} else if (strcmp(s, "DEAD") == 0) {
		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
			"dead.letter");
		return (defbuf);
	} else if (strcmp(s, "MAILRC") == 0) {
		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
			".mailrc");
		return (defbuf);
	} else if (strcmp(s, "HOME") == 0) {
		/* no recursion allowed! */
		return (".");
	}
	return ("DEAD");	/* "cannot happen" */
}