Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  vstream.c

  Sprache: C
 

/*++
/* 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;
/*
/* void vstream_no_debug(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_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.
/*
/* vstream_no_debug() disables 'spontaneous' logging of output
/* activity on the last specified VSTREAM, to prevent recursive
/* logging.
/* 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 <sys/stat.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 */
     0000,   /* 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 */
     0000,   /* 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)

static VSTREAM *vstream_log_veto;

/* 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, 000);
}

/* 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_log_veto)
 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_log_veto && 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;

    /*
     * To set permissions on new files only, we need to distinguish between
     * creating a new file and opening an existing one.
     */

#define open_create(path, flags, mode) \
 open((path), (flags) | (O_CREAT | O_EXCL), (mode))
#define open_exist(path, flags, mode) \
 open((path), (flags) & ~(O_CREAT | O_EXCL), (mode))

    switch (flags & (O_CREAT | O_EXCL)) {
    case O_CREAT:
 fd = open_exist(path, flags, mode);
 if (fd < 0 && errno == ENOENT) {
     fd = open_create(path, flags, mode);
     if (fd >= 0) {
  if (fchmod(fd, mode) < 0/* can't uncreate */
      msg_warn("fchmod %s 0%o: %m", path, (unsigned) mode);
     } else if ( /* fd < 0 && */ errno == EEXIST)
  fd = open_exist(path, flags, mode);
 }
 break;
    case O_CREAT | O_EXCL:
 fd = open(path, flags, mode);
 if (fd >= 0)
     if (fchmod(fd, mode) < 0)  /* can't uncreate */
  msg_warn("fchmod %s 0%o: %m", path, (unsigned) mode);
 break;
    default:
 fd = open(path, flags, mode);
 break;
    }
    if (fd < 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);
}

/* vstream_no_debug - debug logging lockout */

void    vstream_no_debug(VSTREAM *stream)
{
    vstream_log_veto = 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, (long1);
    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

Messung V0.5 in Prozent
C=68 H=95 G=82

¤ Dauer der Verarbeitung: 0.32 Sekunden  (vorverarbeitet am  2026-08-09) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=738142