root/usr/src/cmd/mailx/fio.c
/*
 * 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 2014 Joyent, Inc.
 */

/*
 * 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   */

#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' && is_headline(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, WRDE_NOCMD) != 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" */
}