1025 lines
31 KiB
C
1025 lines
31 KiB
C
/*++
|
|
/* NAME
|
|
/* vstream 3
|
|
/* SUMMARY
|
|
/* light-weight buffered I/O package
|
|
/* SYNOPSIS
|
|
/* #include <vstream.h>
|
|
/*
|
|
/* VSTREAM *vstream_fopen(path, flags, mode)
|
|
/* char *path;
|
|
/* int flags;
|
|
/* int mode;
|
|
/*
|
|
/* VSTREAM *vstream_fdopen(fd, flags)
|
|
/* int fd;
|
|
/* int flags;
|
|
/*
|
|
/* int vstream_fclose(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* VSTREAM *vstream_printf(format, ...)
|
|
/* char *format;
|
|
/*
|
|
/* VSTREAM *vstream_fprintf(stream, format, ...)
|
|
/* VSTREAM *stream;
|
|
/* char *format;
|
|
/*
|
|
/* int VSTREAM_GETC(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* int VSTREAM_PUTC(ch, stream)
|
|
/* int ch;
|
|
/*
|
|
/* int VSTREAM_GETCHAR(void)
|
|
/*
|
|
/* int VSTREAM_PUTCHAR(ch)
|
|
/* int ch;
|
|
/*
|
|
/* int vstream_ungetc(stream, ch)
|
|
/* VSTREAM *stream;
|
|
/* int ch;
|
|
/*
|
|
/* int vstream_fputs(str, stream)
|
|
/* char *str;
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* long vstream_ftell(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* long vstream_fseek(stream, offset, whence)
|
|
/* VSTREAM *stream;
|
|
/* long offset;
|
|
/* int whence;
|
|
/*
|
|
/* int vstream_fflush(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* int vstream_fread(stream, buf, len)
|
|
/* VSTREAM *stream;
|
|
/* char *buf;
|
|
/* int len;
|
|
/*
|
|
/* int vstream_fwrite(stream, buf, len)
|
|
/* VSTREAM *stream;
|
|
/* char *buf;
|
|
/* int len;
|
|
/*
|
|
/* void vstream_control(stream, name, ...)
|
|
/* VSTREAM *stream;
|
|
/* int name;
|
|
/*
|
|
/* int vstream_fileno(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* int vstream_ferror(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* int vstream_feof(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* int vstream_clearerr(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* char *VSTREAM_PATH(stream)
|
|
/* VSTREAM *stream;
|
|
/*
|
|
/* char *vstream_vfprintf(vp, format, ap)
|
|
/* char *format;
|
|
/* va_list *ap;
|
|
/*
|
|
/* int vstream_peek(stream)
|
|
/* VSTREAM *stream;
|
|
/* DESCRIPTION
|
|
/* The \fIvstream\fR module implements light-weight buffered I/O
|
|
/* similar to the standard I/O routines.
|
|
/*
|
|
/* The interface is implemented in terms of VSTREAM structure
|
|
/* pointers, also called streams. For convenience, three streams
|
|
/* are predefined: VSTREAM_IN, VSTREAM_OUT, and VSTREAM_ERR. These
|
|
/* streams are connected to the standard input, output and error
|
|
/* file descriptors, respectively.
|
|
/*
|
|
/* Although the interface is patterned after the standard I/O
|
|
/* library, there are some major differences:
|
|
/* .IP \(bu
|
|
/* File descriptors are not limited to the range 0..255. This
|
|
/* was reason #1 to write these routines in the first place.
|
|
/* .IP \(bu
|
|
/* The application can switch between reading and writing on
|
|
/* the same stream without having to perform a flush or seek
|
|
/* operation, and can change write position without having to
|
|
/* flush. This was reason #2. Upon position or direction change,
|
|
/* unread input is discarded, and unwritten output is flushed
|
|
/* automatically. Exception: with double-buffered streams, unread
|
|
/* input is not discarded upon change of I/O direction, and
|
|
/* output flushing is delayed until the read buffer must be refilled.
|
|
/* .IP \(bu
|
|
/* A bidirectional stream can read and write with the same buffer
|
|
/* and file descriptor, or it can have separate read/write
|
|
/* buffers and/or file descriptors.
|
|
/* .IP \(bu
|
|
/* No automatic flushing of VSTREAM_OUT upon program exit, or of
|
|
/* VSTREAM_ERR at any time. No unbuffered or line buffered modes.
|
|
/* This functionality may be added when it is really needed.
|
|
/* .PP
|
|
/* vstream_fopen() opens the named file and associates a buffered
|
|
/* stream with it. The \fIpath\fR, \fIflags\fR and \fImode\fR
|
|
/* arguments are passed on to the open(2) routine. The result is
|
|
/* a null pointer in case of problems. The \fIpath\fR argument is
|
|
/* copied and can be looked up with VSTREAM_PATH().
|
|
/*
|
|
/* vstream_fdopen() takes an open file and associates a buffered
|
|
/* stream with it. The \fIflags\fR argument specifies how the file
|
|
/* was opened. vstream_fdopen() either succeeds or never returns.
|
|
/*
|
|
/* vstream_fclose() closes the named buffered stream. The result
|
|
/* is 0 in case of success, VSTREAM_EOF in case of problems.
|
|
/*
|
|
/* vstream_fprintf() formats its arguments according to the
|
|
/* \fIformat\fR argument and writes the result to the named stream.
|
|
/* The result is the stream argument. It understands the s, c, d, u,
|
|
/* o, x, X, e, f and g format types, the l modifier, field width and
|
|
/* precision, sign, and padding with zeros or spaces. In addition,
|
|
/* vstream_fprintf() recognizes the %m format specifier and expands
|
|
/* it to the error message corresponding to the current value of the
|
|
/* global \fIerrno\fR variable.
|
|
/*
|
|
/* vstream_printf() performs formatted output to the standard output
|
|
/* stream.
|
|
/*
|
|
/* VSTREAM_GETC() reads the next character from the named stream.
|
|
/* The result is VSTREAM_EOF when end-of-file is reached or if a read
|
|
/* error was detected. VSTREAM_GETC() is an unsafe macro that
|
|
/* evaluates some arguments more than once.
|
|
/*
|
|
/* VSTREAM_GETCHAR() is an alias for VSTREAM_GETC(VSTREAM_IN).
|
|
/*
|
|
/* VSTREAM_PUTC() appends the specified character to the specified
|
|
/* stream. The result is the stored character, or VSTREAM_EOF in
|
|
/* case of problems. VSTREAM_PUTC() is an unsafe macro that
|
|
/* evaluates some arguments more than once.
|
|
/*
|
|
/* VSTREAM_PUTCHAR(c) is an alias for VSTREAM_PUTC(c, VSTREAM_OUT).
|
|
/*
|
|
/* vstream_unget() pushes back a character onto the specified stream
|
|
/* and returns the character, or VSTREAM_EOF in case of problems.
|
|
/* It is an error to push back before reading (or immediately after
|
|
/* changing the stream offset via vstream_fseek()). Upon successful
|
|
/* return, vstream_unget() clears the end-of-file stream flag.
|
|
/*
|
|
/* vstream_fputs() appends the given null-terminated string to the
|
|
/* specified buffered stream. The result is 0 in case of success,
|
|
/* VSTREAM_EOF in case of problems.
|
|
/*
|
|
/* vstream_ftell() returns the file offset for the specified stream,
|
|
/* -1 if the stream is connected to a non-seekable file.
|
|
/*
|
|
/* vstream_fseek() changes the file position for the next read or write
|
|
/* operation. Unwritten output is flushed. With unidirectional streams,
|
|
/* unread input is discarded. The \fIoffset\fR argument specifies the file
|
|
/* position from the beginning of the file (\fIwhence\fR is SEEK_SET),
|
|
/* from the current file position (\fIwhence\fR is SEEK_CUR), or from
|
|
/* the file end (SEEK_END). The result value is the file offset
|
|
/* from the beginning of the file, -1 in case of problems.
|
|
/*
|
|
/* vstream_fflush() flushes unwritten data to a file that was
|
|
/* opened in read-write or write-only mode.
|
|
/* vstream_fflush() returns 0 in case of success, VSTREAM_EOF in
|
|
/* case of problems. It is an error to flush a read-only stream.
|
|
/*
|
|
/* vstream_fread() and vstream_fwrite() perform unformatted I/O
|
|
/* on the named stream. The result value is the number of bytes
|
|
/* transferred. A short count is returned in case of end-of-file
|
|
/* or error conditions.
|
|
/*
|
|
/* vstream_control() allows the user to fine tune the behavior of
|
|
/* the specified stream. The arguments are a list of (name,
|
|
/* value) pairs, terminated with VSTREAM_CTL_END.
|
|
/* The following lists the names and the types of the corresponding
|
|
/* value arguments.
|
|
/* .IP "VSTREAM_CTL_READ_FN (int (*)(int, void *, unsigned))"
|
|
/* The argument specifies an alternative for the read(2) function,
|
|
/* for example, a read function that enforces a time limit.
|
|
/* .IP "VSTREAM_CTL_WRITE_FN (int (*)(int, void *, unsigned))"
|
|
/* The argument specifies an alternative for the write(2) function,
|
|
/* for example, a write function that enforces a time limit.
|
|
/* .IP "VSTREAM_CTL_PATH (char *)"
|
|
/* Updates the stored pathname of the specified stream. The pathname
|
|
/* is copied.
|
|
/* .IP "VSTREAM_CTL_DOUBLE (no value)"
|
|
/* Use separate buffers for reading and for writing. This prevents
|
|
/* unread input from being discarded upon change of I/O direction.
|
|
/* .IP "VSTREAM_CTL_READ_FD (int)
|
|
/* The argument specifies the file descriptor to be used for reading.
|
|
/* This feature is limited to double-buffered streams, and makes the
|
|
/* stream non-seekable.
|
|
/* .IP "VSTREAM_CTL_WRITE_FD (int)
|
|
/* The argument specifies the file descriptor to be used for writing.
|
|
/* This feature is limited to double-buffered streams, and makes the
|
|
/* stream non-seekable.
|
|
/* .IP "VSTREAM_CTL_WAITPID_FN (int (*)(pid_t, WAIT_STATUS_T *, int))"
|
|
/* A pointer to function that behaves like waitpid(). This information
|
|
/* is used by the vstream_pclose() routine.
|
|
/* .PP
|
|
/* vstream_fileno() gives access to the file handle associated with
|
|
/* a buffered stream. With streams that have separate read/write
|
|
/* file descriptors, the result is the current descriptor.
|
|
/*
|
|
/* VSTREAM_PATH() is an unsafe macro that returns the name stored
|
|
/* with vstream_fopen() or with vstream_control(). The macro is
|
|
/* unsafe because it evaluates some arguments more than once.
|
|
/*
|
|
/* vstream_ferror() (vstream_feof()) returns non-zero when a previous
|
|
/* operation on the specified stream caused an error (end-of-file)
|
|
/* condition.
|
|
/*
|
|
/* vstream_clearerr() resets the error and end-of-file indication of
|
|
/* specified stream, and returns no useful result.
|
|
/*
|
|
/* vstream_vfprintf() provides an alternate interface
|
|
/* for formatting an argument list according to a format string.
|
|
/*
|
|
/* vstream_peek() returns the number of characters that can be
|
|
/* read from the named stream without refilling the read buffer.
|
|
/* DIAGNOSTICS
|
|
/* Panics: interface violations. Fatal errors: out of memory.
|
|
/* SEE ALSO
|
|
/* vbuf_print(3) formatting engine
|
|
/* BUGS
|
|
/* Should use mmap() on reasonable systems.
|
|
/* LICENSE
|
|
/* .ad
|
|
/* .fi
|
|
/* The Secure Mailer license must be distributed with this software.
|
|
/* AUTHOR(S)
|
|
/* Wietse Venema
|
|
/* IBM T.J. Watson Research
|
|
/* P.O. Box 704
|
|
/* Yorktown Heights, NY 10598, USA
|
|
/*--*/
|
|
|
|
/* System library. */
|
|
|
|
#include <sys_defs.h>
|
|
#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
/* Utility library. */
|
|
|
|
#include "mymalloc.h"
|
|
#include "msg.h"
|
|
#include "vbuf_print.h"
|
|
#include "vstring.h"
|
|
#include "vstream.h"
|
|
|
|
/* Application-specific. */
|
|
|
|
/*
|
|
* Forward declarations.
|
|
*/
|
|
static int vstream_buf_get_ready(VBUF *);
|
|
static int vstream_buf_put_ready(VBUF *);
|
|
static int vstream_buf_space(VBUF *, int);
|
|
|
|
/*
|
|
* Initialization of the three pre-defined streams. Pre-allocate a static
|
|
* I/O buffer for the standard error stream, so that the error handler can
|
|
* produce a diagnostic even when memory allocation fails.
|
|
*/
|
|
static unsigned char vstream_fstd_buf[VSTREAM_BUFSIZE];
|
|
|
|
VSTREAM vstream_fstd[] = {
|
|
{{
|
|
0, /* flags */
|
|
0, 0, 0, 0, /* buffer */
|
|
vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
|
|
}, STDIN_FILENO, (VSTREAM_FN) read, (VSTREAM_FN) write,},
|
|
{{
|
|
0, /* flags */
|
|
0, 0, 0, 0, /* buffer */
|
|
vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
|
|
}, STDOUT_FILENO, (VSTREAM_FN) read, (VSTREAM_FN) write,},
|
|
{{
|
|
VBUF_FLAG_FIXED | VSTREAM_FLAG_WRITE,
|
|
vstream_fstd_buf, VSTREAM_BUFSIZE, VSTREAM_BUFSIZE, vstream_fstd_buf,
|
|
vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
|
|
}, STDERR_FILENO, (VSTREAM_FN) read, (VSTREAM_FN) write,},
|
|
};
|
|
|
|
#define VSTREAM_STATIC(v) ((v) >= VSTREAM_IN && (v) <= VSTREAM_ERR)
|
|
|
|
/*
|
|
* A bunch of macros to make some expressions more readable. XXX We're
|
|
* assuming that O_RDONLY == 0, O_WRONLY == 1, O_RDWR == 2.
|
|
*/
|
|
#define VSTREAM_ACC_MASK(f) ((f) & (O_APPEND | O_WRONLY | O_RDWR))
|
|
|
|
#define VSTREAM_CAN_READ(f) (VSTREAM_ACC_MASK(f) == O_RDONLY \
|
|
|| VSTREAM_ACC_MASK(f) == O_RDWR)
|
|
#define VSTREAM_CAN_WRITE(f) (VSTREAM_ACC_MASK(f) & O_WRONLY \
|
|
|| VSTREAM_ACC_MASK(f) & O_RDWR \
|
|
|| VSTREAM_ACC_MASK(f) & O_APPEND)
|
|
|
|
#define VSTREAM_BUF_COUNT(bp, n) \
|
|
((bp)->flags & VSTREAM_FLAG_READ ? -(n) : (n))
|
|
|
|
#define VSTREAM_BUF_AT_START(bp) { \
|
|
(bp)->cnt = VSTREAM_BUF_COUNT((bp), (bp)->len); \
|
|
(bp)->ptr = (bp)->data; \
|
|
}
|
|
|
|
#define VSTREAM_BUF_AT_OFFSET(bp, offset) { \
|
|
(bp)->ptr = (bp)->data + (offset); \
|
|
(bp)->cnt = VSTREAM_BUF_COUNT(bp, (bp)->len - (offset)); \
|
|
}
|
|
|
|
#define VSTREAM_BUF_AT_END(bp) { \
|
|
(bp)->cnt = 0; \
|
|
(bp)->ptr = (bp)->data + (bp)->len; \
|
|
}
|
|
|
|
#define VSTREAM_BUF_ZERO(bp) { \
|
|
(bp)->flags = 0; \
|
|
(bp)->data = (bp)->ptr = 0; \
|
|
(bp)->len = (bp)->cnt = 0; \
|
|
}
|
|
|
|
#define VSTREAM_BUF_ACTIONS(bp, get_action, put_action, space_action) { \
|
|
(bp)->get_ready = (get_action); \
|
|
(bp)->put_ready = (put_action); \
|
|
(bp)->space = (space_action); \
|
|
}
|
|
|
|
#define VSTREAM_SAVE_STATE(stream, buffer, filedes) { \
|
|
stream->buffer = stream->buf; \
|
|
stream->filedes = stream->fd; \
|
|
}
|
|
|
|
#define VSTREAM_RESTORE_STATE(stream, buffer, filedes) do { \
|
|
stream->buffer.flags = stream->buf.flags; \
|
|
stream->buf = stream->buffer; \
|
|
stream->fd = stream->filedes; \
|
|
} while(0)
|
|
|
|
#define VSTREAM_FORK_STATE(stream, buffer, filedes) { \
|
|
stream->buffer = stream->buf; \
|
|
stream->filedes = stream->fd; \
|
|
stream->buffer.data = stream->buffer.ptr = 0; \
|
|
stream->buffer.len = stream->buffer.cnt = 0; \
|
|
stream->buffer.flags &= ~VSTREAM_FLAG_FIXED; \
|
|
};
|
|
|
|
#define VSTREAM_FLAG_READ_DOUBLE (VSTREAM_FLAG_READ | VSTREAM_FLAG_DOUBLE)
|
|
#define VSTREAM_FLAG_WRITE_DOUBLE (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_DOUBLE)
|
|
|
|
#define VSTREAM_FFLUSH_SOME(stream) \
|
|
vstream_fflush_some((stream), (stream)->buf.len - (stream)->buf.cnt)
|
|
|
|
/* vstream_buf_init - initialize buffer */
|
|
|
|
static void vstream_buf_init(VBUF *bp, int flags)
|
|
{
|
|
|
|
/*
|
|
* Initialize the buffer such that the first data access triggers a
|
|
* buffer boundary action.
|
|
*/
|
|
VSTREAM_BUF_ZERO(bp);
|
|
VSTREAM_BUF_ACTIONS(bp,
|
|
VSTREAM_CAN_READ(flags) ? vstream_buf_get_ready : 0,
|
|
VSTREAM_CAN_WRITE(flags) ? vstream_buf_put_ready : 0,
|
|
vstream_buf_space);
|
|
}
|
|
|
|
/* vstream_buf_alloc - allocate buffer memory */
|
|
|
|
static void vstream_buf_alloc(VBUF *bp, int len)
|
|
{
|
|
int used = bp->ptr - bp->data;
|
|
const char *myname = "vstream_buf_alloc";
|
|
|
|
if (len < bp->len)
|
|
msg_panic("%s: attempt to shrink buffer", myname);
|
|
if (bp->flags & VSTREAM_FLAG_FIXED)
|
|
msg_panic("%s: unable to extend fixed-size buffer", myname);
|
|
|
|
/*
|
|
* Late buffer allocation allows the user to override the default policy.
|
|
* If a buffer already exists, allow for the presence of (output) data.
|
|
*/
|
|
bp->data = (unsigned char *)
|
|
(bp->data ? myrealloc((char *) bp->data, len) : mymalloc(len));
|
|
bp->len = len;
|
|
if (bp->flags & VSTREAM_FLAG_READ)
|
|
bp->ptr = bp->data + used;
|
|
else
|
|
VSTREAM_BUF_AT_OFFSET(bp, used);
|
|
}
|
|
|
|
/* vstream_buf_wipe - reset buffer to initial state */
|
|
|
|
static void vstream_buf_wipe(VBUF *bp)
|
|
{
|
|
if ((bp->flags & VBUF_FLAG_FIXED) == 0 && bp->data)
|
|
myfree((char *) bp->data);
|
|
VSTREAM_BUF_ZERO(bp);
|
|
VSTREAM_BUF_ACTIONS(bp, 0, 0, 0);
|
|
}
|
|
|
|
/* vstream_fflush_some - flush some buffered data */
|
|
|
|
static int vstream_fflush_some(VSTREAM *stream, int to_flush)
|
|
{
|
|
const char *myname = "vstream_fflush_some";
|
|
VBUF *bp = &stream->buf;
|
|
int used;
|
|
int left_over;
|
|
char *data;
|
|
int len;
|
|
int n;
|
|
|
|
/*
|
|
* Sanity checks. It is illegal to flush a read-only stream. Otherwise,
|
|
* if there is buffered input, discard the input. If there is buffered
|
|
* output, require that the amount to flush is larger than the amount to
|
|
* keep, so that we can memcpy() the residue.
|
|
*/
|
|
if (bp->put_ready == 0)
|
|
msg_panic("%s: read-only stream", myname);
|
|
switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
|
|
case VSTREAM_FLAG_READ: /* discard input */
|
|
VSTREAM_BUF_AT_END(bp);
|
|
/* FALLTHROUGH */
|
|
case 0: /* flush after seek? */
|
|
return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
|
|
case VSTREAM_FLAG_WRITE: /* output buffered */
|
|
break;
|
|
case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
|
|
msg_panic("%s: read/write stream", myname);
|
|
}
|
|
used = bp->len - bp->cnt;
|
|
left_over = used - to_flush;
|
|
|
|
if (msg_verbose > 2 && stream != VSTREAM_ERR)
|
|
msg_info("%s: fd %d flush %d", myname, stream->fd, to_flush);
|
|
if (to_flush < 0 || left_over < 0)
|
|
msg_panic("%s: bad to_flush %d", myname, to_flush);
|
|
if (to_flush < left_over)
|
|
msg_panic("%s: to_flush < left_over", myname);
|
|
if (to_flush == 0)
|
|
return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
|
|
if (bp->flags & VSTREAM_FLAG_ERR)
|
|
return (VSTREAM_EOF);
|
|
|
|
/*
|
|
* When flushing a buffer, allow for partial writes. These can happen
|
|
* while talking to a network. Update the cached file seek position, if
|
|
* any.
|
|
*/
|
|
for (data = (char *) bp->data, len = to_flush; len > 0; len -= n, data += n) {
|
|
if ((n = stream->write_fn(stream->fd, data, len)) <= 0) {
|
|
bp->flags |= VSTREAM_FLAG_ERR;
|
|
return (VSTREAM_EOF);
|
|
}
|
|
if (msg_verbose > 2 && stream != VSTREAM_ERR && n != to_flush)
|
|
msg_info("%s: %d flushed %d/%d", myname, stream->fd, n, to_flush);
|
|
}
|
|
if (bp->flags & VSTREAM_FLAG_SEEK)
|
|
stream->offset += to_flush;
|
|
|
|
/*
|
|
* Allow for partial buffer flush requests. We use memcpy() for reasons
|
|
* of portability to pre-ANSI environments (SunOS 4.x or Ultrix 4.x :-).
|
|
* This is OK because we have already verified that the to_flush count is
|
|
* larger than the left_over count.
|
|
*/
|
|
if (left_over > 0)
|
|
memcpy(bp->data, bp->data + to_flush, left_over);
|
|
bp->cnt += to_flush;
|
|
bp->ptr -= to_flush;
|
|
return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
|
|
}
|
|
|
|
/* vstream_fflush_delayed - delayed stream flush for double-buffered stream */
|
|
|
|
static int vstream_fflush_delayed(VSTREAM *stream)
|
|
{
|
|
int status;
|
|
|
|
/*
|
|
* Sanity check.
|
|
*/
|
|
if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE) != VSTREAM_FLAG_READ_DOUBLE)
|
|
msg_panic("vstream_fflush_delayed: bad flags");
|
|
|
|
/*
|
|
* Temporarily swap buffers and flush unwritten data. This may seem like
|
|
* a lot of work, but it's peanuts compared to the write(2) call that we
|
|
* already have avoided. For example, delayed flush is never used on a
|
|
* non-pipelined SMTP connection.
|
|
*/
|
|
stream->buf.flags &= ~VSTREAM_FLAG_READ;
|
|
VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
|
|
stream->buf.flags |= VSTREAM_FLAG_WRITE;
|
|
VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
|
|
|
|
status = VSTREAM_FFLUSH_SOME(stream);
|
|
|
|
stream->buf.flags &= ~VSTREAM_FLAG_WRITE;
|
|
VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
|
|
stream->buf.flags |= VSTREAM_FLAG_READ;
|
|
VSTREAM_RESTORE_STATE(stream, read_buf, read_fd);
|
|
|
|
return (status);
|
|
}
|
|
|
|
/* vstream_buf_get_ready - vbuf callback to make buffer ready for reading */
|
|
|
|
static int vstream_buf_get_ready(VBUF *bp)
|
|
{
|
|
VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
|
|
const char *myname = "vstream_buf_get_ready";
|
|
int n;
|
|
|
|
/*
|
|
* Detect a change of I/O direction or position. If so, flush any
|
|
* unwritten output immediately when the stream is single-buffered, or
|
|
* when the stream is double-buffered and the read buffer is empty.
|
|
*/
|
|
switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
|
|
case VSTREAM_FLAG_WRITE: /* change direction */
|
|
if (bp->ptr > bp->data)
|
|
if ((bp->flags & VSTREAM_FLAG_DOUBLE) == 0
|
|
|| stream->read_buf.cnt >= 0)
|
|
if (VSTREAM_FFLUSH_SOME(stream))
|
|
return (VSTREAM_EOF);
|
|
bp->flags &= ~VSTREAM_FLAG_WRITE;
|
|
if (bp->flags & VSTREAM_FLAG_DOUBLE)
|
|
VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
|
|
/* FALLTHROUGH */
|
|
case 0: /* change position */
|
|
bp->flags |= VSTREAM_FLAG_READ;
|
|
if (bp->flags & VSTREAM_FLAG_DOUBLE) {
|
|
VSTREAM_RESTORE_STATE(stream, read_buf, read_fd);
|
|
if (bp->cnt < 0)
|
|
return (0);
|
|
}
|
|
/* FALLTHROUGH */
|
|
case VSTREAM_FLAG_READ: /* no change */
|
|
break;
|
|
case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
|
|
msg_panic("%s: read/write stream", myname);
|
|
}
|
|
|
|
/*
|
|
* If this is the first GET operation, allocate a buffer. Late buffer
|
|
* allocation gives the application a chance to override the default
|
|
* buffering policy.
|
|
*/
|
|
if (bp->data == 0)
|
|
vstream_buf_alloc(bp, VSTREAM_BUFSIZE);
|
|
|
|
/*
|
|
* If the stream is double-buffered and the write buffer is not empty,
|
|
* this is the time to flush the write buffer. Delayed flushes reduce
|
|
* system call overhead, and on TCP sockets, avoid triggering Nagle's
|
|
* algorithm.
|
|
*/
|
|
if ((bp->flags & VSTREAM_FLAG_DOUBLE)
|
|
&& stream->write_buf.len > stream->write_buf.cnt)
|
|
if (vstream_fflush_delayed(stream))
|
|
return (VSTREAM_EOF);
|
|
|
|
/*
|
|
* Did we receive an EOF indication?
|
|
*/
|
|
if (bp->flags & VSTREAM_FLAG_EOF)
|
|
return (VSTREAM_EOF);
|
|
|
|
/*
|
|
* Fill the buffer with as much data as we can handle, or with as much
|
|
* data as is available right now, whichever is less. Update the cached
|
|
* file seek position, if any.
|
|
*/
|
|
switch (n = stream->read_fn(stream->fd, bp->data, bp->len)) {
|
|
case -1:
|
|
bp->flags |= VSTREAM_FLAG_ERR;
|
|
return (VSTREAM_EOF);
|
|
case 0:
|
|
bp->flags |= VSTREAM_FLAG_EOF;
|
|
return (VSTREAM_EOF);
|
|
default:
|
|
if (msg_verbose > 2)
|
|
msg_info("%s: fd %d got %d", myname, stream->fd, n);
|
|
bp->cnt = -n;
|
|
bp->ptr = bp->data;
|
|
if (bp->flags & VSTREAM_FLAG_SEEK)
|
|
stream->offset += n;
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
/* vstream_buf_put_ready - vbuf callback to make buffer ready for writing */
|
|
|
|
static int vstream_buf_put_ready(VBUF *bp)
|
|
{
|
|
VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
|
|
const char *myname = "vstream_buf_put_ready";
|
|
|
|
/*
|
|
* Sanity checks. Detect a change of I/O direction or position. If so,
|
|
* discard unread input, and reset the buffer to the beginning.
|
|
*/
|
|
switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
|
|
case VSTREAM_FLAG_READ: /* change direction */
|
|
bp->flags &= ~VSTREAM_FLAG_READ;
|
|
if (bp->flags & VSTREAM_FLAG_DOUBLE)
|
|
VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
|
|
/* FALLTHROUGH */
|
|
case 0: /* change position */
|
|
bp->flags |= VSTREAM_FLAG_WRITE;
|
|
if (bp->flags & VSTREAM_FLAG_DOUBLE)
|
|
VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
|
|
else
|
|
VSTREAM_BUF_AT_START(bp);
|
|
/* FALLTHROUGH */
|
|
case VSTREAM_FLAG_WRITE: /* no change */
|
|
break;
|
|
case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
|
|
msg_panic("%s: read/write stream", myname);
|
|
}
|
|
|
|
/*
|
|
* Remember the direction. If this is the first PUT operation for this
|
|
* stream, allocate a new buffer; obviously there is no data to be
|
|
* flushed yet. Otherwise, flush the buffer if it is full.
|
|
*/
|
|
if (bp->data == 0) {
|
|
vstream_buf_alloc(bp, VSTREAM_BUFSIZE);
|
|
} else if (bp->cnt <= 0) {
|
|
if (VSTREAM_FFLUSH_SOME(stream))
|
|
return (VSTREAM_EOF);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* vstream_buf_space - reserve space ahead of time */
|
|
|
|
static int vstream_buf_space(VBUF *bp, int want)
|
|
{
|
|
VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
|
|
int used;
|
|
int incr;
|
|
int shortage;
|
|
const char *myname = "vstream_buf_space";
|
|
|
|
/*
|
|
* Sanity checks. Reserving space implies writing. It is illegal to write
|
|
* to a read-only stream. Detect a change of I/O direction or position.
|
|
* If so, reset the buffer to the beginning.
|
|
*/
|
|
if (bp->put_ready == 0)
|
|
msg_panic("%s: read-only stream", myname);
|
|
switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
|
|
case VSTREAM_FLAG_READ: /* change direction */
|
|
bp->flags &= ~VSTREAM_FLAG_READ;
|
|
if (bp->flags & VSTREAM_FLAG_DOUBLE)
|
|
VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
|
|
/* FALLTHROUGH */
|
|
case 0: /* change position */
|
|
bp->flags |= VSTREAM_FLAG_WRITE;
|
|
if (bp->flags & VSTREAM_FLAG_DOUBLE)
|
|
VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
|
|
else
|
|
VSTREAM_BUF_AT_START(bp);
|
|
/* FALLTHROUGH */
|
|
case VSTREAM_FLAG_WRITE: /* no change */
|
|
break;
|
|
case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
|
|
msg_panic("%s: read/write stream", myname);
|
|
}
|
|
|
|
/*
|
|
* See if enough space is available. If not, flush a multiple of
|
|
* VSTREAM_BUFSIZE bytes and resize the buffer to a multiple of
|
|
* VSTREAM_BUFSIZE. We flush multiples of VSTREAM_BUFSIZE in an attempt
|
|
* to keep file updates block-aligned for better performance.
|
|
*/
|
|
#define VSTREAM_TRUNCATE(count, base) (((count) / (base)) * (base))
|
|
#define VSTREAM_ROUNDUP(count, base) VSTREAM_TRUNCATE(count + base - 1, base)
|
|
|
|
if (want > bp->cnt) {
|
|
if ((used = bp->len - bp->cnt) > VSTREAM_BUFSIZE)
|
|
if (vstream_fflush_some(stream, VSTREAM_TRUNCATE(used, VSTREAM_BUFSIZE)))
|
|
return (VSTREAM_EOF);
|
|
if ((shortage = (want - bp->cnt)) > 0) {
|
|
incr = VSTREAM_ROUNDUP(shortage, VSTREAM_BUFSIZE);
|
|
vstream_buf_alloc(bp, bp->len + incr);
|
|
}
|
|
}
|
|
return (vstream_ferror(stream) ? VSTREAM_EOF : 0); /* mmap() may fail */
|
|
}
|
|
|
|
/* vstream_fseek - change I/O position */
|
|
|
|
long vstream_fseek(VSTREAM *stream, long offset, int whence)
|
|
{
|
|
const char *myname = "vstream_fseek";
|
|
VBUF *bp = &stream->buf;
|
|
|
|
/*
|
|
* Flush any unwritten output. Discard any unread input. Position the
|
|
* buffer at the end, so that the next GET or PUT operation triggers a
|
|
* buffer boundary action.
|
|
*/
|
|
switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
|
|
case VSTREAM_FLAG_WRITE:
|
|
if (bp->ptr > bp->data)
|
|
if (VSTREAM_FFLUSH_SOME(stream))
|
|
return (-1);
|
|
/* FALLTHROUGH */
|
|
case VSTREAM_FLAG_READ:
|
|
case 0:
|
|
VSTREAM_BUF_AT_END(bp);
|
|
break;
|
|
case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
|
|
msg_panic("%s: read/write stream", myname);
|
|
}
|
|
|
|
/*
|
|
* Clear the read/write flags to inform the buffer boundary action
|
|
* routines that we may have changed I/O position.
|
|
*/
|
|
bp->flags &= ~(VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE);
|
|
|
|
/*
|
|
* Shave an unnecessary system call.
|
|
*/
|
|
if (bp->flags & VSTREAM_FLAG_NSEEK) {
|
|
errno = ESPIPE;
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Update the cached file seek position.
|
|
*/
|
|
if ((stream->offset = lseek(stream->fd, offset, whence)) < 0) {
|
|
bp->flags |= VSTREAM_FLAG_NSEEK;
|
|
} else {
|
|
bp->flags |= VSTREAM_FLAG_SEEK;
|
|
}
|
|
bp->flags &= ~VSTREAM_FLAG_EOF;
|
|
return (stream->offset);
|
|
}
|
|
|
|
/* vstream_ftell - return file offset */
|
|
|
|
long vstream_ftell(VSTREAM *stream)
|
|
{
|
|
VBUF *bp = &stream->buf;
|
|
|
|
/*
|
|
* Shave an unnecessary syscall.
|
|
*/
|
|
if (bp->flags & VSTREAM_FLAG_NSEEK) {
|
|
errno = ESPIPE;
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Use the cached file offset when available. This is the offset after
|
|
* the last read, write or seek operation.
|
|
*/
|
|
if ((bp->flags & VSTREAM_FLAG_SEEK) == 0) {
|
|
if ((stream->offset = lseek(stream->fd, 0L, SEEK_CUR)) < 0) {
|
|
bp->flags |= VSTREAM_FLAG_NSEEK;
|
|
return (-1);
|
|
}
|
|
bp->flags |= VSTREAM_FLAG_SEEK;
|
|
}
|
|
|
|
/*
|
|
* If this is a read buffer, subtract the number of unread bytes from the
|
|
* cached offset. Remember that read counts are negative.
|
|
*/
|
|
if (bp->flags & VSTREAM_FLAG_READ)
|
|
return (stream->offset + bp->cnt);
|
|
|
|
/*
|
|
* If this is a write buffer, add the number of unwritten bytes to the
|
|
* cached offset.
|
|
*/
|
|
if (bp->flags & VSTREAM_FLAG_WRITE)
|
|
return (stream->offset + (bp->ptr - bp->data));
|
|
|
|
/*
|
|
* Apparently, this is a new buffer, or a buffer after seek, so there is
|
|
* no need to account for unread or unwritten data.
|
|
*/
|
|
return (stream->offset);
|
|
}
|
|
|
|
/* vstream_fdopen - add buffering to pre-opened stream */
|
|
|
|
VSTREAM *vstream_fdopen(int fd, int flags)
|
|
{
|
|
VSTREAM *stream;
|
|
|
|
/*
|
|
* Sanity check.
|
|
*/
|
|
if (fd < 0)
|
|
msg_panic("vstream_fdopen: bad file %d", fd);
|
|
|
|
/*
|
|
* Initialize buffers etc. but do as little as possible. Late buffer
|
|
* allocation etc. gives the application a chance to override default
|
|
* policies. Either this, or the vstream*open() routines would have to
|
|
* have a really ugly interface with lots of mostly-unused arguments (can
|
|
* you say VMS?).
|
|
*/
|
|
stream = (VSTREAM *) mymalloc(sizeof(*stream));
|
|
stream->fd = fd;
|
|
stream->read_fn = VSTREAM_CAN_READ(flags) ? (VSTREAM_FN) read : 0;
|
|
stream->write_fn = VSTREAM_CAN_WRITE(flags) ? (VSTREAM_FN) write : 0;
|
|
vstream_buf_init(&stream->buf, flags);
|
|
stream->offset = 0;
|
|
stream->path = 0;
|
|
stream->pid = 0;
|
|
stream->waitpid_fn = 0;
|
|
return (stream);
|
|
}
|
|
|
|
/* vstream_fopen - open buffered file stream */
|
|
|
|
VSTREAM *vstream_fopen(const char *path, int flags, int mode)
|
|
{
|
|
VSTREAM *stream;
|
|
int fd;
|
|
|
|
if ((fd = open(path, flags, mode)) < 0) {
|
|
return (0);
|
|
} else {
|
|
stream = vstream_fdopen(fd, flags);
|
|
stream->path = mystrdup(path);
|
|
return (stream);
|
|
}
|
|
}
|
|
|
|
/* vstream_fflush - flush write buffer */
|
|
|
|
int vstream_fflush(VSTREAM *stream)
|
|
{
|
|
if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE)
|
|
== VSTREAM_FLAG_READ_DOUBLE
|
|
&& stream->write_buf.len > stream->write_buf.cnt)
|
|
vstream_fflush_delayed(stream);
|
|
return (VSTREAM_FFLUSH_SOME(stream));
|
|
}
|
|
|
|
/* vstream_fclose - close buffered stream */
|
|
|
|
int vstream_fclose(VSTREAM *stream)
|
|
{
|
|
int err;
|
|
|
|
if (stream->pid != 0)
|
|
msg_panic("vstream_fclose: stream has process");
|
|
if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0)
|
|
vstream_fflush(stream);
|
|
err = vstream_ferror(stream);
|
|
if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
|
|
err |= close(stream->read_fd);
|
|
if (stream->write_fd != stream->read_fd)
|
|
err |= close(stream->write_fd);
|
|
vstream_buf_wipe(&stream->read_buf);
|
|
vstream_buf_wipe(&stream->write_buf);
|
|
stream->buf = stream->read_buf;
|
|
} else {
|
|
err |= close(stream->fd);
|
|
vstream_buf_wipe(&stream->buf);
|
|
}
|
|
if (stream->path)
|
|
myfree(stream->path);
|
|
if (!VSTREAM_STATIC(stream))
|
|
myfree((char *) stream);
|
|
return (err ? VSTREAM_EOF : 0);
|
|
}
|
|
|
|
/* vstream_printf - formatted print to stdout */
|
|
|
|
VSTREAM *vstream_printf(const char *fmt,...)
|
|
{
|
|
VSTREAM *stream = VSTREAM_OUT;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vbuf_print(&stream->buf, fmt, ap);
|
|
va_end(ap);
|
|
return (stream);
|
|
}
|
|
|
|
/* vstream_fprintf - formatted print to buffered stream */
|
|
|
|
VSTREAM *vstream_fprintf(VSTREAM *stream, const char *fmt,...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vbuf_print(&stream->buf, fmt, ap);
|
|
va_end(ap);
|
|
return (stream);
|
|
}
|
|
|
|
/* vstream_fputs - write string to stream */
|
|
|
|
int vstream_fputs(const char *str, VSTREAM *stream)
|
|
{
|
|
int ch;
|
|
|
|
while ((ch = *str++) != 0)
|
|
if (VSTREAM_PUTC(ch, stream) == VSTREAM_EOF)
|
|
return (VSTREAM_EOF);
|
|
return (0);
|
|
}
|
|
|
|
/* vstream_control - fine control */
|
|
|
|
void vstream_control(VSTREAM *stream, int name,...)
|
|
{
|
|
const char *myname = "vstream_control";
|
|
va_list ap;
|
|
|
|
for (va_start(ap, name); name != VSTREAM_CTL_END; name = va_arg(ap, int)) {
|
|
switch (name) {
|
|
case VSTREAM_CTL_READ_FN:
|
|
stream->read_fn = va_arg(ap, VSTREAM_FN);
|
|
break;
|
|
case VSTREAM_CTL_WRITE_FN:
|
|
stream->write_fn = va_arg(ap, VSTREAM_FN);
|
|
break;
|
|
case VSTREAM_CTL_PATH:
|
|
if (stream->path)
|
|
myfree(stream->path);
|
|
stream->path = mystrdup(va_arg(ap, char *));
|
|
break;
|
|
case VSTREAM_CTL_DOUBLE:
|
|
if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) {
|
|
stream->buf.flags |= VSTREAM_FLAG_DOUBLE;
|
|
if (stream->buf.flags & VSTREAM_FLAG_READ) {
|
|
VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
|
|
VSTREAM_FORK_STATE(stream, write_buf, write_fd);
|
|
} else {
|
|
VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
|
|
VSTREAM_FORK_STATE(stream, read_buf, read_fd);
|
|
}
|
|
}
|
|
break;
|
|
case VSTREAM_CTL_READ_FD:
|
|
if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0)
|
|
msg_panic("VSTREAM_CTL_READ_FD requires double buffering");
|
|
stream->read_fd = va_arg(ap, int);
|
|
stream->buf.flags |= VSTREAM_FLAG_NSEEK;
|
|
break;
|
|
case VSTREAM_CTL_WRITE_FD:
|
|
if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0)
|
|
msg_panic("VSTREAM_CTL_WRITE_FD requires double buffering");
|
|
stream->write_fd = va_arg(ap, int);
|
|
stream->buf.flags |= VSTREAM_FLAG_NSEEK;
|
|
break;
|
|
case VSTREAM_CTL_WAITPID_FN:
|
|
stream->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN);
|
|
break;
|
|
default:
|
|
msg_panic("%s: bad name %d", myname, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* vstream_vfprintf - formatted print engine */
|
|
|
|
VSTREAM *vstream_vfprintf(VSTREAM *vp, const char *format, va_list ap)
|
|
{
|
|
vbuf_print(&vp->buf, format, ap);
|
|
return (vp);
|
|
}
|
|
|
|
/* vstream_peek - peek at a stream */
|
|
|
|
int vstream_peek(VSTREAM *vp)
|
|
{
|
|
if (vp->buf.flags & VSTREAM_FLAG_READ) {
|
|
return (-vp->buf.cnt);
|
|
} else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
|
|
return (-vp->read_buf.cnt);
|
|
} else {
|
|
return (0);
|
|
}
|
|
}
|