#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>
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 *));
struct bf
{
bool bf_committed;
bool bf_ondisk;
long bf_flags;
int bf_disk_fd;
char *bf_buf;
int bf_bufsize;
int bf_buffilled;
char *bf_filename;
MODE_T bf_filemode;
off_t bf_offset;
int bf_size;
};
#ifdef BF_STANDALONE
# define OPEN(fn, omode, cmode, sff) open(fn, omode, cmode)
#else
# define OPEN(fn, omode, cmode, sff) safeopen(fn, omode, cmode, sff)
#endif
struct bf_info
{
char *bi_filename;
MODE_T bi_fmode;
size_t bi_bsize;
long bi_flags;
};
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;
if (*filename == '\0')
{
errno = ENOENT;
return -1;
}
if (stat(filename, &st) == 0)
{
errno = EEXIST;
return -1;
}
bfp = (struct bf *) sm_malloc(sizeof(struct bf));
if (bfp == NULL)
{
errno = ENOMEM;
return -1;
}
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;
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;
}
#ifdef __STDC__
SM_FILE_T *
bfopen(char *filename, MODE_T fmode, size_t bsize, long flags)
#else
SM_FILE_T *
bfopen(filename, fmode, bsize, flags)
char *filename;
MODE_T fmode;
size_t bsize;
long flags;
#endif
{
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;
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);
}
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;
}
}
static int
sm_bfclose(fp)
SM_FILE_T *fp;
{
struct bf *bfp;
bfp = (struct bf *) fp->f_cookie;
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);
if (bfp->bf_bufsize > 0)
sm_free(bfp->bf_buf);
sm_free(bfp);
return 0;
}
static ssize_t
sm_bfread(fp, buf, nbytes)
SM_FILE_T *fp;
char *buf;
size_t nbytes;
{
struct bf *bfp;
ssize_t count = 0;
int retval;
bfp = (struct bf *) fp->f_cookie;
if (bfp->bf_offset < bfp->bf_buffilled)
{
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)
{
if (!bfp->bf_ondisk)
{
if (tTd(58, 8))
sm_dprintf("sm_bfread(%s): to disk\n",
bfp->bf_filename);
goto finished;
}
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))
{
errno = EIO;
}
return -1;
}
while (count < nbytes)
{
retval = read(bfp->bf_disk_fd,
buf + count,
nbytes - count);
if (retval < 0)
{
return -1;
}
else if (retval == 0)
goto finished;
else
count += retval;
}
}
finished:
bfp->bf_offset += count;
return count;
}
static off_t
sm_bfseek(fp, offset, whence)
SM_FILE_T *fp;
off_t offset;
int whence;
{
struct bf *bfp;
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;
}
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;
int retval;
bfp = (struct bf *) fp->f_cookie;
if (bfp->bf_committed)
{
if (lseek(bfp->bf_disk_fd, bfp->bf_offset, SEEK_SET) < 0)
{
if ((errno == EINVAL) || (errno == ESPIPE))
{
errno = EIO;
}
return -1;
}
count = write(bfp->bf_disk_fd, buf, nbytes);
if (count < 0)
{
return -1;
}
goto finished;
}
if (bfp->bf_offset < bfp->bf_bufsize)
{
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)
{
if (!bfp->bf_ondisk)
{
MODE_T omask;
int save_errno;
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;
if (retval < 0)
{
if (!(errno == ENOSPC
#ifdef EDQUOT
|| errno == EDQUOT
#endif
))
errno = EIO;
return -1;
}
bfp->bf_disk_fd = retval;
bfp->bf_ondisk = true;
}
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))
{
errno = EIO;
}
return -1;
}
while (count < nbytes)
{
retval = write(bfp->bf_disk_fd, buf + count,
nbytes - count);
if (retval < 0)
{
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;
}
int
bfrewind(fp)
SM_FILE_T *fp;
{
(void) sm_io_flush(fp, SM_TIME_DEFAULT);
sm_io_clearerr(fp);
return sm_io_seek(fp, SM_TIME_DEFAULT, 0, SM_IO_SEEK_SET);
}
static int
sm_bfcommit(fp)
SM_FILE_T *fp;
{
struct bf *bfp;
int retval;
int byteswritten;
bfp = (struct bf *) fp->f_cookie;
if (bfp->bf_committed)
return 0;
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;
}
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);
if (retval < 0)
{
errno = save_errno;
return -1;
}
bfp->bf_disk_fd = retval;
bfp->bf_ondisk = true;
}
if (bfp->bf_buffilled > 0)
{
byteswritten = 0;
if (lseek(bfp->bf_disk_fd, 0, SEEK_SET) < 0)
{
return -1;
}
while (byteswritten < bfp->bf_buffilled)
{
retval = write(bfp->bf_disk_fd,
bfp->bf_buf + byteswritten,
bfp->bf_buffilled - byteswritten);
if (retval < 0)
{
return -1;
}
else
byteswritten += retval;
}
}
bfp->bf_committed = true;
bfp->bf_buffilled = 0;
if (bfp->bf_bufsize > 0)
{
bfp->bf_bufsize = 0;
sm_free(bfp->bf_buf);
}
return 0;
}
static int
sm_bftruncate(fp)
SM_FILE_T *fp;
{
struct bf *bfp;
if (bfrewind(fp) < 0)
return -1;
bfp = (struct bf *) fp->f_cookie;
bfp->bf_buffilled = 0;
bfp->bf_size = 0;
if (bfp->bf_bufsize > 0)
memset(bfp->bf_buf, '\0', bfp->bf_bufsize);
if (bfp->bf_ondisk)
{
#if NOFTRUNCATE
errno = EINVAL;
return -1;
#else
return ftruncate(bfp->bf_disk_fd, 0);
#endif
}
return 0;
}
static int
sm_bfsetinfo(fp, what, valp)
SM_FILE_T *fp;
int what;
void *valp;
{
struct bf *bfp;
int bsize;
bfp = (struct bf *) fp->f_cookie;
switch (what)
{
case SM_BF_SETBUFSIZE:
bsize = *((int *) valp);
bfp->bf_bufsize = bsize;
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;
default:
errno = EINVAL;
return -1;
}
}