SFIO(3)

NAME
sfio - safe/fast string/file input/output

SYNOPSIS

#include	<sfio.h>
#define uchar	unsigned char
#define uint	unsigned int
#define ulong	unsigned long
Sfile_t*	sfnew(Sfile_t* f, uchar* buf, int size, int fd, int flags);
Sfile_t*	sfopen(Sfile_t* f, char* string, char* mode);
Sfile_t*	sfdopen(int fd, char* mode);
Sfile_t*	sfpopen(char* cmd, char* mode, Sfile_t** fcomp);
Sfile_t*	sfstack(Sfile_t* base, Sfile_t* top);
Sfile_t*	sfpushed(Sfile_t* f);
Sfile_t*	sftmp(int size);
int	sfpool(Sfile_t* f, Sfile_t* poolf, int mode);
Sfdisc_t*	sfsetdisc(Sfile_t* f, Sfdisc_t* disc);
int	sfclose(Sfile_t* f);
int	sfsync(Sfile_t* f);
int	sfpeek(Sfile_t* f, uchar** bufp);
int	sfgetc(Sfile_t* f);
int	sfungetc(Sfile_t* f, int c);
ulong	sfgetu(Sfile_t* f);
long	sfgetl(Sfile_t* f);
double	sfgetd(Sfile_t* f);
char*	sfgets(Sfile_t* f, char* buf, int size);
int	sfread(Sfile_t* f, uchar* buf, int n);
int	sfscanf(Sfile_t* f, char* format, ...);
int	sfsscanf(char* s, char* format, ...);
int	sfvscanf(Sfile_t* f, char* format, va_list args);
int	sfputc(Sfile_t* f, int c);
int	sfnputc(Sfile_t* f, int c, int n);
int	sfputu(Sfile_t* f, ulong v);
int	sfputl(Sfile_t* f, long v);
int	sfputd(Sfile_t* f, double v);
int	sfputs(Sfile_t* f, char* s, int c);
int	sfwrite(Sfile_t* f, uchar* buf, int n);
int	sfmove(Sfile_t* fr, Sfile_t* fw, long n, char* seps);
int	sfprintf(Sfile_t* f, char* format, ...);
int	sfsprintf(char* s, int size, char* format, ...);
int	sfvprintf(Sfile_t* f, char* format, va_list args);
void	sfnotice(void (*noticef)(Sfile_t* f, int type));
int	sfset(Sfile_t* f, int flags, int i);
uchar*	sfsetbuf(Sfile_t* f, uchar* buf, int size);
int	sffileno(Sfile_t* f);
int	sfeof(Sfile_t* f);
int	sferror(Sfile_t* f);
int	sfclearerr(Sfile_t* f);
int	sfclrlock(Sfile_t* f);
int	sfslen();
int	sfulen(ulong v);
int	sfllen(long v);
int	sfdlen(double v);
long	sforigin(Sfile_t* f);
long	sfseek(Sfile_t* f, long addr, int offset);
long	sftell(Sfile_t* f);
char*	sfecvt(double v, int n, int* decpt, int* sign);
char*	sffcvt(double v, int n, int* decpt, int* sign);

DESCRIPTION

sfio is a library of functions to perform input/output on objects called sfio streams. Each sfio stream may correpond to some file descriptor (see open(2)) or some piece of primary memory. A notion of stream stack is supported for processing of data from complexes of streams. Streams can be pooled so that their buffers can be synchronized properly when switching streams for io. It is also possible to change io disciplines by setting alternative functions for read, write and seek.

A stream abstraction is represented by the type Sfile_t which is defined in the header file <sfio.h>. A stream is locked while it is being accessed by some sfio function. A locked stream cannot be further accessed by operations that may change its internal states (see sfclrlock()). Any such access fails and returns an appropriate error code.

During an io request, if a system call read or write() (or their discipline counterparts) is interrupted, unless a discipline function has been defined to process it, the calling sfio function will resume the respective system call as necessary. The interrupt condition is defined by errno == EINTR (see errno.h). To prevent infinite loops, this condition is always cleared before the system call is resumed.

In general, sfio functions either return integer or pointer values. In the event of an error, a function that returns integer value will return -1 while a function that returns a pointer value will return NULL.

A number of bit flags define stream types and their operations. Following are the flags:

    SF_READ: The stream is readable.

    SF_WRITE: The stream is writable.

    SF_STRING: The stream is a string (a byte array) that is readable if SF_READ is specified or writable if SF_WRITE is specified.

    SF_APPEND: The stream is a file opened for appending data. This means that data written to the stream is always appended at the end of the file. On operating systems where there is no primitive to specify at file opening time that a file is opened for append only, lseek() (or its discipline replacement) will be used on the file stream to approximate this behavior.

    SF_RELATIVE: If the stream corresponds to a file, no seek is allowed backward beyond the starting point as defined by lseek(fd,0L,1) (or its discipline replacement) when the stream is initialized by sfnew() (below).

    SF_LINE: The stream is line-oriented. For write-streams, this means that the buffer is flushed whenever a new-line character is output. For read-streams, this means that sfpeek() (below) will return a buffer of data which ends with a new-line. Note that the amount of data that can be returned is limited by the buffer size.

    SF_KEEPFD: The file descriptor of the stream will be kept opened when the stream is closed.

    SF_MALLOC: To indicate that the stream buffer was obtained via malloc() and can be reallocated or freed by the package.

    SF_REUSE: This flag can be set (sfset() so that when the stream is closed, its data structure and associated information such as pool and discipline is not destroyed. It can also be used in a call to sfnew() (see below).

    SF_SHARE: This flag indicates that the associated stream is a file stream that may be operated on by means beyond straightforward sfio usage (e.g., by multiple processes). In this case, each io system call (or its discipline replacement) will be preceded by a lseek() (or its discipline replacement) to ensure that the logical stream location corresponds to the physical file location.

sfnew(f,buf,size,fd,flags) is the primitive for creating or renewing streams. For file streams, a number of operations are performed to determine seekability, optimal buffer sizes if not specified, etc. Each stream has a origin. The origin of a SF_STRING stream is always 0L. If a file stream is not seekable, its origin is defined as -1L. Otherwise, its origin is defined as either the current location or 0L depending on whether or not the flag SF_RELATIVE is turned on. sfseek() operations are relative to this location. The argument f of sfnew(), if not NULL, is a stream to be modified. If it is NULL, a new stream is created. The argument buf, if not NULL, is a buffer to be used. In this case, size should be positive. If size is 0, the stream is unbuffered. If size is negative, sfio will allocate a buffer. The argument fd is a file descriptor (e.g., from open()) for io operations if the stream is not an SF_STRING stream. The last argument flags is a bit vector composing from the flags described above. The SF_REUSE flag, if given, indicates that the current attributes of the stream f should be used instead of whatever else is defined by flags.

sfopen(f,string,mode) is a high-level function based on sfnew() to create new streams from files or strings. The argument f for sfopen(), if not NULL, is a currently opened stream to be closed and replaced by a new stream corresponding to the object string. The argument mode can be any one of: "r", "r+", "w", "w+", "a", "a+", s and s+. The r, w, and a specify read, write and append mode for file streams. In these cases, the argument string defines a path name to a file. The s specifies that string is a nul-terminated string to be opened for read. The + means that the new stream will be opened for both reading and writing.

sfdopen(fd,mode) makes a stream using the file descriptor fd. The mode argument is used in the same way as in sfopen().

sfpopen(cmd,mode,fcomp) opens a stream f which is a pipe to (from) the command cmd if the mode is "w" ("r"). If the mode is "w+" or "r+", another stream for the opposite operation is created and returned in fcomp. Note that if either of these streams is closed, the other is also closed.

sfstack(base,top) is used to push or pop stream stacks. Each stream stack is identified by a base stream via which all io operations are performed. Other streams on the stack are locked so that operations that may change their internal states are forbidden. The type of operations that can be done on a stack is defined by the top level stream. If an io operation is performed and the top level stream reaches the end of file condition or an error condition other than interrupts, it is automatically popped and closed (see also sfsetdisc for alternative handling of these conditions). The first argument of sfstack() specifies the base stream. The second argument, top, if not NULL, is pushed on top of the current top stream. In this case, the base stream pointer is returned. If top is NULL, the stack is popped and the pointer to the popped stream is returned.

sfpushed(f) returns the pointer to the stream pushed below f.

sftmp(size) creates a stream for writing and reading temporary data. If size is negative, the stream is a pure SF_STRING stream. Otherwise, the stream is originally created as a SF_STRING stream with a buffer of the given size. A discipline is set so that when this buffer is exhausted, a real temporary file will be created. Any attempt to change this discipline will also cause the temporary file to be created.

sfpool(f,poolf,mode) manages pools of streams. In a pool of streams, only one stream is current. A stream becomes current when it is used for some io operation. When a new stream is to become current, the current stream is synchronized (see sfsync()) if its type matches the type of the pool. The first argument of sfpool(), f, is the stream to be manipulated. The second argument, poolf, determines the operation to be done on f. If poolf is NULL, f is deleted from its current pool. Otherwise, f is put into the same pool with poolf. If poolf is already in a pool, the third argument is ignored. Otherwise, it determines the type of the new pool. mode can be constructed by bitwise or-ing of SF_READ and SF_WRITE.

sfsetdisc(f,disc) changes the io-discipline of the stream f, i.e., to specify alternative functions for read, write, seek, and to handle exceptions. The default discipline consists of the system calls read(), write(), and lseek(). The disc argument is either NULL to reset to the default discipline or a pointer to a Sfdisc_t structure which contains the following fields:

	int	(*readf)();
	int	(*writef)();
	long	(*seekf)();
	int	(*exceptf)();
	void*	handle;

The first three fields of Sfdisc_t specify alternative io functions. If any of them is NULL, the corresponding system call is used. A discipline io function, say (*readf)(), is called with 4 arguments. The first argument is the stream pointer. The second and third arguments correspond to the second and third arguments of the respected system call. The fourth argument is the handle field of Sfdisc_t. The exception function, (*exceptf)(), if provided, is called when an exception happens during a read/write operation, when a stream is being closed, or when the discipline is being reset. A read/write operation is said to cause an exception if its return value is zero or negative. It is up to the exception function to determine the type of exception (for example, by examining errno). When (*exceptf)() is called, the stream will be opened for general operations. However, (*exceptf)() should not attempt to close the stream. (*exceptf)() is called as: (*exceptf)(f,type,handle). type is: 0 when the discipline is being reset, SF_EOF when the stream is being closed, SF_READ when an exception happens during a read operation, and SF_WRITE when an exception happens during a write operation. For the cases of SF_READ and SF_WRITE, the executing sfio function will examine the return value of (*exceptf)() for further actions: negative for immediate return, zero for executing default actions associated with the exception, and positive for resuming execution. Note that a SF_STRING stream does not perform external io so the io functions are not used. However, an exception occurs whenever an io operation exceeds the stream buffer boundary and (*exceptf)(), if defined, will be called as appropriate. sfsetdisc() returns the pointer to the previous discipline or NULL if an error happened. Finally, it is the application's responsibility to manage the space used by the Sfdisc_t structures.

sfclose(f) closes the given stream f and frees up its resources. If f is NULL, all streams are closed. If f is a stack of streams, all streams on the stack are closed. If f is a sfpopen-stream, its companion stream, if any, is also closed. Further, sfclose() will wait until the associated command terminates, then return its exit status. A few file flags affect the behavior of sfclose(). If SF_KEEPFD is on, the underlying file descriptor is not closed. If SF_REUSE is on, sfclose() will only synchronize the buffer and close the file descriptor (subject to SF_KEEPFD). The stream structure is left intact, including pool (sfpool()) or discipline (sfsetdisc()) information.

sfsync(f) causes the physical file pointer of the stream f to correspond to its logical position. If f is the base of a stack of streams, all streams on the stack are synchronized. Further, a stacked stream can only be synchronized via its base stream.

sfpeek(f,bufp) provides a safe method for enquiring information on the internal buffer of a stream. If bufp is NULL, sfpeek() simply returns the amount of data available in the buffer to read if f is in read mode or the amount of buffer available to write if f is in write mode. If bufp is not NULL, sfpeek() provides access to the buffer. For a read stream, if the buffer is empty, it is filled and, for a write-stream, if the buffer is full, it is flushed. Then, for a read stream, bufp is set to the place in the buffer where data is available and, for a write stream, it is set to where data can be written. The return value of sfseek() indicates how much data or space is available in the buffer. However, if the stream is in SF_LINE|SF_READ mode, the return value will be the data length up to and including the new-line character. In this case, if there is not a new-line character in the buffered data, more data may be read. Note that the buffer location is not advanced by sfpeek(). That must be done by a regular io call such as sfread or sfwrite on the pointer returned in bufp. Finally, sfpeek() treats a read/write-stream like a read-stream (however, see also sfset()).

sfgetc(f) returns a byte from the stream f or -1 when an end-of-file or error condition is encountered.

sfungetc(f,c) puts the byte c back into the stream f. This is guaranteed to work only after a sfgetc() call.

sfgetu(f), sfgetl(f), and sfgetd(f) return an unsigned long, a long value, or a double value that was coded in a portable fashion (see sfputu(), sfputl(), and sfputd()). If there is not enough data to decode a value, these functions will return -1 and the stream is set in an error state (see sferror()).

sfgets(f,buf,size) reads a line of input from the stream f. If buf is not NULL and size is positive, sfgets reads up to size-1 characters into the buffer buf. Otherwise, the characters are read into a static area that is dynamically grown as necessary. Thus, in this case, there is no limit to line length. A nul-character is appended after the input characters. sfgets() returns the pointer to the new string or NULL when no data was read due to end-of-file or an error condition. After a string is read, its length can be found using sfslen().

sfread(f,buf,n) reads up to n bytes from the stream f and stores them in the given buffer buf. It returns the number of bytes actually read.

sfscanf(f,format,...) scans a number of items from the stream f. The item types are determined from the string format. See fscanf() (UNIX User's Manual, Section 3) for details on predefined formats. The standardly supported formats are: i, I, d, D, u, U, o, O, x, X, f, F, e, E, g, G, c, %, s, and []. The sfscanf() interface also supports additional formats as described below.

    The pattern %& indicates that the next argument in the argument list of sfscanf() is a function, say (*extf)(), to process patterns that are not predefined by the sfscanf() interface. The prototype of (*extf)() is:
    	int (*extf)(Sfile_t* f, int fmt, int length, char** rv);
    f is the same input stream passed to sfvscanf. fmt is the pattern to be processed. length, if non-negative, is the maximum number of input bytes to be read in processing the pattern, rv is used to return the ``address'' of the value to be assigned. (*extf)() returns the size of the value to be assigned. A negative return value from (*extf)() means that the specified pattern cannot be handled. This pattern is treated as if it is not matched.

    The pattern %@ indicates that the next argument in the argument list args is a function, say (*argf)(), to process the values of matched patterns. The prototype of (*argf)() is:
    	int (*argf)(int fmt, char* value, int n);
    If the return value of (*argf)() is negative, the processing of the current format string will be stopped (see %$ below). fmt determines the type of value: f for float, F for double, h for short, d for int, D for long, s for char*. Any other value for fmt means that it is an extended pattern and value contains an address to the scanned value. n contains the size of the object if it is a primitive type. If the object is char* or the address of the scanned value of an extended format, n is the length of this object.

    The pattern %: indicates that the next two arguments in the argument list args define a new pair of format string and a list of arguments of the type va_list (see varargs.h or stdarg.h). The new pair is pushed on top of the stack and the scanning process continues with them. The top pair of format string and argument list is popped when the processing of the format string is stopped. When a new pair is stacked, (*argf)() and (*extf)() are inherited. They are reset when the stack is popped.

sfsscanf(s,format,...) is similar to sfscanf() but it scans data from the string s.

sfvscanf(f,format,args) is the primitive underlying sfscanf() and sfscanf(). It also provides a portable variable argument interface. Programs that use sfvscanf() must include either of varargs.h or stdargs.h as appropriate.

sfputc(f,c) writes the byte c to the stream f.

sfnputc(f,c,n) writes the byte c to the stream f n times. It returns the number of bytes successfully written.

sfputu(f,v), sfputl(f,v) write the unsigned long or long value v in a format that is byte-order transparent. sfputd(f,v) writes the double value v in a portable format. Portability across two different machines requires that the bit order in a byte is the same on both machines. sfputd() also relies on the functions ldexp() and frexp() (See frexp.3) for coding. Upon success, sfputu(), sfputl() and sfputd() return the number of bytes output.

sfputs(f,s,c) writes the null-terminated string s to the stream f. If c is not 0, it is a character to be appended after the string has been output. sfputs() returns the number of bytes written.

sfwrite(f,buf,n) writes out n bytes from the buffer buf to the stream f. It returns the number of bytes written.

sfmove(fr,fw,n,seps) moves n objects from the stream fr to the stream fw. If either fr or fw is NULL, it acts as if it is a stream corresponding to /dev/null. If n is <0, all of fr is moved. If seps is NULL or an empty string, the objects to be moved are bytes. Otherwise, the moved objects are records separated by bytes defined in seps. In seps, if the first two bytes is \0, it is mapped to the zero byte. All other cases map a byte to itself. sfmove() returns the number of objects moved.

sfprintf(f,format,...) writes out data in a format as defined by the string format. See fprintf() (UNIX User's Manual, Section 3) for details on predefined conversion formats. The standardly supported formats are: n, s, c, %, h, i, d, p, u, o, x, X, g, G, e, E, f, and F. sfprintf() also supports additional formats as described below.

    The pattern %& indicates that the next argument is a function, say (*extf)(), to interpret patterns not yet defined by sfprintf(). The prototype of (*extf)() is:
    	int (*extf)(void* value, int fmt, int precis, char** sp);
    value is the value to be formatted. fmt is the pattern to format the value. precis is the amount of precision required. sp is used to return the address of a string containing the formatted value. If upon returning from (*extf)(), *sp is NULL, the pattern fmt is treated as if it is not matched. Otherwise, the return value of (*extf)(), if nonnegative, is taken as the length of the string returned in sp. If not, the string is considered null-terminated. The string *sp is processed as if the pattern `s' was specified.

    The pattern %@ indicates that the next argument is a function, say (*argf)(), to get arguments. As long as (*argf)() is defined, the argument list is ignored. The prototype of (*argf)() is:
    	int (*argf)(int fmt, char* val);
    fmt is the pattern to be processed. Following are ASCII characters and corresponding types: @ for getting a new (*argf)(), & for getting a new (*extf)(), 1 for getting a new format string for stacking, 2 for getting a new argument list for stacking, d for int, D for long, f for float, F for double, and s for char*. If (*extf)() is defined, and an undefined pattern is encountered, (*argf)() will be called with this pattern. val is an address to store the value to be formatted. The return value of (*argf)(), if negative, stops the processing of the current format (see below).

    The pattern %: indicates that the next two arguments define a pair of format string and argument list of the type va_list. If the argument getting function (*argf)() is already defined, it is called with the argument fmt being the characters 1 and 2 for the new format string and argument list respectively. The new pair is stacked on top and processing continue from there. The top pair of format string and argument is popped when the format string is exhausted. When a new pair is pushed, (*argf)() and (*extf)() are inherited. When a pair is popped, these functions will be reset.

sfsprintf(s,size,format,...) is similar to sfprintf() but it is used to format the character array s which is of size size. The length of the resulting string can be gotten via sfslen().

sfvprintf(f,format,args) is the primitive underlying sfprintf() and sfsprintf(). It provides a portable variable argument interface. Programs that use sfvprintf() must include either of varargs.h or stdargs.h as appropriate.

sfnotice(noticef) sets a function (*noticef)() which will be called whenever a stream is created or closed. (*noticef)() is called with two arguments. The first argument is the stream pointer and the second argument is either 0 or SF_EOF to indicate whether the stream is being opened or being closed.

sfset(f,flags,i) sets flags or file descriptor for the stream f. If flags is the value SF_EOF, the file descriptor of the stream is changed to the value in i. In this case, sfset() returns -1 on error or i on success. If flags is not SF_EOF, it defines a collection of flags to be turned on or off depending on whether i is non-zero or zero. The flags that can be turned on or off are: SF_READ, SF_WRITE, SF_LINE, SF_KEEPFD, SF_REUSE, SF_MALLOC and SF_SHARE. The flags SF_READ and SF_WRITE can be used in a call to sfset() only if the stream f was opened for both read and write. Turning off one of these flags means that the stream is to be treated as if it was opened with the other flag exclusively (see sfpeek()). In this case, sfset() returns the entire set of flags controlling the stream. Thus, the current set of flags can be found by sfset(f,0,0).

sfsetbuf(f,buf,size) changes the current buffer of the stream f to the new buffer buf. If the stream is a SF_WRITE stream, any data still in the current buffer is thrown away. Thus, if an application desires to preserve such data, it should call sfsync() before trying to switch buffers. If size is positive, buf is taken as a buffer of the given size. If size is zero, the stream will be unbuffered. If size is negative, an internal buffer is allocated. sfsetbuf() returns the address of the old buffer.

sffileno(f) returns the file descriptor of the stream f.

sfeof(f) tells whether there is any more data in the stream f.

sforigin(f) returns the origin location in the stream f (see sfnew()). If this location is -1L, the stream is not seekable. Note that the standard streams sfstdin, sfstdout, and sfstderr, though statically allocated, are not initialized until an operation that may affect its internal structure. Thus, the return value 0L of sforigin() on such an initialized stream is not reliable.

sferror(f) and sfclearerr(f) returns or clears the error condition of the stream f. Note that the error condition of a stream does not prevent further io operations to be performed on them.

sfclrlock(f) clears the lock on a locked stream. Though this is unsafe, it is useful for emergency access to a locked stream or to clear a stream left locked because of non-local jumps (e.g., longjmp()).

sfslen() returns the length of the string most recently obtained via a sfgets(), sfsprintf(), sfecvt() or sffcvt() call.

sfulen(v), sfllen(v) and sfdlen(v) return the number of bytes required to code the unsigned long, long or double value v.

sfseek(f,addr,offset) sets the next read/write location for the stream f at a new address defined by the combination of addr and offset. If offset is 0, addr is offset from the origin of the stream (see sfnew()). If offset is 1, addr is offset from the current location. Note that if f was opened for appending (SF_APPEND) and the last operation done on it was a write operation, the current location is at the physical end of file. If offset is 2, addr is offset from the physical end of the stream. In all cases, sfseek() is not allowed to seek backward beyond the stream origin.

sftell(f) returns the current location in the stream f relative to the stream origin (see sfnew()). As with sfseek(), if f was opened for appending (SF_APPEND) and the last operation done on it was a write operation, the current location is at the physical end of file. If the stream f is unseekable, sftell returns the number of bytes read from or written to f.

sfecvt(v,n,decpt,sign) and sffcvt(v,n,decpt,sign) are functions to convert floating values to ASCII. They corresponds to the standard functions ecvt() and fcvt(). The length of the conversion string most recently done by sfecvt() or sffcvt() can be found by sfslen().

HISTORY AND FUTURE CONSIDERATIONS
sfio has similar functionality, but is more general than the stdio package. It grows from our dissatisfaction with the awkwardness, fragility and inefficiency in stdio. An example of stdio awkwardness is that even if a stream was opened for read and write, the application code cannot arbitrarily mix read and write operations. An earlier attempt was made at rewriting stdio. This failed due to problems that arise when linking with code based on stdio. Changing the name space reduces this type of problems. It also allows us to both stream-line and extend the interface as appropriate.

AUTHORS
Kiem-Phong Vo (att!ulysses!kpv) and David G. Korn (att!ulysses!dgk).