diff options
Diffstat (limited to 'contrib/sendmail/src/bf.c')
-rw-r--r-- | contrib/sendmail/src/bf.c | 864 |
1 files changed, 864 insertions, 0 deletions
diff --git a/contrib/sendmail/src/bf.c b/contrib/sendmail/src/bf.c new file mode 100644 index 0000000..b31ce7e --- /dev/null +++ b/contrib/sendmail/src/bf.c @@ -0,0 +1,864 @@ +/* + * 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; + } +} |