summaryrefslogtreecommitdiffstats
path: root/src/util/vstream.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/vstream.c')
-rw-r--r--src/util/vstream.c2046
1 files changed, 2046 insertions, 0 deletions
diff --git a/src/util/vstream.c b/src/util/vstream.c
new file mode 100644
index 0000000..b4f9fbb
--- /dev/null
+++ b/src/util/vstream.c
@@ -0,0 +1,2046 @@
+/*++
+/* NAME
+/* vstream 3
+/* SUMMARY
+/* light-weight buffered I/O package
+/* SYNOPSIS
+/* #include <vstream.h>
+/*
+/* VSTREAM *vstream_fopen(path, flags, mode)
+/* const char *path;
+/* int flags;
+/* mode_t mode;
+/*
+/* VSTREAM *vstream_fdopen(fd, flags)
+/* int fd;
+/* int flags;
+/*
+/* VSTREAM *vstream_memopen(string, flags)
+/* VSTRING *string;
+/* int flags;
+/*
+/* VSTREAM *vstream_memreopen(stream, string, flags)
+/* VSTREAM *stream;
+/* VSTRING *string;
+/* int flags;
+/*
+/* int vstream_fclose(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fdclose(stream)
+/* VSTREAM *stream;
+/*
+/* VSTREAM *vstream_printf(format, ...)
+/* const char *format;
+/*
+/* VSTREAM *vstream_fprintf(stream, format, ...)
+/* VSTREAM *stream;
+/* const 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)
+/* const char *str;
+/* VSTREAM *stream;
+/*
+/* off_t vstream_ftell(stream)
+/* VSTREAM *stream;
+/*
+/* off_t vstream_fseek(stream, offset, whence)
+/* VSTREAM *stream;
+/* off_t offset;
+/* int whence;
+/*
+/* int vstream_fflush(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fpurge(stream, direction)
+/* VSTREAM *stream;
+/* int direction;
+/*
+/* ssize_t vstream_fread(stream, buf, len)
+/* VSTREAM *stream;
+/* void *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fwrite(stream, buf, len)
+/* VSTREAM *stream;
+/* void *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fread_app(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fread_buf(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* void vstream_control(stream, name, ...)
+/* VSTREAM *stream;
+/* int name;
+/*
+/* int vstream_fileno(stream)
+/* VSTREAM *stream;
+/*
+/* const ssize_t vstream_req_bufsize(stream)
+/* VSTREAM *stream;
+/*
+/* void *vstream_context(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_ferror(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_ftimeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_feof(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_clearerr(stream)
+/* VSTREAM *stream;
+/*
+/* const char *VSTREAM_PATH(stream)
+/* VSTREAM *stream;
+/*
+/* char *vstream_vprintf(format, ap)
+/* const char *format;
+/* va_list *ap;
+/*
+/* char *vstream_vfprintf(stream, format, ap)
+/* VSTREAM *stream;
+/* const char *format;
+/* va_list *ap;
+/*
+/* ssize_t vstream_bufstat(stream, command)
+/* VSTREAM *stream;
+/* int command;
+/*
+/* ssize_t vstream_peek(stream)
+/* VSTREAM *stream;
+/*
+/* const char *vstream_peek_data(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_setjmp(stream)
+/* VSTREAM *stream;
+/*
+/* void vstream_longjmp(stream, val)
+/* VSTREAM *stream;
+/* int val;
+/*
+/* time_t vstream_ftime(stream)
+/* VSTREAM *stream;
+/*
+/* struct timeval vstream_ftimeval(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_rd_error(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_wr_error(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_rd_timeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_wr_timeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fstat(stream, flags)
+/* VSTREAM *stream;
+/* int flags;
+/* 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_memopen() opens a VSTRING as a stream. The \fIflags\fR
+/* argument must specify one of O_RDONLY, O_WRONLY, or O_APPEND.
+/* vstream_memopen() either succeeds or never returns. Streams
+/* opened with vstream_memopen() have limitations: they can't
+/* be opened in read/write mode, they can't seek beyond the
+/* end of the VSTRING, and they don't support vstream_control()
+/* methods that manipulate buffers, file descriptors, or I/O
+/* functions. After a VSTRING is opened for writing, its content
+/* will be in an indeterminate state while the stream is open,
+/* and will be null-terminated when the stream is closed.
+/*
+/* vstream_memreopen() reopens a memory stream. When the
+/* \fIstream\fR argument is a null pointer, the behavior is that
+/* of vstream_memopen().
+/*
+/* vstream_fclose() closes the named buffered stream. The result
+/* is 0 in case of success, VSTREAM_EOF in case of problems.
+/* vstream_fclose() reports the same errors as vstream_ferror().
+/*
+/* vstream_fdclose() leaves the file(s) open but is otherwise
+/* identical to vstream_fclose().
+/*
+/* 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_ungetc() 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_ungetc() 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_fflush() reports the same errors as vstream_ferror().
+/*
+/* vstream_fpurge() discards the contents of the stream buffer.
+/* If direction is VSTREAM_PURGE_READ, it discards unread data,
+/* else if direction is VSTREAM_PURGE_WRITE, it discards unwritten
+/* data. In the case of a double-buffered stream, if direction is
+/* VSTREAM_PURGE_BOTH, it discards the content of both the read
+/* and write buffers. vstream_fpurge() returns 0 in case of success,
+/* VSTREAM_EOF in case of problems.
+/*
+/* 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_fread_buf() resets the buffer write position,
+/* allocates space for the specified number of bytes in the
+/* buffer, reads the bytes from the specified VSTREAM, and
+/* adjusts the buffer write position. The buffer is NOT
+/* null-terminated. The result value is as with vstream_fread().
+/* NOTE: do not skip calling vstream_fread_buf() when len == 0.
+/* This function has side effects including resetting the buffer
+/* write position, and skipping the call would invalidate the
+/* buffer state.
+/*
+/* vstream_fread_app() is like vstream_fread_buf() but appends
+/* to existing buffer content, instead of writing over it.
+/*
+/* vstream_control() allows the user to fine tune the behavior of
+/* the specified stream. The arguments are a list of macros with
+/* zero or more arguments, terminated with CA_VSTREAM_CTL_END
+/* which has none. The following lists the names and the types
+/* of the corresponding value arguments.
+/* .IP "CA_VSTREAM_CTL_READ_FN(ssize_t (*)(int, void *, size_t, int, void *))"
+/* The argument specifies an alternative for the timed_read(3) function,
+/* for example, a read function that performs decryption.
+/* This function receives as arguments a file descriptor, buffer pointer,
+/* buffer length, timeout value, and the VSTREAM's context value.
+/* A timeout value <= 0 disables the time limit.
+/* This function should return the positive number of bytes transferred,
+/* 0 upon EOF, and -1 upon error with errno set appropriately.
+/* .IP "CA_VSTREAM_CTL_WRITE_FN(ssize_t (*)(int, void *, size_t, int, void *))"
+/* The argument specifies an alternative for the timed_write(3) function,
+/* for example, a write function that performs encryption.
+/* This function receives as arguments a file descriptor, buffer pointer,
+/* buffer length, timeout value, and the VSTREAM's context value.
+/* A timeout value <= 0 disables the time limit.
+/* This function should return the positive number of bytes transferred,
+/* and -1 upon error with errno set appropriately. Instead of -1 it may
+/* also return 0, e.g., upon remote party-initiated protocol shutdown.
+/* .IP "CA_VSTREAM_CTL_CONTEXT(void *)"
+/* The argument specifies application context that is passed on to
+/* the application-specified read/write routines. No copy is made.
+/* .IP "CA_VSTREAM_CTL_PATH(const char *)"
+/* Updates the stored pathname of the specified stream. The pathname
+/* is copied.
+/* .IP "CA_VSTREAM_CTL_DOUBLE (no arguments)"
+/* Use separate buffers for reading and for writing. This prevents
+/* unread input from being discarded upon change of I/O direction.
+/* .IP "CA_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 "CA_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 "CA_VSTREAM_CTL_SWAP_FD(VSTREAM *)"
+/* The argument specifies a VSTREAM pointer; the request swaps the
+/* file descriptor members of the two streams. This feature is limited
+/* to streams that are both double-buffered or both single-buffered.
+/* .IP "CA_VSTREAM_CTL_DUPFD(int)"
+/* The argument specifies a minimum file descriptor value. If
+/* the actual stream's file descriptors are below the minimum,
+/* reallocate the descriptors to the first free value greater
+/* than or equal to the minimum. The VSTREAM_CTL_DUPFD macro
+/* is defined only on systems with fcntl() F_DUPFD support.
+/* .IP "CA_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.
+/* .IP "CA_VSTREAM_CTL_TIMEOUT(int)"
+/* The deadline for a descriptor to become readable in case of a read
+/* request, or writable in case of a write request. Specify a value
+/* of 0 to disable deadlines.
+/* .IP "CA_VSTREAM_CTL_EXCEPT (no arguments)"
+/* Enable exception handling with vstream_setjmp() and vstream_longjmp().
+/* This involves allocation of additional memory that normally isn't
+/* used.
+/* .IP "CA_VSTREAM_CTL_BUFSIZE(ssize_t)"
+/* Specify a non-default buffer size for the next read(2) or
+/* write(2) operation, or zero to implement a no-op. Requests
+/* to reduce the buffer size are silently ignored (i.e. any
+/* positive value <= vstream_req_bufsize()). To get a buffer
+/* size smaller than VSTREAM_BUFSIZE, make the VSTREAM_CTL_BUFSIZE
+/* request before the first stream read or write operation
+/* (i.e., vstream_req_bufsize() returns zero). Requests to
+/* change a fixed-size buffer (i.e., VSTREAM_ERR) are not
+/* allowed.
+/*
+/* NOTE: the vstream_*printf() routines may silently expand a
+/* buffer, so that the result of some %letter specifiers can
+/* be written to contiguous memory.
+/* .IP CA_VSTREAM_CTL_START_DEADLINE (no arguments)
+/* Change the VSTREAM_CTL_TIMEOUT behavior, to a deadline for
+/* the total amount of time for all subsequent file descriptor
+/* read or write operations, and recharge the deadline timer.
+/* .IP CA_VSTREAM_CTL_STOP_DEADLINE (no arguments)
+/* Revert VSTREAM_CTL_TIMEOUT behavior to the default, i.e.
+/* a time limit for individual file descriptor read or write
+/* operations.
+/* .IP CA_VSTREAM_CTL_MIN_DATA_RATE (int)
+/* When the DEADLINE is enabled, the amount of data that must
+/* be transferred to add 1 second to the deadline. However,
+/* the deadline will never exceed the timeout specified with
+/* VSTREAM_CTL_TIMEOUT. A zero value requests no update to the
+/* deadline as data is transferred; that is appropriate for
+/* request/reply interactions.
+/* .IP CA_VSTREAM_CTL_OWN_VSTRING (no arguments)
+/* Transfer ownership of the VSTRING that was opened with
+/* vstream_memopen() etc. to the stream, so that the VSTRING
+/* is automatically destroyed when the stream is closed.
+/* .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_req_bufsize() returns the buffer size that will be
+/* used for the next read(2) or write(2) operation on the named
+/* stream. A zero result means that the next read(2) or write(2)
+/* operation will use the default buffer size (VSTREAM_BUFSIZE).
+/*
+/* vstream_context() returns the application context that is passed on to
+/* the application-specified read/write routines.
+/*
+/* 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_feof() returns non-zero when a previous operation on the
+/* specified stream caused an end-of-file condition.
+/* Although further read requests after EOF may complete
+/* successfully, vstream_feof() will keep returning non-zero
+/* until vstream_clearerr() is called for that stream.
+/*
+/* vstream_ferror() returns non-zero when a previous operation on the
+/* specified stream caused a non-EOF error condition, including timeout.
+/* After a non-EOF error on a stream, no I/O request will
+/* complete until after vstream_clearerr() is called for that stream.
+/*
+/* vstream_ftimeout() returns non-zero when a previous operation on the
+/* specified stream caused a timeout error condition. See
+/* vstream_ferror() for error persistence details.
+/*
+/* vstream_clearerr() resets the timeout, error and end-of-file indication
+/* of the specified stream, and returns no useful result.
+/*
+/* vstream_vfprintf() provides an alternate interface
+/* for formatting an argument list according to a format string.
+/*
+/* vstream_vprintf() provides a similar alternative interface.
+/*
+/* vstream_bufstat() provides input and output buffer status
+/* information. The command is one of the following:
+/* .IP VSTREAM_BST_IN_PEND
+/* Return the number of characters that can be read without
+/* refilling the read buffer.
+/* .IP VSTREAM_BST_OUT_PEND
+/* Return the number of characters that are waiting in the
+/* write buffer.
+/* .PP
+/* vstream_peek() returns the number of characters that can be
+/* read from the named stream without refilling the read buffer.
+/* This is an alias for vstream_bufstat(stream, VSTREAM_BST_IN_PEND).
+/*
+/* vstream_peek_data() returns a pointer to the unread bytes
+/* that exist according to vstream_peek(), or null if no unread
+/* bytes are available.
+/*
+/* vstream_setjmp() saves processing context and makes that context
+/* available for use with vstream_longjmp(). Normally, vstream_setjmp()
+/* returns zero. A non-zero result means that vstream_setjmp() returned
+/* through a vstream_longjmp() call; the result is the \fIval\fR argument
+/* given to vstream_longjmp().
+/*
+/* NB: non-local jumps such as vstream_longjmp() are not safe
+/* for jumping out of any routine that manipulates VSTREAM data.
+/* longjmp() like calls are best avoided in signal handlers.
+/*
+/* vstream_ftime() returns the time of initialization, the last buffer
+/* fill operation, or the last buffer flush operation for the specified
+/* stream. This information is maintained only when stream timeouts are
+/* enabled.
+/*
+/* vstream_ftimeval() is like vstream_ftime() but returns more
+/* detail.
+/*
+/* vstream_rd_mumble() and vstream_wr_mumble() report on
+/* read and write error conditions, respectively.
+/*
+/* vstream_fstat() queries stream status information about
+/* user-requested features. The \fIflags\fR argument is the
+/* bitwise OR of one or more of the following, and the result
+/* value is the bitwise OR of the features that are activated.
+/* .IP VSTREAM_FLAG_DEADLINE
+/* The deadline feature is activated.
+/* .IP VSTREAM_FLAG_DOUBLE
+/* The double-buffering feature is activated.
+/* .IP VSTREAM_FLAG_MEMORY
+/* The stream is connected to a VSTRING buffer.
+/* .IP VSTREAM_FLAG_OWN_VSTRING
+/* The stream 'owns' the VSTRING buffer, and is responsible
+/* for cleaning up when the stream is closed.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: out of memory.
+/* SEE ALSO
+/* timed_read(3) default read routine
+/* timed_write(3) default write routine
+/* vbuf_print(3) formatting engine
+/* setjmp(3) non-local jumps
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, 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 <time.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#define VSTRING_INTERNAL
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "vbuf_print.h"
+#include "iostuff.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 *, ssize_t);
+
+ /*
+ * 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_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ 0,},
+ {{
+ 0, /* flags */
+ 0, 0, 0, 0, /* buffer */
+ vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
+ }, STDOUT_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ 0,},
+ {{
+ 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_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ VSTREAM_BUFSIZE,},
+};
+
+#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)
+
+/* Note: this does not change a negative result into a zero result. */
+#define VSTREAM_SUB_TIME(x, y, z) \
+ do { \
+ (x).tv_sec = (y).tv_sec - (z).tv_sec; \
+ (x).tv_usec = (y).tv_usec - (z).tv_usec; \
+ while ((x).tv_usec < 0) { \
+ (x).tv_usec += 1000000; \
+ (x).tv_sec -= 1; \
+ } \
+ while ((x).tv_usec >= 1000000) { \
+ (x).tv_usec -= 1000000; \
+ (x).tv_sec += 1; \
+ } \
+ } while (0)
+
+#define VSTREAM_ADD_TIME(x, y, z) \
+ do { \
+ (x).tv_sec = (y).tv_sec + (z).tv_sec; \
+ (x).tv_usec = (y).tv_usec + (z).tv_usec; \
+ while ((x).tv_usec >= 1000000) { \
+ (x).tv_usec -= 1000000; \
+ (x).tv_sec += 1; \
+ } \
+ } while (0)
+
+/* 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, ssize_t len)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ ssize_t 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((void *) bp->data, len) : mymalloc(len));
+ if (bp->flags & VSTREAM_FLAG_MEMORY)
+ memset(bp->data + bp->len, 0, len - bp->len);
+ bp->len = len;
+ if (bp->flags & VSTREAM_FLAG_READ) {
+ bp->ptr = bp->data + used;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ } else {
+ VSTREAM_BUF_AT_OFFSET(bp, used);
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ }
+}
+
+/* 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((void *) 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, ssize_t to_flush)
+{
+ const char *myname = "vstream_fflush_some";
+ VBUF *bp = &stream->buf;
+ ssize_t used;
+ ssize_t left_over;
+ void *data;
+ ssize_t len;
+ ssize_t n;
+ int timeout;
+ struct timeval before;
+ struct timeval elapsed;
+ struct timeval bonus;
+
+ /*
+ * 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 %ld", myname, stream->fd, (long) to_flush);
+ if (to_flush < 0 || left_over < 0)
+ msg_panic("%s: bad to_flush %ld", myname, (long) 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.
+ *
+ * When deadlines are enabled, we count the elapsed time for each write
+ * operation instead of simply comparing the time-of-day clock with a
+ * per-stream deadline. The latter could result in anomalies when an
+ * application does lengthy processing between write operations. Keep in
+ * mind that a receiver may not be able to keep up when a sender suddenly
+ * floods it with a lot of data as it tries to catch up with a deadline.
+ */
+ for (data = (void *) bp->data, len = to_flush; len > 0; len -= n, data += n) {
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0);
+ if (timeout <= 0) {
+ bp->flags |= (VSTREAM_FLAG_WR_ERR | VSTREAM_FLAG_WR_TIMEOUT);
+ errno = ETIMEDOUT;
+ return (VSTREAM_EOF);
+ }
+ if (len == to_flush)
+ GETTIMEOFDAY(&before);
+ else
+ before = stream->iotime;
+ } else
+ timeout = stream->timeout;
+ if ((n = stream->write_fn(stream->fd, data, len, timeout, stream->context)) <= 0) {
+ bp->flags |= VSTREAM_FLAG_WR_ERR;
+ if (errno == ETIMEDOUT) {
+ bp->flags |= VSTREAM_FLAG_WR_TIMEOUT;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ }
+ return (VSTREAM_EOF);
+ }
+ if (timeout) {
+ GETTIMEOFDAY(&stream->iotime);
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ VSTREAM_SUB_TIME(elapsed, stream->iotime, before);
+ VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed);
+ if (stream->min_data_rate > 0) {
+ bonus.tv_sec = n / stream->min_data_rate;
+ bonus.tv_usec = (n % stream->min_data_rate) * 1000000;
+ bonus.tv_usec /= stream->min_data_rate;
+ VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit,
+ bonus);
+ if (stream->time_limit.tv_sec >= stream->timeout) {
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ }
+ }
+ }
+ }
+ if (msg_verbose > 2 && stream != VSTREAM_ERR && n != to_flush)
+ msg_info("%s: %d flushed %ld/%ld", myname, stream->fd,
+ (long) n, (long) 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";
+ ssize_t n;
+ struct timeval before;
+ struct timeval elapsed;
+ struct timeval bonus;
+ int timeout;
+
+ /*
+ * 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.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (bp->len < stream->req_bufsize)
+ vstream_buf_alloc(bp, stream->req_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.
+ *
+ * When deadlines are enabled, we count the elapsed time for each read
+ * operation instead of simply comparing the time-of-day clock with a
+ * per-stream deadline. The latter could result in anomalies when an
+ * application does lengthy processing between read operations. Keep in
+ * mind that a sender may get blocked, and may not be able to keep up
+ * when a receiver suddenly wants to read a lot of data as it tries to
+ * catch up with a deadline.
+ */
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0);
+ if (timeout <= 0) {
+ bp->flags |= (VSTREAM_FLAG_RD_ERR | VSTREAM_FLAG_RD_TIMEOUT);
+ errno = ETIMEDOUT;
+ return (VSTREAM_EOF);
+ }
+ GETTIMEOFDAY(&before);
+ } else
+ timeout = stream->timeout;
+ switch (n = stream->read_fn(stream->fd, bp->data, bp->len, timeout, stream->context)) {
+ case -1:
+ bp->flags |= VSTREAM_FLAG_RD_ERR;
+ if (errno == ETIMEDOUT) {
+ bp->flags |= VSTREAM_FLAG_RD_TIMEOUT;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ }
+ return (VSTREAM_EOF);
+ case 0:
+ bp->flags |= VSTREAM_FLAG_EOF;
+ return (VSTREAM_EOF);
+ default:
+ if (timeout) {
+ GETTIMEOFDAY(&stream->iotime);
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ VSTREAM_SUB_TIME(elapsed, stream->iotime, before);
+ VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed);
+ if (stream->min_data_rate > 0) {
+ bonus.tv_sec = n / stream->min_data_rate;
+ bonus.tv_usec = (n % stream->min_data_rate) * 1000000;
+ bonus.tv_usec /= stream->min_data_rate;
+ VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit,
+ bonus);
+ if (stream->time_limit.tv_sec >= stream->timeout) {
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ }
+ }
+ }
+ }
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d got %ld", myname, stream->fd, (long) 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 or if the buffer is smaller than the requested size, allocate a
+ * new buffer; obviously there is no data to be flushed yet. Otherwise,
+ * flush the buffer.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (bp->len < stream->req_bufsize) {
+ vstream_buf_alloc(bp, stream->req_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, ssize_t want)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ ssize_t used;
+ ssize_t incr;
+ ssize_t 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);
+ if (want < 0)
+ msg_panic("%s: bad length %ld", myname, (long) want);
+ 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.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+#define VSTREAM_TRUNCATE(count, base) (((count) / (base)) * (base))
+#define VSTREAM_ROUNDUP(count, base) VSTREAM_TRUNCATE(count + base - 1, base)
+
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (want > bp->cnt) {
+ if ((used = bp->len - bp->cnt) > stream->req_bufsize)
+ if (vstream_fflush_some(stream, VSTREAM_TRUNCATE(used, stream->req_bufsize)))
+ return (VSTREAM_EOF);
+ if ((shortage = (want - bp->cnt)) > 0) {
+ if ((bp->flags & VSTREAM_FLAG_FIXED)
+ || shortage > __MAXINT__(ssize_t) -bp->len - stream->req_bufsize) {
+ bp->flags |= VSTREAM_FLAG_WR_ERR;
+ } else {
+ incr = VSTREAM_ROUNDUP(shortage, stream->req_bufsize);
+ vstream_buf_alloc(bp, bp->len + incr);
+ }
+ }
+ }
+ return (vstream_ferror(stream) ? VSTREAM_EOF : 0); /* mmap() may fail */
+}
+
+/* vstream_fpurge - discard unread or unwritten content */
+
+int vstream_fpurge(VSTREAM *stream, int direction)
+{
+ const char *myname = "vstream_fpurge";
+ VBUF *bp = &stream->buf;
+
+#define VSTREAM_MAYBE_PURGE_WRITE(d, b) if ((d) & VSTREAM_PURGE_WRITE) \
+ VSTREAM_BUF_AT_START((b))
+#define VSTREAM_MAYBE_PURGE_READ(d, b) if ((d) & VSTREAM_PURGE_READ) \
+ VSTREAM_BUF_AT_END((b))
+
+ /*
+ * To discard all unread contents, position the read buffer at its end,
+ * so that we skip over any unread data, and so that the next read
+ * operation will refill the buffer.
+ *
+ * To discard all unwritten content, position the write buffer at its
+ * beginning, so that the next write operation clobbers any unwritten
+ * data.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE)) {
+ case VSTREAM_FLAG_READ_DOUBLE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_READ:
+ VSTREAM_MAYBE_PURGE_READ(direction, bp);
+ break;
+ case VSTREAM_FLAG_DOUBLE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf);
+ VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf);
+ break;
+ case VSTREAM_FLAG_WRITE_DOUBLE:
+ VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_WRITE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, bp);
+ break;
+ case VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE:
+ case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * Invalidate the cached file seek position.
+ */
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ stream->offset = 0;
+
+ return (0);
+}
+
+/* vstream_fseek - change I/O position */
+
+off_t vstream_fseek(VSTREAM *stream, off_t offset, int whence)
+{
+ const char *myname = "vstream_fseek";
+ VBUF *bp = &stream->buf;
+
+ /*
+ * TODO: support data length (data length != buffer length). Without data
+ * length information, Without explicit data length information,
+ * vstream_memopen(O_RDONLY) has to set the VSTREAM buffer length to the
+ * vstring payload length to avoid accessing unwritten data after
+ * vstream_fseek(), because for lseek() compatibility, vstream_fseek()
+ * must allow seeking past the end of a file.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY) {
+ if (whence == SEEK_CUR)
+ offset += (bp->ptr - bp->data);
+ else if (whence == SEEK_END)
+ offset += bp->len;
+ if (offset < 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (offset > bp->len && (bp->flags & VSTREAM_FLAG_WRITE))
+ vstream_buf_space(bp, offset - bp->len);
+ VSTREAM_BUF_AT_OFFSET(bp, offset);
+ return (offset);
+ }
+
+ /*
+ * 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 (whence == SEEK_CUR)
+ offset += (bp->ptr - bp->data); /* add unwritten data */
+ else if (whence == SEEK_END)
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ if (VSTREAM_FFLUSH_SOME(stream))
+ return (-1);
+ }
+ VSTREAM_BUF_AT_END(bp);
+ break;
+ case VSTREAM_FLAG_READ:
+ if (whence == SEEK_CUR)
+ offset += bp->cnt; /* subtract unread data */
+ else if (whence == SEEK_END)
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ /* FALLTHROUGH */
+ 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) {
+ if (errno == ESPIPE)
+ bp->flags |= VSTREAM_FLAG_NSEEK;
+ } else {
+ bp->flags |= VSTREAM_FLAG_SEEK;
+ }
+ bp->flags &= ~VSTREAM_FLAG_EOF;
+ return (stream->offset);
+}
+
+/* vstream_ftell - return file offset */
+
+off_t vstream_ftell(VSTREAM *stream)
+{
+ VBUF *bp = &stream->buf;
+
+ /*
+ * Special case for memory buffer.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ return (bp->ptr - bp->data);
+
+ /*
+ * 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, (off_t) 0, 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_subopen - initialize everything except buffers and I/O handlers */
+
+static VSTREAM *vstream_subopen(void)
+{
+ VSTREAM *stream;
+
+ /* Note: memset() is not a portable way to initialize non-integer types. */
+ stream = (VSTREAM *) mymalloc(sizeof(*stream));
+ stream->offset = 0;
+ stream->path = 0;
+ stream->pid = 0;
+ stream->waitpid_fn = 0;
+ stream->timeout = 0;
+ stream->context = 0;
+ stream->jbuf = 0;
+ stream->iotime.tv_sec = stream->iotime.tv_usec = 0;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ stream->req_bufsize = 0;
+ stream->vstring = 0;
+ stream->min_data_rate = 0;
+ return (stream);
+}
+
+/* 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_subopen();
+ stream->fd = fd;
+ stream->read_fn = VSTREAM_CAN_READ(flags) ? (VSTREAM_RW_FN) timed_read : 0;
+ stream->write_fn = VSTREAM_CAN_WRITE(flags) ? (VSTREAM_RW_FN) timed_write : 0;
+ vstream_buf_init(&stream->buf, flags);
+ return (stream);
+}
+
+/* vstream_fopen - open buffered file stream */
+
+VSTREAM *vstream_fopen(const char *path, int flags, mode_t 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)
+{
+
+ /*
+ * With VSTRING, the write pointer must be positioned behind the end of
+ * data. But vstream_fseek() changes the write position, and causes the
+ * data length to be forgotten. Before flushing to vstream, remember the
+ * current write position, move the write pointer and do what needs to be
+ * done, then move the write pointer back to the saved location.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY) {
+ if (stream->buf.flags & VSTREAM_FLAG_WRITE) {
+ VSTRING *string = stream->vstring;
+
+#ifdef PENDING_VSTREAM_FSEEK_FOR_MEMORY
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, stream->buf.data_len);
+#endif
+ memcpy(&string->vbuf, &stream->buf, sizeof(stream->buf));
+ string->vbuf.flags &= VSTRING_FLAG_MASK;
+ VSTRING_TERMINATE(string);
+ }
+ return (0);
+ }
+ 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;
+
+ /*
+ * NOTE: Negative file descriptors are not part of the external
+ * interface. They are for internal use only, in order to support
+ * vstream_fdclose() without a lot of code duplication. Applications that
+ * rely on negative VSTREAM file descriptors will break without warning.
+ */
+ if (stream->pid != 0)
+ msg_panic("vstream_fclose: stream has process");
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ || ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0
+ && stream->fd >= 0))
+ vstream_fflush(stream);
+ /* Do not remove: vstream_fdclose() depends on this error test. */
+ err = vstream_ferror(stream);
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ if (stream->read_fd >= 0)
+ err |= close(stream->read_fd);
+ if (stream->write_fd != stream->read_fd)
+ if (stream->write_fd >= 0)
+ err |= close(stream->write_fd);
+ vstream_buf_wipe(&stream->read_buf);
+ vstream_buf_wipe(&stream->write_buf);
+ stream->buf = stream->read_buf;
+ } else {
+ if (stream->fd >= 0)
+ err |= close(stream->fd);
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0)
+ vstream_buf_wipe(&stream->buf);
+ }
+ if (stream->path)
+ myfree(stream->path);
+ if (stream->jbuf)
+ myfree((void *) stream->jbuf);
+ if (stream->vstring && (stream->buf.flags & VSTREAM_FLAG_OWN_VSTRING))
+ vstring_free(stream->vstring);
+ if (!VSTREAM_STATIC(stream))
+ myfree((void *) stream);
+ return (err ? VSTREAM_EOF : 0);
+}
+
+/* vstream_fdclose - close stream, leave file(s) open */
+
+int vstream_fdclose(VSTREAM *stream)
+{
+
+ /*
+ * Flush unwritten output, just like vstream_fclose(). Errors are
+ * reported by vstream_fclose().
+ */
+ if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0)
+ (void) vstream_fflush(stream);
+
+ /*
+ * NOTE: Negative file descriptors are not part of the external
+ * interface. They are for internal use only, in order to support
+ * vstream_fdclose() without a lot of code duplication. Applications that
+ * rely on negative VSTREAM file descriptors will break without warning.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ stream->fd = stream->read_fd = stream->write_fd = -1;
+ } else {
+ stream->fd = -1;
+ }
+ return (vstream_fclose(stream));
+}
+
+/* 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_fread_buf - unformatted read to VSTRING */
+
+ssize_t vstream_fread_buf(VSTREAM *fp, VSTRING *vp, ssize_t len)
+{
+ ssize_t ret;
+
+ VSTRING_RESET(vp);
+ VSTRING_SPACE(vp, len);
+ ret = vstream_fread(fp, vstring_str(vp), len);
+ if (ret > 0)
+ VSTRING_AT_OFFSET(vp, ret);
+ return (ret);
+}
+
+/* vstream_fread_app - unformatted read to VSTRING */
+
+ssize_t vstream_fread_app(VSTREAM *fp, VSTRING *vp, ssize_t len)
+{
+ ssize_t ret;
+
+ VSTRING_SPACE(vp, len);
+ ret = vstream_fread(fp, vstring_end(vp), len);
+ if (ret > 0)
+ VSTRING_AT_OFFSET(vp, VSTRING_LEN(vp) + ret);
+ return (ret);
+}
+
+/* vstream_control - fine control */
+
+void vstream_control(VSTREAM *stream, int name,...)
+{
+ const char *myname = "vstream_control";
+ va_list ap;
+ int floor;
+ int old_fd;
+ ssize_t req_bufsize = 0;
+ VSTREAM *stream2;
+ int min_data_rate;
+
+#define SWAP(type,a,b) do { type temp = (a); (a) = (b); (b) = (temp); } while (0)
+
+ /*
+ * A crude 'allow' filter for memory streams.
+ */
+ int memory_ops =
+ ((1 << VSTREAM_CTL_END) | (1 << VSTREAM_CTL_CONTEXT)
+ | (1 << VSTREAM_CTL_PATH) | (1 << VSTREAM_CTL_EXCEPT)
+ | (1 << VSTREAM_CTL_OWN_VSTRING));
+
+ for (va_start(ap, name); name != VSTREAM_CTL_END; name = va_arg(ap, int)) {
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ && (memory_ops & (1 << name)) == 0)
+ msg_panic("%s: memory stream does not support VSTREAM_CTL_%d",
+ VSTREAM_PATH(stream), name);
+ switch (name) {
+ case VSTREAM_CTL_READ_FN:
+ stream->read_fn = va_arg(ap, VSTREAM_RW_FN);
+ break;
+ case VSTREAM_CTL_WRITE_FN:
+ stream->write_fn = va_arg(ap, VSTREAM_RW_FN);
+ break;
+ case VSTREAM_CTL_CONTEXT:
+ stream->context = va_arg(ap, void *);
+ 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_SWAP_FD:
+ stream2 = va_arg(ap, VSTREAM *);
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE)
+ != (stream2->buf.flags & VSTREAM_FLAG_DOUBLE))
+ msg_panic("VSTREAM_CTL_SWAP_FD can't swap descriptors between "
+ "single-buffered and double-buffered streams");
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ SWAP(int, stream->read_fd, stream2->read_fd);
+ SWAP(int, stream->write_fd, stream2->write_fd);
+ stream->fd = ((stream->buf.flags & VSTREAM_FLAG_WRITE) ?
+ stream->write_fd : stream->read_fd);
+ } else {
+ SWAP(int, stream->fd, stream2->fd);
+ }
+ break;
+ case VSTREAM_CTL_TIMEOUT:
+ if (stream->timeout == 0)
+ GETTIMEOFDAY(&stream->iotime);
+ stream->timeout = va_arg(ap, int);
+ if (stream->timeout < 0)
+ msg_panic("%s: bad timeout %d", myname, stream->timeout);
+ break;
+ case VSTREAM_CTL_EXCEPT:
+ if (stream->jbuf == 0)
+ stream->jbuf =
+ (VSTREAM_JMP_BUF *) mymalloc(sizeof(VSTREAM_JMP_BUF));
+ break;
+
+#ifdef VSTREAM_CTL_DUPFD
+
+#define VSTREAM_TRY_DUPFD(backup, fd, floor) do { \
+ if (((backup) = (fd)) < floor) { \
+ if (((fd) = fcntl((backup), F_DUPFD, (floor))) < 0) \
+ msg_fatal("fcntl F_DUPFD %d: %m", (floor)); \
+ (void) close(backup); \
+ } \
+ } while (0)
+
+ case VSTREAM_CTL_DUPFD:
+ floor = va_arg(ap, int);
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ VSTREAM_TRY_DUPFD(old_fd, stream->read_fd, floor);
+ if (stream->write_fd == old_fd)
+ stream->write_fd = stream->read_fd;
+ else
+ VSTREAM_TRY_DUPFD(old_fd, stream->write_fd, floor);
+ stream->fd = (stream->buf.flags & VSTREAM_FLAG_READ) ?
+ stream->read_fd : stream->write_fd;
+ } else {
+ VSTREAM_TRY_DUPFD(old_fd, stream->fd, floor);
+ }
+ break;
+#endif
+
+ /*
+ * Postpone memory (re)allocation until the space is needed.
+ */
+ case VSTREAM_CTL_BUFSIZE:
+ req_bufsize = va_arg(ap, ssize_t);
+ /* Heuristic to detect missing (ssize_t) type cast on LP64 hosts. */
+ if (req_bufsize < 0 || req_bufsize > INT_MAX)
+ msg_panic("unreasonable VSTREAM_CTL_BUFSIZE request: %ld",
+ (long) req_bufsize);
+ if ((stream->buf.flags & VSTREAM_FLAG_FIXED) == 0
+ && req_bufsize > stream->req_bufsize) {
+ if (msg_verbose)
+ msg_info("fd=%d: stream buffer size old=%ld new=%ld",
+ vstream_fileno(stream),
+ (long) stream->req_bufsize,
+ (long) req_bufsize);
+ stream->req_bufsize = req_bufsize;
+ }
+ break;
+
+ /*
+ * Make no gettimeofday() etc. system call until we really know
+ * that we need to do I/O. This avoids a performance hit when
+ * sending or receiving body content one line at a time.
+ */
+ case VSTREAM_CTL_STOP_DEADLINE:
+ stream->buf.flags &= ~VSTREAM_FLAG_DEADLINE;
+ break;
+ case VSTREAM_CTL_START_DEADLINE:
+ if (stream->timeout <= 0)
+ msg_panic("%s: bad timeout %d", myname, stream->timeout);
+ stream->buf.flags |= VSTREAM_FLAG_DEADLINE;
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ break;
+ case VSTREAM_CTL_MIN_DATA_RATE:
+ min_data_rate = va_arg(ap, int);
+ if (min_data_rate < 0)
+ msg_panic("%s: bad min_data_rate %d", myname, min_data_rate);
+ stream->min_data_rate = min_data_rate;
+ break;
+ case VSTREAM_CTL_OWN_VSTRING:
+ if ((stream->buf.flags |= VSTREAM_FLAG_MEMORY) == 0)
+ msg_panic("%s: operation on non-VSTRING stream", myname);
+ stream->buf.flags |= VSTREAM_FLAG_OWN_VSTRING;
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
+
+/* vstream_vprintf - formatted print to stdout */
+
+VSTREAM *vstream_vprintf(const char *format, va_list ap)
+{
+ VSTREAM *vp = VSTREAM_OUT;
+
+ vbuf_print(&vp->buf, format, ap);
+ return (vp);
+}
+
+/* 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_bufstat - get stream buffer status */
+
+ssize_t vstream_bufstat(VSTREAM *vp, int command)
+{
+ VBUF *bp;
+
+ switch (command & VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_IN:
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ bp = &vp->buf;
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ bp = &vp->read_buf;
+ } else {
+ bp = 0;
+ }
+ switch (command & ~VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_PEND:
+ return (bp ? -bp->cnt : 0);
+ /* Add other requests below. */
+ }
+ break;
+ case VSTREAM_BST_FLAG_OUT:
+ if (vp->buf.flags & VSTREAM_FLAG_WRITE) {
+ bp = &vp->buf;
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ bp = &vp->write_buf;
+ } else {
+ bp = 0;
+ }
+ switch (command & ~VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_PEND:
+ return (bp ? bp->len - bp->cnt : 0);
+ /* Add other requests below. */
+ }
+ break;
+ }
+ msg_panic("vstream_bufstat: unknown command: %d", command);
+}
+
+#undef vstream_peek /* API binary compatibility. */
+
+/* vstream_peek - peek at a stream */
+
+ssize_t 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);
+ }
+}
+
+/* vstream_peek_data - peek at unread data */
+
+const char *vstream_peek_data(VSTREAM *vp)
+{
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ return ((const char *) vp->buf.ptr);
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ return ((const char *) vp->read_buf.ptr);
+ } else {
+ return (0);
+ }
+}
+
+/* vstream_memopen - open a VSTRING */
+
+VSTREAM *vstream_memreopen(VSTREAM *stream, VSTRING *string, int flags)
+{
+ if (stream == 0)
+ stream = vstream_subopen();
+ else if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0)
+ msg_panic("vstream_memreopen: cannot reopen non-memory stream");
+ stream->fd = -1;
+ stream->read_fn = 0;
+ stream->write_fn = 0;
+ stream->vstring = string;
+ memcpy(&stream->buf, &stream->vstring->vbuf, sizeof(stream->buf));
+ stream->buf.flags |= VSTREAM_FLAG_MEMORY;
+ switch (VSTREAM_ACC_MASK(flags)) {
+ case O_RDONLY:
+ stream->buf.flags |= VSTREAM_FLAG_READ;
+ /* Prevent reading unwritten data after vstream_fseek(). */
+ stream->buf.len = stream->buf.ptr - stream->buf.data;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, 0);
+ break;
+ case O_WRONLY:
+ stream->buf.flags |= VSTREAM_FLAG_WRITE;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, 0);
+ break;
+ case O_APPEND:
+ stream->buf.flags |= VSTREAM_FLAG_WRITE;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf,
+ stream->buf.ptr - stream->buf.data);
+ break;
+ default:
+ msg_panic("vstream_memopen: flags must be one of "
+ "O_RDONLY, O_WRONLY, or O_APPEND");
+ }
+ return (stream);
+}
+
+#ifdef TEST
+
+static void copy_line(ssize_t bufsize)
+{
+ int c;
+
+ /*
+ * Demonstrates that VSTREAM_CTL_BUFSIZE increases the buffer size, but
+ * does not decrease it. Uses VSTREAM_ERR for non-test output to avoid
+ * interfering with the test.
+ */
+ vstream_fprintf(VSTREAM_ERR, "buffer size test: copy text with %ld buffer size, ignore requests to shrink\n",
+ (long) bufsize);
+ vstream_fflush(VSTREAM_ERR);
+ vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END);
+ vstream_control(VSTREAM_OUT, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END);
+ while ((c = VSTREAM_GETC(VSTREAM_IN)) != VSTREAM_EOF) {
+ VSTREAM_PUTC(c, VSTREAM_OUT);
+ if (c == '\n')
+ break;
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstream_fprintf(VSTREAM_ERR, "actual read/write buffer sizes: %ld/%ld\n\n",
+ (long) VSTREAM_IN->buf.len, (long) VSTREAM_OUT->buf.len);
+ vstream_fflush(VSTREAM_ERR);
+}
+
+static void printf_number(void)
+{
+
+ /*
+ * Demonstrates that vstream_printf() use vbuf_print().
+ */
+ vstream_printf("formatting test: print a number\n");
+ vstream_printf("%d\n\n", 1234567890);
+ vstream_fflush(VSTREAM_OUT);
+}
+
+static void do_memory_stream(void)
+{
+ VSTRING *buf = vstring_alloc(1);
+ VSTREAM *fp;
+ off_t offset;
+ int ch;
+
+ /*
+ * Preload the string.
+ */
+ vstream_printf("memory stream test prep: prefill the VSTRING\n");
+ vstring_strcpy(buf, "01234567");
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Test: open the memory VSTREAM in write-only mode, and clobber it.
+ */
+ vstream_printf("memory stream test: open the VSTRING for writing, overwrite, close\n");
+ fp = vstream_memopen(buf, O_WRONLY);
+ vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fprintf(fp, "hallo");
+ vstream_printf("final memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fclose(fp);
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Test: open the memory VSTREAM for append. vstream_memopen() sets the
+ * buffer length to the VSTRING buffer length, and positions the write
+ * pointer at the VSTRING write position. Write some content, then
+ * overwrite one character.
+ */
+ vstream_printf("memory stream test: open the VSTRING for append, write multiple, then overwrite 1\n");
+ fp = vstream_memopen(buf, O_APPEND);
+ vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fprintf(fp, " world");
+ vstream_printf("final memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ if (vstream_fflush(fp))
+ msg_fatal("vstream_fflush: %m");
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+
+ /*
+ * While the stream is still open, replace the second character.
+ */
+ vstream_printf("replace second character and close\n");
+ if ((offset = vstream_fseek(fp, 1, SEEK_SET)) != 1)
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) 1);
+ VSTREAM_PUTC('e', fp);
+
+ /*
+ * Skip to the end of the content, so that vstream_fflush() will update
+ * the VSTRING with the right content length.
+ */
+ if ((offset = vstream_fseek(fp, VSTRING_LEN(buf), SEEK_SET)) != VSTRING_LEN(buf))
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) VSTRING_LEN(buf));
+ vstream_fclose(fp);
+
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * TODO: test that in write/append mode, seek past the end of data will
+ * result in zero-filled space.
+ */
+
+ /*
+ * Test: Open the VSTRING for reading. This time, vstream_memopen() will
+ * set the VSTREAM buffer length to the content length of the VSTRING, so
+ * that it won't attempt to read past the end of the content.
+ */
+ vstream_printf("memory stream test: open VSTRING for reading, then read\n");
+ fp = vstream_memopen(buf, O_RDONLY);
+ vstream_printf("initial memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("reading memory VSTREAM: ");
+ while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ VSTREAM_PUTCHAR(ch);
+ VSTREAM_PUTCHAR('\n');
+ vstream_printf("final memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("seeking to offset %ld should work: ",
+ (long) fp->buf.len + 1);
+ vstream_fflush(VSTREAM_OUT);
+ if ((offset = vstream_fseek(fp, fp->buf.len + 1, SEEK_SET)) != fp->buf.len + 1)
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) fp->buf.len + 1);
+ vstream_printf("PASS\n");
+ vstream_fflush(VSTREAM_OUT);
+ vstream_printf("VSTREAM_GETC should return VSTREAM_EOF\n");
+ ch = VSTREAM_GETC(fp);
+ if (ch != VSTREAM_EOF)
+ msg_panic("unexpected vstream_fseek VSTREAM_GETC return: %d, expected: %d",
+ ch, VSTREAM_EOF);
+ vstream_printf("PASS\n");
+ vstream_printf("final memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+ vstream_fclose(fp);
+ vstring_free(buf);
+}
+
+ /*
+ * Exercise some of the features.
+ */
+
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Test buffer expansion and shrinking. Formatted print may silently
+ * expand the write buffer and cause multiple bytes to be written.
+ */
+ copy_line(1); /* one-byte read/write */
+ copy_line(2); /* two-byte read/write */
+ copy_line(1); /* two-byte read/write */
+ printf_number(); /* multi-byte write */
+ do_memory_stream();
+
+ exit(0);
+}
+
+#endif