/*
* Copyright (c) 1999-2005 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.
*
*/
#include "sm/generic.h"
SM_RCSID("@(#)$Id: bf.c,v 1.22 2006/07/16 02:07:39 ca Exp $")
#include "sm/types.h"
#include "sm/stat.h"
#include "sm/uio.h"
#include "sm/fcntl.h"
#include "sm/error.h"
#include "sm/rpool.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/bf.h"
#include "sm/debug.h"
/*
** Notice: this does double buffering:
** once in sm_file_T, once here. We should get rid of one layer.
** Possible solutions:
** 1. get rid of the one here, but: does it work?
** "All" this should do is avoiding creating/deleting the file
** until the available buffer size is exceeded. Currently there is
** too much copying going on.
** 2. disable the sm I/O buffer by using sm_io_setvbuf()
** done, see below.
*/
/* bf io functions */
static open_F sm_bfopen;
static close_F sm_bfclose;
static read_F sm_bfread;
static write_F sm_bfwrite;
static seek_F sm_bfseek;
static setinfo_F sm_bfsetinfo;
static getinfo_F sm_bfgetinfo;
sm_stream_T SmBfIO = SM_STREAM_STRUCT(sm_bfopen, sm_bfclose, sm_bfread,
sm_bfwrite, NULL, NULL, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo);
/* Data structure for storing information about each buffered file */
struct bf
{
bool bf_committed; /* Has this buffered file been committed? */
bool bf_ondisk; /* On disk: committed or buffer overflow */
int bf_disk_fd; /* If on disk, associated file descriptor */
/* this should be a generic (I/O) buffer */
uchar *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 */
};
#define OPEN(fn, omode, cmode) open(fn, omode, cmode)
/*
** 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
**
** Returns:
** usual sm_error code
*/
static sm_ret_T
sm_bfopen(sm_file_T *fp, const void *info, int flags, va_list ap)
{
const char *filename;
mode_t fmode;
size_t bsize;
struct bf *bfp;
int l;
size_t s;
struct stat st;
SM_IS_FP(fp);
SM_REQUIRE(info != NULL);
filename = (const char *) info;
fmode = 0600;
bsize = SM_IO_BUFSIZ;
/* Sanity checks */
if (*filename == '\0')
{
/* Empty filename string */
return sm_error_perm(SM_EM_IO, ENOENT);
}
if (stat(filename, &st) == 0)
{
/* File already exists on disk */
return sm_error_perm(SM_EM_IO, EEXIST);
}
/* Allocate memory */
bfp = (struct bf *) sm_malloc(sizeof(struct bf));
if (bfp == NULL)
return sm_error_temp(SM_EM_IO, ENOMEM);
for (;;)
{
l = va_arg(ap, int);
if (l == SM_IO_WHAT_END)
break;
switch (l)
{
case SM_IO_WHAT_BF_BUFSIZE:
bsize = va_arg(ap, size_t);
break;
case SM_IO_WHAT_FMODE:
fmode = (mode_t) va_arg(ap, int);
break;
default: /* ignore unknown values? */
/* what should we do about the argument then? */
(void *)va_arg(ap, int);
break;
}
}
/* Assign data buffer */
/* A zero bsize is valid, just don't allocate memory */
if (bsize > 0)
{
bfp->bf_buf = (uchar *) sm_malloc(bsize);
if (bfp->bf_buf == NULL)
{
bfp->bf_bufsize = 0;
sm_free(bfp);
return sm_error_temp(SM_EM_IO, ENOMEM);
}
/* turn off buffering in upper layer */
l = sm_io_setvbuf(fp, NULL, SM_IO_NBF, 0);
SM_ASSERT(SM_SUCCESS == l);
}
else
bfp->bf_buf = NULL;
/* Nearly home free, just set all the parameters now */
bfp->bf_committed = false;
bfp->bf_ondisk = false;
bfp->bf_bufsize = bsize;
bfp->bf_buffilled = 0;
s = strlen(filename) + 1;
bfp->bf_filename = (char *) sm_malloc(s);
if (bfp->bf_filename == NULL)
{
SM_FREE(bfp->bf_buf);
sm_free(bfp);
return sm_error_temp(SM_EM_IO, ENOMEM);
}
(void) strlcpy(bfp->bf_filename, filename, s);
bfp->bf_filemode = fmode;
bfp->bf_offset = 0;
bfp->bf_size = 0;
bfp->bf_disk_fd = -1;
f_cookie(*fp) = bfp;
return SM_SUCCESS;
}
/*
** 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 sm_ret_T
sm_bfgetinfo(sm_file_T *fp, int what, void *valp)
{
struct bf *bfp;
SM_IS_FP(fp);
bfp = (struct bf *) f_cookie(*fp);
switch (what)
{
case SM_IO_WHAT_BF_BUFSIZE:
return bfp->bf_bufsize;
case SM_IO_WHAT_FD:
return bfp->bf_disk_fd;
case SM_IO_WHAT_SIZE:
return bfp->bf_size;
default:
return sm_error_perm(SM_EM_IO, EINVAL);
}
}
/*
** SM_BFCLOSE -- close a buffered file
**
** Parameters:
** fp -- cookie of file to close
** flags -- ignored
**
** Returns:
** usual sm_error code
**
** Side Effects:
** deletes backing file, sm_frees memory.
*/
static int
sm_bfclose(sm_file_T *fp, int flags)
{
struct bf *bfp;
SM_IS_FP(fp);
bfp = (struct bf *) f_cookie(*fp);
/* 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);
SM_FREE(bfp->bf_buf);
/* Finally, sm_free the structure */
sm_free(bfp);
return SM_SUCCESS;
}
/*
** SM_BFREAD -- read a buffered file
**
** Parameters:
** cookie -- cookie of file to read
** buf -- buffer to fill
** nbytes -- how many bytes to read
** bytesread -- number of bytes read (output)
**
** Returns:
** number of bytes read or -1 indicate failure
**
** Side Effects:
** none.
**
*/
static sm_ret_T
sm_bfread(sm_file_T *fp, uchar *buf, size_t nbytes, ssize_t *bytesread)
{
struct bf *bfp;
ssize_t count; /* Number of bytes put in buf so far */
int retval;
SM_IS_FP(fp);
bfp = (struct bf *) f_cookie(*fp);
count = 0;
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;
sm_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. */
goto finished;
}
/* Catch a read() on an earlier failed write to disk */
if (bfp->bf_disk_fd < 0)
return sm_error_perm(SM_EM_IO, EIO);
if (lseek(bfp->bf_disk_fd,
bfp->bf_offset + count, SEEK_SET) < 0)
{
if (errno == EINVAL || errno == ESPIPE)
errno = EIO;
return sm_error_perm(SM_EM_IO, errno);
}
while (count < nbytes)
{
retval = read(bfp->bf_disk_fd,
buf + count,
nbytes - count);
if (retval < 0)
return sm_error_perm(SM_EM_IO, errno);
else if (retval == 0)
goto finished;
else
count += retval;
}
}
finished:
bfp->bf_offset += count;
*bytesread = count;
return SM_SUCCESS;
}
/*
** 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
**
*/
static sm_ret_T
sm_bfseek(sm_file_T *fp, off_t offset, int whence)
{
struct bf *bfp;
SM_IS_FP(fp);
bfp = (struct bf *) f_cookie(*fp);
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:
return sm_error_perm(SM_EM_IO, EINVAL);
}
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
** byteswritten -- number of bytes written (output)
**
** Returns:
** number of bytes written or -1 indicate failure
**
** Side Effects:
** may create backing file if over memory limit for file.
**
*/
static sm_ret_T
sm_bfwrite(sm_file_T *fp, const uchar *buf, size_t nbytes, ssize_t *byteswritten)
{
struct bf *bfp;
ssize_t count; /* Number of bytes written so far */
int retval;
SM_IS_FP(fp);
bfp = (struct bf *) f_cookie(*fp);
count = 0;
/* 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)
errno = EIO;
return sm_error_perm(SM_EM_IO, errno);
}
count = write(bfp->bf_disk_fd, buf, nbytes);
if (count < 0)
return sm_error_perm(SM_EM_IO, errno);
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;
sm_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;
/* Clear umask as bf_filemode are the true perms */
omask = umask(0);
retval = OPEN(bfp->bf_filename,
O_RDWR | O_CREAT | O_TRUNC,
bfp->bf_filemode);
(void) umask(omask);
/* 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
))
errno = EIO;
return sm_error_perm(SM_EM_IO, errno);
}
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)
return sm_error_perm(SM_EM_IO, EIO);
if (lseek(bfp->bf_disk_fd,
bfp->bf_offset + count, SEEK_SET) < 0)
{
if (errno == EINVAL || errno == ESPIPE)
errno = EIO;
return sm_error_perm(SM_EM_IO, errno);
}
while (count < nbytes)
{
retval = write(bfp->bf_disk_fd, buf + count,
nbytes - count);
if (retval < 0)
return sm_error_perm(SM_EM_IO, errno);
else
count += retval;
}
}
finished:
bfp->bf_offset += count;
if (bfp->bf_offset > bfp->bf_size)
bfp->bf_size = bfp->bf_offset;
*byteswritten = count;
return SM_SUCCESS;
}
/*
** BFREWIND -- rewinds the sm_file_T *
**
** Parameters:
** fp -- sm_file_T * to rewind
**
** Returns:
** usual sm_error code
**
** 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().
*/
static sm_ret_T
bfrewind(sm_file_T *fp)
{
SM_IS_FP(fp);
(void) sm_io_flush(fp);
sm_io_clearerr(fp); /* quicker just to do it */
return sm_io_seek(fp, 0L, SM_IO_SEEK_SET);
}
/*
** SM_BFCOMMIT -- "commits" the buffered file
**
** Parameters:
** fp -- sm_file_T * to commit to disk
** sync -- invoke fsync()?
**
** Returns:
** usual sm_error code
**
** 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.
*/
static sm_ret_T
sm_bfcommit(sm_file_T *fp, bool sync)
{
struct bf *bfp;
int retval;
int byteswritten;
sm_ret_T ret;
SM_IS_FP(fp);
bfp = (struct bf *) f_cookie(*fp);
/* If already committed, noop */
if (bfp->bf_committed)
return SM_SUCCESS;
/* Do we need to open a file? */
if (!bfp->bf_ondisk)
{
mode_t omask;
struct stat st;
if (stat(bfp->bf_filename, &st) == 0)
return sm_error_perm(SM_EM_IO, EEXIST);
/* Clear umask as bf_filemode are the true perms */
omask = umask(0);
retval = OPEN(bfp->bf_filename, O_RDWR | O_CREAT | O_TRUNC,
bfp->bf_filemode);
(void) umask(omask);
/* Couldn't create file: failure */
if (retval < 0)
return sm_error_perm(SM_EM_IO, errno);
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, (off_t) 0, SEEK_SET) < 0)
return sm_error_perm(SM_EM_IO, errno);
while (byteswritten < bfp->bf_buffilled)
{
retval = write(bfp->bf_disk_fd,
bfp->bf_buf + byteswritten,
bfp->bf_buffilled - byteswritten);
if (retval < 0)
return sm_error_perm(SM_EM_IO, errno);
else
byteswritten += retval;
}
}
bfp->bf_committed = true;
ret = SM_SUCCESS;
if (sync)
{
SM_ASSERT(is_valid_fd(bfp->bf_disk_fd));
if (fsync(bfp->bf_disk_fd) == -1)
ret = sm_error_perm(SM_EM_IO, errno);
}
/* Invalidate buf; all goes to file now */
bfp->bf_buffilled = 0;
if (bfp->bf_bufsize > 0)
{
sm_io_setvbuf(fp, bfp->bf_buf, SM_IO_FBF, bfp->bf_bufsize);
bfp->bf_bufsize = 0;
}
return ret;
}
/*
** SM_BFTRUNCATE -- rewinds and truncates the sm_file_T *
**
** Parameters:
** fp -- sm_file_T * to truncate
**
** Returns:
** usual sm_error code
**
** Side Effects:
** rewinds the sm_file_T *, truncates it to zero length, and puts
** it into write mode.
*/
static sm_ret_T
sm_bftruncate(sm_file_T *fp)
{
sm_ret_T ret;
struct bf *bfp;
SM_IS_FP(fp);
ret = bfrewind(fp);
if (sm_is_err(ret))
return ret;
/* Get bf structure */
bfp = (struct bf *) f_cookie(*fp);
bfp->bf_buffilled = 0;
bfp->bf_size = 0;
/* Need to zero the buffer */
if (bfp->bf_buf != NULL)
sm_memzero(bfp->bf_buf, bfp->bf_bufsize);
if (bfp->bf_ondisk)
{
#if NOFTRUNCATE
/* XXX: Not much we can do except rewind it */
return sm_error_perm(SM_EM_IO, EINVAL);
#else
if (ftruncate(bfp->bf_disk_fd, (off_t) 0) < 0)
return sm_error_perm(SM_EM_IO, errno);
#endif
}
return SM_SUCCESS;
}
/*
** 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 sm_ret_T
sm_bfsetinfo(sm_file_T *fp, int what, void *valp)
{
struct bf *bfp;
int bsize;
SM_IS_FP(fp);
bfp = (struct bf *) f_cookie(*fp);
switch (what)
{
case SM_IO_WHAT_BF_BUFSIZE:
bsize = *((int *) valp);
bfp->bf_bufsize = bsize;
/* A zero bsize is valid, just don't allocate memory */
if (bsize > 0)
{
bfp->bf_buf = (uchar *) sm_malloc(bsize);
if (bfp->bf_buf == NULL)
{
bfp->bf_bufsize = 0;
return sm_error_temp(SM_EM_IO, ENOMEM);
}
}
else
bfp->bf_buf = NULL;
return SM_SUCCESS;
case SM_IO_WHAT_COMMIT:
return sm_bfcommit(fp, true);
case SM_IO_WHAT_BF_COMMIT:
return sm_bfcommit(fp, false);
case SM_IO_WHAT_BF_TRUNCATE:
return sm_bftruncate(fp);
case SM_IO_WHAT_BF_TEST:
return SM_SUCCESS; /* always */
default:
return sm_error_perm(SM_EM_IO, EINVAL);
}
/* NOTREACHED */
}
syntax highlighted by Code2HTML, v. 0.9.1