Multi-Disk I/O Package
----------------------

This is an input/output package designed to allow a file to span
multiple diskettes.  It mimics much of the functionality of the stdio
package.  When writing to a file, the flow of control goes something
like this:

   1) A file is opened with mdopen().  Information is written to the
   open file using mdputc(), mdwrite(), or both.  When there is no more
   room to write data onto the given file, a user callback `ateof'
   function is called.  If we want to continue, we will return a zero.

   2) The file is closed, and another user callback function is called,
   this time to prompt the user between diskettes.  If we want to
   continue, we return a 1.

   3) It is assumed that a new diskette is now inserted.  A third user
   callback function `filenm()' is called, which returns the name of the
   file to be opened on this diskette.

   4) The file is opened for writing, and writing continues.

When reading from a file, the flow of control goes something like this:

    1) A file is opened with mdopen().  Information is read from the
    file using mdgetc(), mdread, or both.  When there is no more data
    left in the file, a user callback `ateof' function is called.  If
    this is *really* the end of the file (i.e. this is the last diskette
    we are expecting), then we return a 1, and an EOF is returned to the
    caller of the mdgetc() or mdread().  Otherwise, we return a zero,
    and continue from step 2 (above).

Data Structures
---------------

struct mdiskbuf {
	char   *bufptr;			/* pointer to next position to fill */
	int	bufcnt;			/* number of bytes left to fill */
	char   *buffer;			/* buffer of data */
	char   *basename;		/* base name of the file */
	char   *filename;		/* constructed filename */
	int	seq;			/* next sequence number to be used */
	char	fid;			/* file id */
	char	spare;			/* in case we can't allocate a buffer */
	char	flags;			/* high level flags */
	int	oflag;			/* file creation flags */
	char *(*filenm)();		/* to construct new filename */
	int   (*ateof)();		/* at *final* EOF? */
	int   (*prompt)();		/* to prompt between diskettes */
};
#define	MDFILE	struct mdiskbuf

Some of these fields are specifically for the mdiskio package, and some
are for the user program.  Specifically, the bufptr, bufcnt and buffer
fields are to keep track of internally buffered data.

The `basename' is a pointer to a copy of the filename passed to
mdopen().  Space for the copy is malloc()'d.  The `filename' field is
normally a pointer to the same place as `basename', but may completely
be under user control.  The only mdiskio routine which accesses this
field is the default (*filenm)() function, which may be overridden.
This function is described in greater detail under `mdopen'.

The `seq' field is not used at all by the mdiskio package.  It is
strictly for the user program.  The supposition is that the user may
wish to keep track of the diskette number, so a convenient place is
provided.

The `fid' field is the file id, as returned from open(2).  It should not
be modified by the user program.

The `spare' field is used as a single byte buffer, if no buffer space is
available.  Normally the buffer space is malloc()'d.

The `flags' field contains buffer information, such as whether the
buffer is empty, whether it has been read from or written to, as well as
whether we are at the *real* EOF, and if an error has occured.

The `oflags' field contains the flags that are to be passed to each call
to open().  Since a file must be opened on each diskette, we must keep
track of how the user program wants it opened.

The `filenm' field points to a function which takes a pointer to an
mdiskbuf structure as its sole parameter, and returns a pointer to the
name of a file to be opened.  The user may supply his/her own function,
if for instance they wish to return "filename.001", "filename.002", etc.
based on the sequence number.  The default function simply returns the
`basename' pointer.

The `ateof' field points to a function which takes a pointer to an
mdiskbuf structure and an access-type integer as its parameters.  It is
called whenever the mdiskio package hits end-of-file, in order to ask
the user program if this is *really* the end-of-file, or whether there
is another file on a different diskette to open.  This function returns
1 if we are *really* at the end of the file, and 0 if there is another
diskette to be accessed.  The default routine says that input files are
always at EOF, and that output files are never at EOF.  This allows
regular (non-spanning) files to be read properly.  The default function
will be discussed in greater detail below.

The `prompt' field points to a function which takes a pointer to an
mdiskbuf structure.  It's function is to prompt the user to take some
action, such as putting a new diskette in the drive.  If all has gone
well, it should return 1, and return a zero if something has gone wrong.

Functions
---------

MDFILE *mdopen(char *name,char *mode,char *(*fnfilenm)(),int seq);

    This function opens a named file with a given mode.  The name
    is any name which is valid as a parameter to `fopen'.  The mode
    may be one of the following strings:

        "r"     Opens for reading.  If the file does not exist, or
                cannot be found, the `mdopen' call will fail.

        "w"     Opens an empty file for writing.  If the given file
                exists, its contents are destroyed.

        "a"     Opens for writing at the end of the file (appending).
                The file is created if it does not exist.

        "r+"    Opens for both reading and writing.  The file must
                exist.

        "w+"    Opens an empty file for both reading and writing.  If
                the given file exists, its contents are destroyed.

        "a+"    Opens for reading and appending.  The file is first
                created if it does not exist.

    Additionally, one of the following characters may be appended
    (or inserted after the '+'):

        t       Open in text (translated) mode.  In this mode, CR-LF
                combinations are translated into single LF's in input
                and output LF's are translated to CR-LF.  Note: this is
                operating system dependent.  It has been included for
                MS/PC DOS systems.

        b       Open text in binary (untranslated) mode.  The above
                translations are suppressed.

    The `fnfilenm' parameter points to a function which takes a pointer
    to an open MDFILE and returns a filename.  It is called before the
    file is actually opened, so that it takes effect even for the first
    segment of a file.  If it is NULL, a default function is called
    which simply returns a pointer to the supplied filename.  The
    function (either the default or the user supplied one) is stored and
    called each time a new file (i.e.  on a new diskette) is to be
    opened.

    The `seq' parameter is saved as the initial sequence number.  It is
    never accessed by the mdiskio routines, but is available to the user
    callback functions within the mdiskbuf structure.

    Of special note here, because I can't think of where else to put it,
    is that mdopen() sets up a few default callback functions.  One is
    mdateof() as the `ateof' function which is described below.  The
    other is a default prompt function.  This function writes a message
    to the stderr, and waits for a carriage return or ENTER key from the
    keyboard.  Note that it gets the character from the keyboard, not
    from stdin.

int	mdclose(MDFILE *mdp);

    The open file pointed to by `mdp' is closed.  If it is open for
    output, its buffers are first flushed.  After closing, any allocated
    buffers are freed.  There is currently no return value.


int	mdflush(MDFILE *mdp);

    If the file pointed to by `mdp' is open for writing, all buffered
    data is written to the file.  If the file is open for reading, the
    the buffer is cleared.  If the file is open in read/write mode (e.g.
    "r+"), input operations and output operations must be separated by a
    call to `mdflush' (or to `mdseek' which forces a flush).


long	mdtell(MDFILE *mdp);

    This function takes an open file pointer and returns the current
    position, relative to the beginning of the file ON THIS DISKETTE.
    This position is the number of bytes from the beginning of the file.
    It does not take into account the size of files on previous
    diskettes.


long	mdseek(MDFILE *mdp,long c,int orig);

    This function sets the current position within the file.  If `orig'
    is 0, `c' is an offset from the beginning of the file, and had
    better be positive.  If `orig' is 1, `c' is relative to the current
    position, and may be positive or negative.  If `orig' is 2, `c' is
    relative to the end of the file, and usually is negative.  Seeking
    past the end of file is operating system dependent.

    Data is flushed from the buffer before the seek is attempted.  Thus
    mdseek(mdp, 0L, 1) is equivalent to mdflush(mdp).

    Note: All seeking is relative to the current diskette.  You may not
    seek across diskettes.

    This function returns 0 if the seek worked correctly, and non-zero
    if it did not.


int	mdsetbuf(MDFILE *mdp, int blen);

    A buffer of size `blen' bytes is allocated for the open file.  If
    any I/O operations have previously been done, this may result in
    lost data.  If a buffer has already been allocated, a 1 is returned.
    If there is not enough memory to allocate a buffer of the requested
    size, a 2 is returned.  In this case, a default buffer (usually 512
    or 1024 bytes, but system dependent) will still be allocated on the
    first I/O operation.  If all goes well, a zero is returned.


int	mdgetc(MDFILE *mdp);

    This function returns a character from the specified file.  If the
    *real* EOF has been encountered, it returns EOF.

    Note: this is implemented as a macro.


int	mdread(char *ptr,int size,int nitems,MDFILE *mdp);

    This function reads `nitems' of `size' bytes each from the specified
    file, and copies the data into the location indicated by `ptr'.
    This had better be large enough to accomodate the data.  It returns
    the number of items fully read.

    Note that multiple diskettes may be spanned during a single call to
    this function.  The appropriate callback functions are called at the
    appropriate time to ensure that this is handled correctly.  This
    function will not return to the caller until the data has been read
    (or a *real* EOF has been encountered).


int	mdungetc(int c,MDFILE *mdp);

    The character in `c' is pushed back into the buffer for the supplied
    open file.  It is not guaranteed for more than one character.  It
    should only be used for files opened for reading or read/writing.


int	mdputc(int c,MDFILE *mdp);

    This function writes a character `c' to the specified open file.  It
    returns the character written.

    Note: this function is actually implemented as a macro.

int	mdwrite(char *ptr,int size,int nitems,MDFILE *mdp);

    This function writes `nitems' of `size' bytes each to the specified
    file.  The data is taken from the location pointed to by `ptr'.  It
    returns the number of items fully read.

    See the note above under mdread().


mdsetbase(MDFILE *mdp,char *name);

    This macro allows the user program to change the basename of the
    file after it has been opened.  This is of little use, actually, but
    could possibly be used to switch between filenames when spanning
    diskettes.


mdsetseq(MDFILE *mdp,int n);

    This macro allows the user program to set the current sequence
    number.


mdfnfilenm(MDFILE *mdp,char *(*filenm)());

    This macro allows the user program to set the filename generating
    function after a file has been opened.


mdfnateof(MDFILE *mdp,int (*ateof)());

    This macro allows the user program to set the `ateof' function.


mdfnprompt(MDFILE *mdp,int (*prompt));

    This macro allows the user program to set the `prompt' function.


mdfileno(MDFILE *mdp);

    This macro allows the user program to get the file number which was
    returned from the call to open(2).


mdseqno(MDFILE *mdp);

    This macro allows the user program to access the current sequence
    number.  There is actually no real reason for this to exist any
    longer, since the sequence number is completely under control of the
    application, now.


mdeof(MDFILE *mdp);

    This macro returns non-zero if we have encountered the *real* EOF.


mderror(MDFILE *mdp);

    This macro returns non-zero if an error has occurred.


mdclrerr(MDFILE *mdp);

    This macro clears any I/O related error in order to allow the user
    program to attempt to continue.


int	mdateof(MDFILE *mdp,int mode);
int	mdalleof(MDFILE *mdp,int mode);
int	mdnoteof(MDFILE *mdp,int mode);

    These are supplied functions which may be used as the `ateof'
    function.  mdateof() returns a zero if the file is being written and
    1 if it is being read.  This allows us to process normal
    (non-spanning) files as the default.

    mdalleof() always returns a 1, which means that files can never span
    diskette boundaries.  A little bit silly, I know, but its there for
    completeness (and it is small).

    mdnoteof() always returns a 0.  It is useful if you don't know how
    many diskettes your file will span, but you will get a definite
    indication in the input stream indicating that you have read enough.
    Sound cryptic enough?  For an example, say you are reading an
    archive file, and you know when you get to the end because you have
    a special null header.  You always want it to *not* be at EOF, so
    that it will automatically ask for the next diskette.  When you find
    your special header, your program ends, without having ever received
    a *real* EOF from the I/O package.


This is as much documentation as you get for this package.  If you
wish more information, have suggestions or patches, please contact
me.

	-greg

Greg Yachuk	Informix Software Inc., Menlo Park, CA	(415) 322-6457
{uunet,pyramid}!infmx!greggy	why yes, I DID choose that login myself

