root/usr/src/cmd/sendmail/src/bf.c
/*
 * Copyright (c) 1999-2002, 2004, 2006 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 * Contributed by Exactis.com, Inc.
 *
 */

/*
**  This is in transition. Changed from the original bf_torek.c code
**  to use sm_io function calls directly rather than through stdio
**  translation layer. Will be made a built-in file type of libsm
**  next (once safeopen() linkable from libsm).
*/

#include <sm/gen.h>
SM_RCSID("@(#)$Id: bf.c,v 8.62 2006/03/31 18:45:56 ca Exp $")

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "sendmail.h"
#include "bf.h"

#include <syslog.h>

/* bf io functions */
static ssize_t  sm_bfread __P((SM_FILE_T *, char *, size_t));
static ssize_t  sm_bfwrite __P((SM_FILE_T *, const char *, size_t));
static off_t    sm_bfseek __P((SM_FILE_T *, off_t, int));
static int      sm_bfclose __P((SM_FILE_T *));
static int      sm_bfcommit __P((SM_FILE_T *));
static int      sm_bftruncate __P((SM_FILE_T *));

static int      sm_bfopen __P((SM_FILE_T *, const void *, int, const void *));
static int      sm_bfsetinfo __P((SM_FILE_T *, int , void *));
static int      sm_bfgetinfo __P((SM_FILE_T *, int , void *));

/*
**  Data structure for storing information about each buffered file
**  (Originally in sendmail/bf_torek.h for the curious.)
*/

struct bf
{
        bool    bf_committed;   /* Has this buffered file been committed? */
        bool    bf_ondisk;      /* On disk: committed or buffer overflow */
        long    bf_flags;
        int     bf_disk_fd;     /* If on disk, associated file descriptor */
        char    *bf_buf;        /* Memory buffer */
        int     bf_bufsize;     /* Length of above buffer */
        int     bf_buffilled;   /* Bytes of buffer actually filled */
        char    *bf_filename;   /* Name of buffered file, if ever committed */
        MODE_T  bf_filemode;    /* Mode of buffered file, if ever committed */
        off_t   bf_offset;      /* Currect file offset */
        int     bf_size;        /* Total current size of file */
};

#ifdef BF_STANDALONE
# define OPEN(fn, omode, cmode, sff) open(fn, omode, cmode)
#else /* BF_STANDALONE */
# define OPEN(fn, omode, cmode, sff) safeopen(fn, omode, cmode, sff)
#endif /* BF_STANDALONE */

struct bf_info
{
        char    *bi_filename;
        MODE_T  bi_fmode;
        size_t  bi_bsize;
        long    bi_flags;
};

/*
**  SM_BFOPEN -- the "base" open function called by sm_io_open() for the
**              internal, file-type-specific info setup.
**
**      Parameters:
**              fp -- file pointer being filled-in for file being open'd
**              info -- information about file being opened
**              flags -- ignored
**              rpool -- ignored (currently)
**
**      Returns:
**              Failure: -1 and sets errno
**              Success: 0 (zero)
*/

static int
sm_bfopen(fp, info, flags, rpool)
        SM_FILE_T *fp;
        const void *info;
        int flags;
        const void *rpool;
{
        char *filename;
        MODE_T fmode;
        size_t bsize;
        long sflags;
        struct bf *bfp;
        int l;
        struct stat st;

        filename = ((struct bf_info *) info)->bi_filename;
        fmode = ((struct bf_info *) info)->bi_fmode;
        bsize = ((struct bf_info *) info)->bi_bsize;
        sflags = ((struct bf_info *) info)->bi_flags;

        /* Sanity checks */
        if (*filename == '\0')
        {
                /* Empty filename string */
                errno = ENOENT;
                return -1;
        }
        if (stat(filename, &st) == 0)
        {
                /* File already exists on disk */
                errno = EEXIST;
                return -1;
        }

        /* Allocate memory */
        bfp = (struct bf *) sm_malloc(sizeof(struct bf));
        if (bfp == NULL)
        {
                errno = ENOMEM;
                return -1;
        }

        /* Assign data buffer */
        /* A zero bsize is valid, just don't allocate memory */
        if (bsize > 0)
        {
                bfp->bf_buf = (char *) sm_malloc(bsize);
                if (bfp->bf_buf == NULL)
                {
                        bfp->bf_bufsize = 0;
                        sm_free(bfp);
                        errno = ENOMEM;
                        return -1;
                }
        }
        else
                bfp->bf_buf = NULL;

        /* Nearly home free, just set all the parameters now */
        bfp->bf_committed = false;
        bfp->bf_ondisk = false;
        bfp->bf_flags = sflags;
        bfp->bf_bufsize = bsize;
        bfp->bf_buffilled = 0;
        l = strlen(filename) + 1;
        bfp->bf_filename = (char *) sm_malloc(l);
        if (bfp->bf_filename == NULL)
        {
                if (bfp->bf_buf != NULL)
                        sm_free(bfp->bf_buf);
                sm_free(bfp);
                errno = ENOMEM;
                return -1;
        }
        (void) sm_strlcpy(bfp->bf_filename, filename, l);
        bfp->bf_filemode = fmode;
        bfp->bf_offset = 0;
        bfp->bf_size = 0;
        bfp->bf_disk_fd = -1;
        fp->f_cookie = bfp;

        if (tTd(58, 8))
                sm_dprintf("sm_bfopen(%s)\n", filename);

        return 0;
}

/*
**  BFOPEN -- create a new buffered file
**
**      Parameters:
**              filename -- the file's name
**              fmode -- what mode the file should be created as
**              bsize -- amount of buffer space to allocate (may be 0)
**              flags -- if running under sendmail, passed directly to safeopen
**
**      Returns:
**              a SM_FILE_T * which may then be used with stdio functions,
**              or NULL on failure. SM_FILE_T * is opened for writing
**              "SM_IO_WHAT_VECTORS").
**
**      Side Effects:
**              none.
**
**      Sets errno:
**              any value of errno specified by sm_io_setinfo_type()
**              any value of errno specified by sm_io_open()
**              any value of errno specified by sm_io_setinfo()
*/

#ifdef __STDC__
/*
**  XXX This is a temporary hack since MODE_T on HP-UX 10.x is short.
**      If we use K&R here, the compiler will complain about
**      Inconsistent parameter list declaration
**      due to the change from short to int.
*/

SM_FILE_T *
bfopen(char *filename, MODE_T fmode, size_t bsize, long flags)
#else /* __STDC__ */
SM_FILE_T *
bfopen(filename, fmode, bsize, flags)
        char *filename;
        MODE_T fmode;
        size_t bsize;
        long flags;
#endif /* __STDC__ */
{
        MODE_T omask;
        SM_FILE_T SM_IO_SET_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose,
                sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo,
                SM_TIME_FOREVER);
        struct bf_info info;

        /*
        **  Apply current umask to fmode as it may change by the time
        **  the file is actually created.  fmode becomes the true
        **  permissions of the file, which OPEN() must obey.
        */

        omask = umask(0);
        fmode &= ~omask;
        (void) umask(omask);

        SM_IO_INIT_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose,
                sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo,
                SM_TIME_FOREVER);
        info.bi_filename = filename;
        info.bi_fmode = fmode;
        info.bi_bsize = bsize;
        info.bi_flags = flags;

        return sm_io_open(&vector, SM_TIME_DEFAULT, &info, SM_IO_RDWR, NULL);
}

/*
**  SM_BFGETINFO -- returns info about an open file pointer
**
**      Parameters:
**              fp -- file pointer to get info about
**              what -- type of info to obtain
**              valp -- thing to return the info in
*/

static int
sm_bfgetinfo(fp, what, valp)
        SM_FILE_T *fp;
        int what;
        void *valp;
{
        struct bf *bfp;

        bfp = (struct bf *) fp->f_cookie;
        switch (what)
        {
          case SM_IO_WHAT_FD:
                return bfp->bf_disk_fd;
          case SM_IO_WHAT_SIZE:
                return bfp->bf_size;
          default:
                return -1;
        }
}

/*
**  SM_BFCLOSE -- close a buffered file
**
**      Parameters:
**              fp -- cookie of file to close
**
**      Returns:
**              0 to indicate success
**
**      Side Effects:
**              deletes backing file, sm_frees memory.
**
**      Sets errno:
**              never.
*/

static int
sm_bfclose(fp)
        SM_FILE_T *fp;
{
        struct bf *bfp;

        /* Cast cookie back to correct type */
        bfp = (struct bf *) fp->f_cookie;

        /* Need to clean up the file */
        if (bfp->bf_ondisk && !bfp->bf_committed)
                unlink(bfp->bf_filename);
        sm_free(bfp->bf_filename);

        if (bfp->bf_disk_fd != -1)
                close(bfp->bf_disk_fd);

        /* Need to sm_free the buffer */
        if (bfp->bf_bufsize > 0)
                sm_free(bfp->bf_buf);

        /* Finally, sm_free the structure */
        sm_free(bfp);
        return 0;
}

/*
**  SM_BFREAD -- read a buffered file
**
**      Parameters:
**              cookie -- cookie of file to read
**              buf -- buffer to fill
**              nbytes -- how many bytes to read
**
**      Returns:
**              number of bytes read or -1 indicate failure
**
**      Side Effects:
**              none.
**
*/

static ssize_t
sm_bfread(fp, buf, nbytes)
        SM_FILE_T *fp;
        char *buf;
        size_t nbytes;
{
        struct bf *bfp;
        ssize_t count = 0;      /* Number of bytes put in buf so far */
        int retval;

        /* Cast cookie back to correct type */
        bfp = (struct bf *) fp->f_cookie;

        if (bfp->bf_offset < bfp->bf_buffilled)
        {
                /* Need to grab some from buffer */
                count = nbytes;
                if ((bfp->bf_offset + count) > bfp->bf_buffilled)
                        count = bfp->bf_buffilled - bfp->bf_offset;

                memcpy(buf, bfp->bf_buf + bfp->bf_offset, count);
        }

        if ((bfp->bf_offset + nbytes) > bfp->bf_buffilled)
        {
                /* Need to grab some from file */
                if (!bfp->bf_ondisk)
                {
                        /* Oops, the file doesn't exist. EOF. */
                        if (tTd(58, 8))
                                sm_dprintf("sm_bfread(%s): to disk\n",
                                           bfp->bf_filename);
                        goto finished;
                }

                /* Catch a read() on an earlier failed write to disk */
                if (bfp->bf_disk_fd < 0)
                {
                        errno = EIO;
                        return -1;
                }

                if (lseek(bfp->bf_disk_fd,
                          bfp->bf_offset + count, SEEK_SET) < 0)
                {
                        if ((errno == EINVAL) || (errno == ESPIPE))
                        {
                                /*
                                **  stdio won't be expecting these
                                **  errnos from read()! Change them
                                **  into something it can understand.
                                */

                                errno = EIO;
                        }
                        return -1;
                }

                while (count < nbytes)
                {
                        retval = read(bfp->bf_disk_fd,
                                      buf + count,
                                      nbytes - count);
                        if (retval < 0)
                        {
                                /* errno is set implicitly by read() */
                                return -1;
                        }
                        else if (retval == 0)
                                goto finished;
                        else
                                count += retval;
                }
        }

finished:
        bfp->bf_offset += count;
        return count;
}

/*
**  SM_BFSEEK -- seek to a position in a buffered file
**
**      Parameters:
**              fp     -- fp of file to seek
**              offset -- position to seek to
**              whence -- how to seek
**
**      Returns:
**              new file offset or -1 indicate failure
**
**      Side Effects:
**              none.
**
*/

static off_t
sm_bfseek(fp, offset, whence)
        SM_FILE_T *fp;
        off_t offset;
        int whence;

{
        struct bf *bfp;

        /* Cast cookie back to correct type */
        bfp = (struct bf *) fp->f_cookie;

        switch (whence)
        {
          case SEEK_SET:
                bfp->bf_offset = offset;
                break;

          case SEEK_CUR:
                bfp->bf_offset += offset;
                break;

          case SEEK_END:
                bfp->bf_offset = bfp->bf_size + offset;
                break;

          default:
                errno = EINVAL;
                return -1;
        }
        return bfp->bf_offset;
}

/*
**  SM_BFWRITE -- write to a buffered file
**
**      Parameters:
**              fp -- fp of file to write
**              buf -- data buffer
**              nbytes -- how many bytes to write
**
**      Returns:
**              number of bytes written or -1 indicate failure
**
**      Side Effects:
**              may create backing file if over memory limit for file.
**
*/

static ssize_t
sm_bfwrite(fp, buf, nbytes)
        SM_FILE_T *fp;
        const char *buf;
        size_t nbytes;
{
        struct bf *bfp;
        ssize_t count = 0;      /* Number of bytes written so far */
        int retval;

        /* Cast cookie back to correct type */
        bfp = (struct bf *) fp->f_cookie;

        /* If committed, go straight to disk */
        if (bfp->bf_committed)
        {
                if (lseek(bfp->bf_disk_fd, bfp->bf_offset, SEEK_SET) < 0)
                {
                        if ((errno == EINVAL) || (errno == ESPIPE))
                        {
                                /*
                                **  stdio won't be expecting these
                                **  errnos from write()! Change them
                                **  into something it can understand.
                                */

                                errno = EIO;
                        }
                        return -1;
                }

                count = write(bfp->bf_disk_fd, buf, nbytes);
                if (count < 0)
                {
                        /* errno is set implicitly by write() */
                        return -1;
                }
                goto finished;
        }

        if (bfp->bf_offset < bfp->bf_bufsize)
        {
                /* Need to put some in buffer */
                count = nbytes;
                if ((bfp->bf_offset + count) > bfp->bf_bufsize)
                        count = bfp->bf_bufsize - bfp->bf_offset;

                memcpy(bfp->bf_buf + bfp->bf_offset, buf, count);
                if ((bfp->bf_offset + count) > bfp->bf_buffilled)
                        bfp->bf_buffilled = bfp->bf_offset + count;
        }

        if ((bfp->bf_offset + nbytes) > bfp->bf_bufsize)
        {
                /* Need to put some in file */
                if (!bfp->bf_ondisk)
                {
                        MODE_T omask;
                        int save_errno;

                        /* Clear umask as bf_filemode are the true perms */
                        omask = umask(0);
                        retval = OPEN(bfp->bf_filename,
                                      O_RDWR | O_CREAT | O_TRUNC | QF_O_EXTRA,
                                      bfp->bf_filemode, bfp->bf_flags);
                        save_errno = errno;
                        (void) umask(omask);
                        errno = save_errno;

                        /* Couldn't create file: failure */
                        if (retval < 0)
                        {
                                /*
                                **  stdio may not be expecting these
                                **  errnos from write()! Change to
                                **  something which it can understand.
                                **  Note that ENOSPC and EDQUOT are saved
                                **  because they are actually valid for
                                **  write().
                                */

                                if (!(errno == ENOSPC
#ifdef EDQUOT
                                      || errno == EDQUOT
#endif /* EDQUOT */
                                     ))
                                        errno = EIO;

                                return -1;
                        }
                        bfp->bf_disk_fd = retval;
                        bfp->bf_ondisk = true;
                }

                /* Catch a write() on an earlier failed write to disk */
                if (bfp->bf_ondisk && bfp->bf_disk_fd < 0)
                {
                        errno = EIO;
                        return -1;
                }

                if (lseek(bfp->bf_disk_fd,
                          bfp->bf_offset + count, SEEK_SET) < 0)
                {
                        if ((errno == EINVAL) || (errno == ESPIPE))
                        {
                                /*
                                **  stdio won't be expecting these
                                **  errnos from write()! Change them into
                                **  something which it can understand.
                                */

                                errno = EIO;
                        }
                        return -1;
                }

                while (count < nbytes)
                {
                        retval = write(bfp->bf_disk_fd, buf + count,
                                       nbytes - count);
                        if (retval < 0)
                        {
                                /* errno is set implicitly by write() */
                                return -1;
                        }
                        else
                                count += retval;
                }
        }

finished:
        bfp->bf_offset += count;
        if (bfp->bf_offset > bfp->bf_size)
                bfp->bf_size = bfp->bf_offset;
        return count;
}

/*
**  BFREWIND -- rewinds the SM_FILE_T *
**
**      Parameters:
**              fp -- SM_FILE_T * to rewind
**
**      Returns:
**              0 on success, -1 on error
**
**      Side Effects:
**              rewinds the SM_FILE_T * and puts it into read mode. Normally
**              one would bfopen() a file, write to it, then bfrewind() and
**              fread(). If fp is not a buffered file, this is equivalent to
**              rewind().
**
**      Sets errno:
**              any value of errno specified by sm_io_rewind()
*/

int
bfrewind(fp)
        SM_FILE_T *fp;
{
        (void) sm_io_flush(fp, SM_TIME_DEFAULT);
        sm_io_clearerr(fp); /* quicker just to do it */
        return sm_io_seek(fp, SM_TIME_DEFAULT, 0, SM_IO_SEEK_SET);
}

/*
**  SM_BFCOMMIT -- "commits" the buffered file
**
**      Parameters:
**              fp -- SM_FILE_T * to commit to disk
**
**      Returns:
**              0 on success, -1 on error
**
**      Side Effects:
**              Forces the given SM_FILE_T * to be written to disk if it is not
**              already, and ensures that it will be kept after closing. If
**              fp is not a buffered file, this is a no-op.
**
**      Sets errno:
**              any value of errno specified by open()
**              any value of errno specified by write()
**              any value of errno specified by lseek()
*/

static int
sm_bfcommit(fp)
        SM_FILE_T *fp;
{
        struct bf *bfp;
        int retval;
        int byteswritten;

        /* Get associated bf structure */
        bfp = (struct bf *) fp->f_cookie;

        /* If already committed, noop */
        if (bfp->bf_committed)
                return 0;

        /* Do we need to open a file? */
        if (!bfp->bf_ondisk)
        {
                int save_errno;
                MODE_T omask;
                struct stat st;

                if (tTd(58, 8))
                {
                        sm_dprintf("bfcommit(%s): to disk\n", bfp->bf_filename);
                        if (tTd(58, 32))
                                sm_dprintf("bfcommit(): filemode %o flags %ld\n",
                                           bfp->bf_filemode, bfp->bf_flags);
                }

                if (stat(bfp->bf_filename, &st) == 0)
                {
                        errno = EEXIST;
                        return -1;
                }

                /* Clear umask as bf_filemode are the true perms */
                omask = umask(0);
                retval = OPEN(bfp->bf_filename,
                              O_RDWR | O_CREAT | O_EXCL | QF_O_EXTRA,
                              bfp->bf_filemode, bfp->bf_flags);
                save_errno = errno;
                (void) umask(omask);

                /* Couldn't create file: failure */
                if (retval < 0)
                {
                        /* errno is set implicitly by open() */
                        errno = save_errno;
                        return -1;
                }

                bfp->bf_disk_fd = retval;
                bfp->bf_ondisk = true;
        }

        /* Write out the contents of our buffer, if we have any */
        if (bfp->bf_buffilled > 0)
        {
                byteswritten = 0;

                if (lseek(bfp->bf_disk_fd, 0, SEEK_SET) < 0)
                {
                        /* errno is set implicitly by lseek() */
                        return -1;
                }

                while (byteswritten < bfp->bf_buffilled)
                {
                        retval = write(bfp->bf_disk_fd,
                                       bfp->bf_buf + byteswritten,
                                       bfp->bf_buffilled - byteswritten);
                        if (retval < 0)
                        {
                                /* errno is set implicitly by write() */
                                return -1;
                        }
                        else
                                byteswritten += retval;
                }
        }
        bfp->bf_committed = true;

        /* Invalidate buf; all goes to file now */
        bfp->bf_buffilled = 0;
        if (bfp->bf_bufsize > 0)
        {
                /* Don't need buffer anymore; free it */
                bfp->bf_bufsize = 0;
                sm_free(bfp->bf_buf);
        }
        return 0;
}

/*
**  SM_BFTRUNCATE -- rewinds and truncates the SM_FILE_T *
**
**      Parameters:
**              fp -- SM_FILE_T * to truncate
**
**      Returns:
**              0 on success, -1 on error
**
**      Side Effects:
**              rewinds the SM_FILE_T *, truncates it to zero length, and puts
**              it into write mode.
**
**      Sets errno:
**              any value of errno specified by fseek()
**              any value of errno specified by ftruncate()
*/

static int
sm_bftruncate(fp)
        SM_FILE_T *fp;
{
        struct bf *bfp;

        if (bfrewind(fp) < 0)
                return -1;

        /* Get bf structure */
        bfp = (struct bf *) fp->f_cookie;
        bfp->bf_buffilled = 0;
        bfp->bf_size = 0;

        /* Need to zero the buffer */
        if (bfp->bf_bufsize > 0)
                memset(bfp->bf_buf, '\0', bfp->bf_bufsize);
        if (bfp->bf_ondisk)
        {
#if NOFTRUNCATE
                /* XXX: Not much we can do except rewind it */
                errno = EINVAL;
                return -1;
#else /* NOFTRUNCATE */
                return ftruncate(bfp->bf_disk_fd, 0);
#endif /* NOFTRUNCATE */
        }
        return 0;
}

/*
**  SM_BFSETINFO -- set/change info for an open file pointer
**
**      Parameters:
**              fp -- file pointer to get info about
**              what -- type of info to set/change
**              valp -- thing to set/change the info to
**
*/

static int
sm_bfsetinfo(fp, what, valp)
        SM_FILE_T *fp;
        int what;
        void *valp;
{
        struct bf *bfp;
        int bsize;

        /* Get bf structure */
        bfp = (struct bf *) fp->f_cookie;
        switch (what)
        {
          case SM_BF_SETBUFSIZE:
                bsize = *((int *) valp);
                bfp->bf_bufsize = bsize;

                /* A zero bsize is valid, just don't allocate memory */
                if (bsize > 0)
                {
                        bfp->bf_buf = (char *) sm_malloc(bsize);
                        if (bfp->bf_buf == NULL)
                        {
                                bfp->bf_bufsize = 0;
                                errno = ENOMEM;
                                return -1;
                        }
                }
                else
                        bfp->bf_buf = NULL;
                return 0;
          case SM_BF_COMMIT:
                return sm_bfcommit(fp);
          case SM_BF_TRUNCATE:
                return sm_bftruncate(fp);
          case SM_BF_TEST:
                return 1; /* always */
          default:
                errno = EINVAL;
                return -1;
        }
}