Streams Library version 1.3
Copyright (C) Angus J. C. Duggan 1997-1998
This library is used by several of my programs, including my NNTP buffer and
SMTP buffer Windows NT services. It provides abstractions of socket and file
streams, some common network protocol methods, and some other useful stream
abstractions.
INSTALLATION:
The library should be compiled and installed in a publically-accessible
directory on your LIB path. The include file streams.h should be placed in a
publically-accessible directory on you INCLUDE path.
INTERFACING:
Most functions in the library return an invalid result when they fail, and
use GetLastError (Win32) or errno (Unix/POSIX) to return an error code. Some
streams allow an error callback function to be supplied, which is used for
non-fatal errors which don't return invalid results. The error callback is
called with the stream, an error code, and parameters specific to the error.
The pre-defined streams define two values for the code,
SOCKSTREAM_ERROR_CLOSE and FILESTREAM_ERROR_CLOSE, which indicate that a
close socket on a socket stream or a close file on a filestream failed. The
parameters to these are the handle of the file or socket that had the error,
and the actual error code returned by GetLastError()/errno.
If you are using a debug compilation of the library, you will need to provide
the following functions:
void stream_debugstr(long cookie, char *string, int length) ;
void stream_debug(long cookie, char *format, ...) ;
The cookies are values that get passed back to the stream_debug*
routines to identify where the message originated. stream_debugstr is called
with a cookie, a string and a length, which can be used for writing out debug
information. stream_debug takes a printf-style format string and can be used
to write out debug information. It should support "%d" and "%s" formats at
the minimum. The following cookies are used by the pre-defined streams:
SOCKSTREAM_DEBUG 0x524f434b /* 'SOCK' */
FILESTREAM_DEBUG 0x46494c45 /* 'FILE' */
STDIOSTREAM_DEBUG 0x5354494f /* 'STIO' */
LINESTREAM_DEBUG_READ 0x4c494e52 /* 'LINR' */
LINESTREAM_DEBUG_WRITE 0x4c494e57 /* 'LINW' */
LIBRARY:
The library provides the following types:
Stream
This is an opaque type used for passing arguments to and from library
functions. In general, you should let the library deal with
allocating and deallocating these.
The library provides the following generic stream functions:
NewStream
Stream *NewStream(Stream *this, Stream *under, short subclassbytes,
Handler reader, Handler writer, Ioctl ioctl,
Error error, Destructor destructor) ;
typedef long Handler(Stream *this, void *buffer, long bufsize) ;
typedef long Ioctl(Stream *this, int op, va_list args) ;
typedef void Error(Stream *this, int err, va_list args) ;
typedef void Destructor(Stream *this) ;
This function creates a new stream, or initialises an existing Stream
variable. "under" is the underlying stream, which the handlers
"reader" and "writer" will read from. "ioctl" is a specialised I/O
handler, "error" is an error callback for non-fatal errors, and
"destructor" is a specialised function to destroy the stream.
"subclassbytes" is the number of bytes required in a subclassed
stream structure.
In general, you should not call this function directly, but wrap it
in an abstraction layer such as the MakeSocketStream or
MakeFileStream below.
If an existing stream is supplied, it *must* have been originally
created by NewStream.
The reader and writer handlers get called with the stream itself, and
the buffer and length parameters given to the ReadStream or
WriteStream function. They return the number of bytes read or written
to or from the stream. They should return -1 on error, and use
SetLastError/errno to set an appropriate error code.
The ioctl handler gets called with the stream, an operation, and a
va_args list. It should ignore the standard ioctl codes if it does
not want to prevent them. It should return -1 on failure, and use
SetLastError/errno to set an appropriate error code. If an ioctl
handler is not supplied, Ioctls will be forwarded to the underlying
stream. If an ioctl handler is supplied, and unrecognised Ioctls
should be passed to the underlying streams, this should be done by
calling IoctlStreamArgs() with the underlying stream as the first
argument. If forwarding of unrecognised ioctls is not desired, a
handler should be created which returns -1 for such codes. Ioctl
codes should be kept unique across all streams; this is done in the
pre-supplied streams by using a long hex value representative of the
stream name and Ioctl function.
The error handler gets called with the stream, an error code, and a
va_args list. This will usually be supplied by a consumer of a
stream. Errors in the destructor will usually be logged through this
handler. If the handler is not supplied, errors logged through
ErrorStream will be ignored.
The destructor handler should free any extra resources attached to
the stream, and close any handles stored in it. Errors should be
logged through the error function. No return code is set. It should
not affect the underlying stream, or free the stream pointer itself.
NewStream returns NULL if an error occurred, and sets an error code
that can be examined with GetLastError()/errno.
DestroyStream
Stream *DestroyStream(Stream *this) ;
DestroyStream destroys a single stream, calling the stream destructor
and freeing the stream memory if necessary. It returns the underlying
stream, so to free a chain of streams, you can write:
while ( stream )
stream = DestroyStream(stream) ;
ReadStream
long ReadStream(Stream *this, void *buffer, long bufsize) ;
ReadStream reads bytes from a stream into a buffer. There are two
modes of operation which may be supported by streams. The
non-buffered mode requires the caller to supply a buffer into
which the data will be put, whereas the self-buffered mode lets the
stream implementation allocated a buffer itself. Some streams may
support only one mode or the other.
If bufsize is not zero, the call is a non-buffered read, and
the stream will put up to the number of bytes specified into the
buffer given, and return the number of bytes read.
If bufsize is zero, the ReadStream will return the number of bytes
available, and will store a pointer to the data in the address
passed in the buffer parameter. The stream implementation will
assume that all of the bytes have been consumed before the next call.
ReadStream checks the error and eof flags, before passing the call
to the read handler to actually implement the read.
ReadStream returns -1 if an error occurs, and sets an error code that
can be looked at with GetLastError()/errno.
WriteStream
long WriteStream(Stream *this, void *buffer, long bufsize) ;
WriteStream writes out a number of bytes up to bufsize from buffer to
a stream. It returns the number of bytes actually written.
WriteStream checks the error and eof flags, before passing the call
to the write handler to actually implement the read.
WriteStream returns -1 if an error occurs, and sets an error code that
can be looked at with GetLastError()/errno.
WriteStreamAll
long WriteStreamAll(Stream *this, void *buffer, long bufsize) ;
WriteStreamAll is similar to WriteStream, but ensures that the number
of bytes in the buffer is written out unless an error occurs. It
returns a true value if there was no error, and a false value if
there was an error, in which case it sets an error code that can be
looked at with GetLastError()/errno.
IoctlStream
long IoctlStream(Stream *this, long op, ...) ;
enum { STREAM_CLEAREOF = 0x43454f46, /* 'CEOF' */
STREAM_CLEARERR = 0x43455252, /* 'CERR' */
STREAM_REWIND = 0x52574e44, /* 'RWND' */
STREAM_POSITION = 0x504f534e /* 'POSN' */ } ;
IoctlStream performs the I/O control operation specified. Some
streams may not support all or any operations, and some may provide
extra operations. The variable arguments are used for extended
operations provided by specialised streams.
The standard ioctls are:
STREAM_CLEAREOF
Clears the EOF condition on a stream, allowing another read
or write to be attempted.
STREAM_CLEARERR
Clears the error condition on a stream, allowing another read
or write to be attempted.
STREAM_REWIND
Rewinds the stream to the start if possible.
STREAM_POSITION
Positions the stream at the position specified by the
argument (a long), if possible.
IoctlStream returns -1 if an error occurred, and sets an error code
that can be looked at with GetLastError()/errno.
If an ioctl handler is not supplied by a stream, requests will
automatically be forwarded to underlying streams.
IoctlStreamArgs
long IoctlStreamArgs(Stream *this, long op, va_list args) ;
IoctlStreamArgs is provided for authors of new streams. It is used
for forwarding unrecognised Ioctls for underlying streams, as in this
code fragment:
long myioctl(Stream *me, long op, va_list args)
{
if ( op == MY_IOCTL ) {
/* Do whatever I my operation implies */
return 0 ;
} else {
return IoctlStreamArgs(UnderlyingStream(me), op, args) ;
}
}
This function is only available if is included.
LockStream and UnlockStream
void LockStream(Stream *this) ;
void UnlockStream(Stream *this) ;
The LockStream and UnlockStream calls should be used with extreme
care, or not at all. They grant exclusive access to the stream for
the calling thread. Note that LockStream and UnlockStream are
implicitly used in ReadStream and WriteStream, so you don't need to
use them for single reads or writes.
SendStream
long SendStream(Stream *this, char *format, ...) ;
SendStream sends a printf-type formatted string to a stream. The
format codes supported are:
%d Decimal integer
%s Null-terminated string
%c Character
%t NNTP-style date and time, from FILETIME parameter.
(May not be available on all operating systems.)
%% Percent character
The maximum size of the string with format replacements is BUFSIZ.
SendStream returns a the number of bytes written if there were no
errors, or -1 if there were. In this case it sets an error code that
can be looked at with GetLastError()/errno.
The library provides the following specialised functions for particular
stream types:
MakeSocketStream
Stream *MakeSocketStream(StreamSocket socket, Error socketerror) ;
MakeSocketStream provides a stream interface to an existing socket.
Socket streams can be read in self-buffered or non-buffered mode.
Socket writes are passed straight through to the underlying socket.
MakeSocketStream returns NULL on error, and sets an error code
that can be looked at with GetLastError()/errno.
Socket streams do not support the STREAM_REWIND or STREAM_POSITION
ioctl operations.
The socketerror callback supplied may be called with one error code,
SOCKSTREAM_ERROR_CLOSE. The extra parameters to the error handler
will be the socket handle and the error number as returned by
GetLastError()/errno.
The StreamSocket type is a typedef for SOCKET on Win32 or socket on
Unix/POSIX.
MakeFileStream
Stream *MakeFileStream(char *filename, int access, Error fileerror) ;
MakeFileStream provides a stream interface to an existing file.
File streams can be read in self-buffered or non-buffered mode.
File writes are passed straight through to the underlying file.
The access parameter is one of FILESTREAM_READ, FILESTREAM_WRITE, or
FILESTREAM_READWRITE.
MakeFileStream returns NULL on error, and sets an error code
that can be looked at with GetLastError()/errno.
File streams support the STREAM_REWIND and STREAM_POSITION ioctl
operations.
The fileerror callback supplied may be called with one error code,
FILESTREAM_ERROR_CLOSE. The extra parameters to the error handler
will be the socket handle and the error number as returned by
GetLastError()/errno.
MakeStdioStream
Stream *MakeStdioStream(FILE *fp) ;
MakeStdioStream allows standard I/O streams to be used through the
Streams interface. Stdio streams can be read in self-buffered or
non-buffered mode. Stdio writes are passed straight through to the
underlying file.
MakeStdioStream returns NULL on error, and sets an error code
that can be looked at with GetLastError()/errno.
Stdio streams support the STREAM_REWIND ioctl and STREAM_POSITION
operations.
MakeLineStream
Stream *MakeLineStream(Stream *under, char *term) ;
enum { LINESTREAM_TERM = 0x4c494e54, /* 'LINT' */
LINESTREAM_AGAIN = 0x4c494e41 /* 'LINA' */ } ;
Line streams read or write a line at a time from an underlying
stream. Line streams operate in self-buffered read mode only, and
the underlying stream for a line stream must also support
self-buffered read mode.
The line terminator specified is included in the line read or written.
There are two extra ioctls for line streams:
LINESTREAM_TERM
This takes an extra parameter, which is a string (char *) to
which the line terminator is set.
LINESTREAM_AGAIN
This causes the immediately previously read line to be
available again for the next ReadStream call.
MakeLineStream returns NULL on error, and sets an error code
that can be looked at with GetLastError()/errno.
Reponse
long Response(Stream *this) ;
Response reads a line from a line stream and returns the decimal
numeric value of the digits at the start of the line, or -1 on error.
Reason
long Reason(Stream *this, char *buffer, int bufsize) ;
Reason copies up to bufsize-1 characters from the last line of a line
stream into the supplied buffer, and NULL-terminates the buffer. It
returns the number of bytes copied into the buffer.
MakeDotStream
Stream *MakeDotStream(Stream *under) ;
Dot streams are read-only, and operate in self-buffered read mode.
They read up to a line containing a single dot followed by CR and LF.
The dot line is not returned as part of the stream. The underlying
stream for a dot stream should be a line stream.
MakeDotStream returns NULL on error, and sets an error code
that can be looked at with GetLastError()/errno.
MakeHeadStream
Stream *MakeHeadStream(Stream *under) ;
Head streams are read-only, and operate in self-buffered read mode.
They read up to the first line containing just CR followed by LF.
The CR-LF line is not returned as part of the stream. The underlying
stream for a head stream should be a line stream.
MakeHeadStream returns NULL on error, and sets an error code
that can be looked at with GetLastError()/errno.
MakeBodyStream
Stream *MakeBodyStream(Stream *under) ;
Body streams are read-only, and operate in self-buffered read mode.
They read everything beyond the first line containing just CR
followed by LF. The CR-LF line is not returned as part of the stream.
The underlying stream for a body stream should be a line stream.
MakeBodyStream returns NULL on error, and sets an error code
that can be looked at with GetLastError()/errno.
MakeSearchStream
Stream *MakeSearchStream(Stream *under, void *search, int len) ;
enum { SEARCHSTREAM_RESULT = 0x53434852 , /* 'SCHR' */
SEARCHSTREAM_AGAIN = 0x53434841 /* 'SCHA' */ } ;
Search streams are read-only. They search for a pattern of bytes in a
stream, returning EOF when the pattern is found. The result can be
tested through the SEARCHSTREAM_RESULT ioctl, which returns a positive
value if the pattern was found. The search can be reset with the
SEARCHSTREAM_AGAIN ioctl.
MakeSearchStream returns NULL on error, and sets an error code
that can be looked at with GetLastError()/errno.
CREATING NEW STREAMS:
New streams can be easily defined. A new stream which does not
contain data can be defined with a call to NewStream with the
subclassbytes parameter set to zero. A new stream which requires data
should define a structure to store the data; the first element of
this structure must be a stream pointer. The sub-class should avoid
manipulating this pointer. The call to NewStream should have the size
of the structure as the subclassbytes parameter. If pointers to
dynamically allocated memory are stored in this structure, the stream
should define a destructor to clean up after itself.
The handler, ioctl, error and destructor routines should cast the
incoming Stream pointer into the sub-class before using sub-class
fields.
For example, the following structure defines a sub-class with some
extra data. The outline of a constructor function and the call to
NewStream are shown:
typedef struct {
Stream *base ; /* Do not manipulate this pointer */
int my_data ;
} MyStream ;
Stream *MakeMyStream(Stream *under, int data)
{
Stream *this = NewStream(NULL, under, sizeof(MyStream),
mystream_reader, mystream_writer,
mystream_ioctl, mystream_error,
mystream_destructor) ;
if ( this ) {
MyStream *mine = (MyStream *)this ;
mine->my_data = data ;
}
return this ;
}
AJCD 23rd October 1998
Wrapped on 23rd October 1998 by angus@harlequin.com