Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C++ File Objects


JUN90: C++ FILE OBJECTS

Kevin had been coding in C for six years. He is working for Computational Systems, a vibration analysis company in Knoxville, Tenn. Kevin can be reached at 508 Valparaiso Rd., Oak Ridge, TN 37830.


A few months ago I wrote a database program for a client and found myself manipulating multiple files in multiple directories on multiple drives using multiple devices. Although I'd run into this sort of challenge many times before, this particular program involved some variations on the theme that didn't quite fit my previous work. Nevertheless, I went ahead and wrote the program in C, rewriting file access/handling routines I'd developed before.

As an afterthought, I decided to use C++ to design a basic object or objects that would at least minimize some of these problems in the future. Although there are a number of examples of file-type objects around, I haven't run across any that are capable of taking a partial file specification, such as TEMPFILE.DAT, and completing it by adding the drive and path components. Failure to do so can result in unpredictable references. For instance, A:TEMPFILE.DAT obviously refers to a file on drive A in the current subdirectory. But what is the current subdirectory? Is it what the user intended? I came to the conclusion that a file object starts too high up the tree. The first step is creating the file specification, the second is handling the file.

Creating a good base class is like sculpting, start with a block of marble and then chip away everything that doesn't look like a statue. This took a while but the result was class File_Spec.

File_Spec

Although the code that defines the File_Spec class is large, it contains nothing that isn't specific to the creation and maintenance of a DOS/Unix-style file specification. Each component can be retrieved or modified, an incomplete file specification can be completed, and the object's current status is maintained and made available to clients. Of course, not all the methods have to be included in a given application. I have provided a single File_Spec module (FILESPEC.CPP, Listing Two, page 96) tor simplicity's sake. If the individual methods are compiled separately and placed in a library, however, then only those methods actually used by a program will be included.

If you look at Listing One (FILESPEC.HPP, page 96) you'll see first that I named the fields (attributes in OOP-speak) device, prefix, name, and suffix. The idea behind these labels is to provide some degree of abstraction in case the object is ported to a system where the device doesn't have to be a disk drive, or the prefix a DOS/Unix-style path. (For instance, I've recently been coding on a Prime Series 50 machine where the device names are logical drives enclosed in <...>.) Next you should notice that the device, name, and suffix are implemented as arrays.

C++ books are big on the new operator and seem to use it at the drop of a class hat. I've gotten burned by poor malloc() implementations a time or two, where memory ended up so fragmented that malloc() lost all conception of order. Because there is no specification for memory management in C++, it seems wise to limit the potential for such problems when objects are potentially volatile.

device, name, and suffix are all relatively short; any waste space is minimized, reducing both the code and the time required to change them with the change_XXXX() methods. The exceptions are prefix (the path) and request. Because these may vary in length from one character to as many as 78 characters, it seemed more reasonable to swallow the overhead and reduce RAM requirements in their case. If you're wondering what the request attribute accomplishes, it forces me to look and not touch.

All strings that have to be returned are returned as a pointer to request, which is allocated as needed. Obviously I could reduce code and execution overhead even more if, instead of copying the device, name, and so on to another string and returning it, I simply returned pointers to the respective attributes. I must confess, however, that I might be tempted at some point to modify the attributes directly by using an alias, instead of playing by the rules. What can I say? I'm weak. Because I can't depend on self-discipline, I'm better off returning something that can't modify the originals. Besides, I have to create another string to return the complete file specification to clients anyway. Why not let the caller pass a string pointer for the method to fill? Two reason: First, without bounds checking there's no way to be sure the passed string is big enough, and second, this adds to the complexity of using the class methods, without really buying anything.

Making It

The first four methods in Listing Two are the constructors and the destructor. Constructors constitute a built-in setup or initialization routine for an object. All you have to do is declare the object, and the compiler automatically calls the appropriate constructor. C++ also allows you to have more than one constructor (or method) with the same name (hence multiple constructors). Depending on the number and type of parameters passed, the compiler determines just which method you need. This is called "overloading" and is an example of polymorphism. More on this later.

There is a drawback to constructors: They always return a reference to the object created. If problems occurred, you'll have a malformed object. There's no such thing as an error or even a NULL return. Consequently, you should check the status (see status() later) prior to using the object (see Class File for examples) and check errno for memory allocation errors following the object's creation. This necessity is irritating and unrefined, but we're stuck with it. On the positive side, because both of the objects presented here are aware of their own status, they won't misbehave if asked to do the impossible -- they'll simply report an error back to the caller. Ideally, incorporating a reference to a separate Error class would enable you to use the objects without worries.

The first constructor simply creates an empty object that can later be filled by using the other class methods. The second constructor accepts a reference (pointer) to another File_Spec object and makes a copy of it. These two are pretty straightforward. The constructor that accepts a character string, however, isn't quite so obvious.

Following initialization of the various attributes, the (char*string) constructor uses a finite state machine to parse the string passed to it. This string may consist of any or all combinations of a device, prefix (path), name, and/ or suffix (extension.) The prefix may be a full or partial path, but must end with a backslash to be properly recognized. Once the string has been parsed, checks are made both to see which components are missing and for valid file characters.

You indicate to C++ that a method is a destructor by prepending a tilde (~) to the class name. This message is automatically passed to an object whenever the object passes out of scope and is meant to perform any necessary cleanup. In the case of File_Spec this means deallocating any memory assigned to prefix or request.

The Rest of File_Spec

Status( ) returns the condition of the file spec. A non-zero return value indicates a problem of some sort. Exactly what the problem is can be determined by examining the value. The low nibble of the low byte of the status word indicates whether or not the spec is complete; the high nibble of the low byte of the status word indicates invalid file characters. Next is, in my opinion, one of the most exciting aspect of object-oriented programming -- operator overloading or polymorphism.

C allows you to create new types with the typedef operator. What it doesn't do is redefine operators to handle the new types. With C++, you can. This provides genuine language extensibility, which is one of the things that gets Forth programmers so excited (even if they can't figure out what they did a week later). Most of the C operators can be overloaded, and early versions of File_Spec got rather carried away with this capability. By dint of much pruning, we are left with the equal sign (=). In this incarnation, File_Spec= doesn't even do much, it just calls copy(), a private method. Operator overloading is a powerful capability, and everyone will tell you it can be abused. They're right, but go ahead and abuse. Get it out of your system. I did. Now back to earth.

The get_XXXX( ) and change_XXXX ( ) methods should be readily understandable. Because both, request and prefix, have lengths associated with them, they are only reallocated if they aren't long enough. (I know. If I'm concerned enough about storage to make them dynamic to begin with, why don't I shorten them when possible. What can I say? GIGO is one thing, but someone has to pick up the garbage.) The change methods also keep the condition flags up to date. The read_only( ) method is to prevent the change methods from working at inconvenient times, such as when the file is open. Check_prefix( ) determines whether or not a path is complete. It does this by checking for a backslash as the first character. If there is no backslash, the path is relative to the current working directory. Next it looks for a period (.) followed by backslashes or another period. This refers either to the current directory or to the parent directory. The last step is to make sure the prefix ends with a backslash.

One of the principles of object-oriented programming is late binding. Without getting into details or specifics, this means that decisions concerning which functions to call or what parameters to pass can be delayed until run time, thus making a virtue of procrastination. The advantage is an increase in the flexibility of the program. In keeping with this principle, File_Spec objects do not attempt to automatically complete themselves until specifically requested to do so. This means that a partial File_Spec can be instantiated, passed around, copied, and modified without worrying about the validity or presence of a drive or path until those elements are necessary.

Complete( ) attempts to resolve an incomplete file specification. If a name was not specified or if any of the attributes have invalid characters, complete( ) immediately returns a FALSE. If complete( ) passes those tests, it checks for a device specification and, if one isn't found, calls DOS for the current drive using ll_get_drive( ). I wrote the assembly language module (LOWIO.ASM, Listing Five, page 111) to provide three functions. First, I needed a routine for getting just the current drive (without the current working directory). Second, I needed to be able to get the current working directory of another drive. Third, I wanted a routine to truncate a file at a position other than its beginning.

The last step is to check for and, if necessary, complete the prefix. If the prefix is incomplete then parse_prefix( ) is called. Space is allocated for a path name, and then DOS is called for the current working directory for the specified device via ll_get_cwd( ). Again, as in the constructor, a finite state machine is used to build the prefix. Relative references (..\ and .\) are treated as DOS would. This routine, though, is a bit smarter, or takes a bit more for granted, than DOS. Multiple backslashes are ignored. For instance, \\ subdir \is treated as \ subdir\. More than two periods in a row are treated as two periods in a row: ...\ subdir \ = ..\ subdir\. And two or more periods in a row without a backslash are treated as two periods with a backslash:.. subdir = ..\ subdir. Obviously these fixes are not guaranteed to produce what you and/or your user had in mind, but the chance of possible harm seems minimal.

Class File

Class File (see FILE.HPP, Listing Three page 106 and FILE.CPP, Listing Four page 108) is the first derived class of File_Spec. Its purpose is to bundle the basic file operations such as create, read, rename, and so on into a single object. File is defined as a :public descendant of File_Spec so that all of the File_Spec methods are available to File and its clients.

The first File constructor is defined like this:

  File::File(char *name, int open_flag = FALSE):(char *name)

The phrase int open_flag = FALSE is one of two points worthy of comment. This phrase defines a pass parameter, open_flag, and states that if none is provided to default to FALSE. Default parameters are sexy. They're a kind of "just give me the usual" to the compiler. The other point worth noting here is the phrase ... :(chart *name). This is a call to the parent's class constructor. As you can see, the File class doesn't use the name parameter itself but instead passes it on to its parent, File_Spec. Although a child constructor does not automatically call its parent constructor, the parent destructor is automatically called.

Next, exists( ) reports to its client on the existence of the file specified. In addition to reporting to clients, the exists( ) method is used by the File methods open( ) and create( ). With these two we come to a somewhat peculiar function call:

  ::close(tmp_handle);

C++ defines an "overload" directive that is intended to allow the programmer to use the same name for multiple functions and which, according to the books, should work like this:

  overload    foo;
   int        foo(char *);
   int           foo(unsigned int);

Following this declaration, you should be able to issue calls to foo(...) and depending on the type and number of parameters passed, the compiler will select the appropriate function. Unfortunately, there is a bug in Zortech's 1.0 implementation and explicit overloading doesn't work properly. When I spoke to Zortech's tech support people about this they recommended using the :: operator as a workaround. This operator is a scope qualifier (notice its use in all the method definitions to restrict their scope to their class). When used without a class name it is a global reference and enables me to access the standard C close( ) function. I also use the :: operator to access the library versions of open( ), read( ), and rename( ).

As for read( ) and write( ), under DOS, Unix, and many other operating systems, the file pointer is automatically advanced whenever a read or write operation is performed. While this is convenient in the case of a sequential file, most of the time it constitutes an undesired side effect (certainly when you're writing a random access database program). For this reason I added an auto-advance file attribute, which is set when the object is opened or created by ORing F_ADVANCE with the other file attributes. Consequently, if a File is opened or created with the F_ADVANCE flag it behaves just as you would expect it to. Without this flag, however, reads and writes don't lose their position in the file. Now just imagine a (descendant) database class using methods such as retrieve( ) and replace( ) instead of lseek( ), read( ), lseek( ), write( ). Seems just a hair more elegant, doesn't it.

Truncate( ) is a routine that has been part of my standard library for some time. When DOS is called to write a file and the number of bytes specified is zero, DOS truncates the file at that point. For reasons of safety and history, the C write( ) routines doesn't do this; it returns zero bytes written. I can't really argue with that philosophy, but sometimes you must truncate a file. In the case of the File class, the write( ) method continues to behave as expected and returns immediately, with no effect, if zero bytes are written. An explicit message to a File object to truncate though, uses DOS to actually chop off the file at the current file pointer location.

Set_position( ) and get_position( ) allow a File client to specify where the file pointer should be. Size( ), rename( ), erase( ), and copy( ) do what their names suggest. Notice that both copy( ) and rename( ) operate whether the file is open or not. They both preserve the current condition, restore upon completion, and, aside from the long-winded error-testing stuff, they're all simple.

Listing Six

Epilogue

An obvious heir of File is class Buf_File that would provide buffered I/O. This would be very easily accomplished by descending publicly from class File and writing new read( ) and write( ) methods. Nothing else is required. A class Text_File might in turn descend from Buf_File. A Directory class could descend directly from File_Spec. C++ 2.0 offers multiple inheritance and the possibilities boggle the mind.

I chose C++ for my OOP explorations thinking that my familiarity with C would enable me to clearly see what OOP was against -- the backdrop of a known language. In retrospect I think it had the opposite effect.

In many ways object-oriented programming is like writing several miniprograms. This has the advantage of restricting the problem domain at any given point to a more easily managed size. I also found that if one thinks of objects in this way they are easier to define. For a lucid description of the concepts of OOP, I highly recommend Bertrand Meyer's book, Object-oriented Software Construction (Prentice Hall, 1988), which does an outstanding job of describing what OOP is and isn't and, although the book uses Eiffel (which Meyer wrote), it helped my C++ programming immensely. C++ in turn is having an impact on my C programming.

OOP's promise is to bring us a bit closer to component level design and implementation. I think OOP does have the potential to accomplish this. The cost is another layer of code between you and the machine. This means slower and larger programs, in other words, less efficient use of computer resources. On the other hand, the extra layer of abstraction will mean more efficient use of the programmer. No language can decide this swap-off for us. There will always be a need and a time for assembler, just as there will always be a need and a time for dBase IV.

_C++ FILE OBJECTS_ by Kevin Weeks

[LISTING ONE]

<a name="013f_000a">

/* FILETEST.HPP Written by Kevin D. Weeks Released to the Public Domain  */

#ifndef FILESPEC_HPP                        // prevent multiple #includes
#define FILESPEC_HPP

#include <stdio.h>

#define ERR     -1
// create a boolean type
typedef enum{FALSE,TRUE}     bool;

// specify attribute sizes
#define SIZE_DEVICE 2
#define SIZE_PREFIX 64
#define SIZE_NAME   9                       // the dot is part of the name
#define SIZE_SUFFIX 3

// these constants are used as flags in the condition attribute
#define FLAG_DEVICE     0x0001
#define FLAG_PREFIX     0x0002
#define FLAG_NAME       0x0004
#define FLAG_SUFFIX     0x0008
#define INCOMPLETE      0x000f
#define INVALID_CHAR    0x00f0
#define READ_ONLY       0x0100

class   File_Spec
{
    // first define the attributes
    char    device[SIZE_DEVICE + 1];        // under MS-DOS, the disk drive
    char    *prefix;                        //   "     "   , the path
    char    name[SIZE_NAME + 1];            //   "     "   , still the name
    char    suffix[SIZE_SUFFIX + 1];        //   "     "   , the extension
    char    *request;                       // pointer to response string for
                                            // get_XXXX() methods
    int     prefix_length;
    int     request_length;
    unsigned int    condition;              // current status of the object

    // and then the private methods
    bool    check_prefix(void);             // determine completeness of prefix
    bool    parse_prefix(void);             // interpret relative prefix
    bool    check_chars(char *string, unsigned int attrib_flag);
                                            // test for valid MS-DOS file chars
    void    copy(const File_Spec& original);  // copy another file spec
    bool    clear_attribute(char *attribute, unsigned int attrib_flag);
    bool    realloc(char **pointer, int *length, int new_length);

    // make class File_Spec a friend of itself
    friend  class File_Spec;

  // now the public methods
  public:
    // construct file specifications
            File_Spec(void);
            File_Spec(const char *file);
            File_Spec(const File_Spec& original);

    // destroy a file specification
            ~File_Spec(void);

    // return status of object
    unsigned int    status(void);

    // extend the language by overloading the = operator
    File_Spec   operator=(const File_Spec& original);

    // ALL get_XXXX() methods guarantee to return NUL-terminated strings
    char    *get_device(void);
    char    *get_prefix(void);
    char    *get_name(void);
    char    *get_suffix(void);
    char    *filespec(void);

    // ALL change_XXXX() methods guarantee to copy no more than SIZE_n
    // characters from the pass parameter
    bool    change_device(const char *string = NULL);
    bool    change_prefix(const char *string = NULL);
    bool    change_name(const char *string = NULL);
    bool    change_suffix(const char *string = NULL);

    // disables/enables change_XXXX()
    void    read_only(bool flag);

    // attempt to complete the file specification
    bool    complete(void);
};

#endif




<a name="013f_000b"><a name="013f_000b">
<a name="013f_000c">
[LISTING TWO]
<a name="013f_000c">

/* FILESPEC.CPP Written by Kevin D. Weeks Released to the Public Domain  */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include "filespec.hpp"

// this declaration instructs the compiler to NOT perform name-mangling
// on these functions.
extern "C"
{
    extern char     ll_get_drive(void);
    extern int      ll_get_cwd(int, char *);
    extern unsigned int ll_write(int, unsigned int, const void *);
}

// these constants are states used in parsing the file string
#define DEVICE  1
#define PREFIX  2
#define NAME    3
#define SUFFIX  4

// a macro to return out of memory errors
#define MEM_ERR(length) { errno = ENOMEM; length = 0; return NULL; }

extern volatile int errno;

/*  This final File_Spec constructor is passed a character string which it
    attempts to parse into its various components. The parsing is done with
    a finite state machine that begins at the end of the string and backs
    up to the beginning, changing state as it encounters the element delimiters
    ':', '.', and '\'. Once the string has been parsed the components are
    checked for completeness and for the validity of the file characters.
    In order to correctly interpret a string with a prefix but no name the
    string must end with a '\'. Also note that any individual component
    device, name, etc.) that is too long is truncated to a legal length.
*/
File_Spec::File_Spec(const char *file)
{
    char    *tmp_file;                  // local copy of tmp_file
    char    *tmp_prefix;                // temporary string for the prefix
    int     pos;                        // current position in tmp_file string
    int     state;                      // current state
    int     i;                          // trash variable
    // initialize everything that needs initializing
    prefix = NULL;
    prefix_length = 0;
    request_length = 0;
    condition = 0;
    device[0] = device[SIZE_DEVICE] = '\0';
    name[0] = name[SIZE_NAME] = '\0';
    suffix[0] = suffix[SIZE_SUFFIX] = '\0';
    errno = 0;
    if (file == NULL || *file == '\0')
    {
        condition = INCOMPLETE;
        return;
    }
    if ((tmp_file = new char[strlen(file) + 1]) == NULL)
    {
        errno = ENOMEM;
        return;
    }
    strcpy(tmp_file,file);
    if ((tmp_prefix = new char[SIZE_PREFIX + 1]) == NULL)
    {
        errno = ENOMEM;
        return;
    }
    tmp_prefix[0] = tmp_prefix[SIZE_PREFIX] = '\0';
    pos = strlen(tmp_file) - 1;             // set pos to last character
    // this while loop is the finite state machine mentioned above. note
    // that the strncpy() calls copy everthing from just beyond the
    // character that satisfies the case, and then the tmp_file is truncated
    // at that point with a '\0'.
    state = SUFFIX;
    do
    {
        switch (tmp_file[pos])
        {
            case '.':
                // a dot only counts in the SUFFIX state
                if (state == SUFFIX)
                {
                    strncpy(suffix,&tmp_file[pos + 1],SIZE_SUFFIX);
                    tmp_file[pos + 1] = '\0';
                    state = NAME;
                }
                else
                    if (state == NAME)
                        // this means we've got two or more dots in a name
                        // which is illegal. flag it as an invalid char
                        condition |= FLAG_NAME << 4;
                break;
            case '\\':
                if ((state == SUFFIX) || (state == NAME))
                {
                    strncpy(name,&tmp_file[pos + 1],SIZE_NAME);
                    tmp_file[pos + 1] = '\0';
                    state = PREFIX;
                }
                break;
            case ':':
                if ((state == SUFFIX) || (state == NAME))
                {
                    strncpy(name,&tmp_file[pos + 1],SIZE_NAME);
                    tmp_file[pos + 1] = '\0';
                    state = DEVICE;
                }
                else
                    if (state == PREFIX)
                    {
                        strncpy(tmp_prefix,&tmp_file[pos + 1],SIZE_PREFIX);
                        tmp_file[pos + 1] = '\0';
                        state = DEVICE;
                    }
                break;
        }
        --pos;                              // go to next character
    } while(pos >= 0);
    // now resolve whatever state we ended up in
    if ((state == SUFFIX) || (state == NAME))
        strncpy(name,tmp_file,SIZE_NAME);
    else
        if (state == PREFIX)
            strncpy(tmp_prefix,tmp_file,SIZE_PREFIX);
        else
            strncpy(device,tmp_file,SIZE_DEVICE);
    // validate the device
    device[1] = ':';
    device[2] = '\0';
    if (device[0] == '\0')
        condition |= FLAG_DEVICE;
    else
    {
        // make the device upper-case for simplicity's sake later on
        device[0] = toupper(device[0]);
        if (device[0] < 'A' || device[0] > 'Z')
            condition |= FLAG_DEVICE << 4;
    }
    // use the existing change_prefix() method to create the prefix and
    // validate it
    change_prefix(tmp_prefix);
    delete[SIZE_PREFIX + 1] tmp_prefix;
    // now validate the name
    if (name[0] == '\0')
        condition |= FLAG_NAME;
    else
    {
        if (name[0] == '.')
        {
            condition |= FLAG_NAME;
            name[0] = '\0';
        }
        else
            if (check_chars(name,FLAG_NAME))
            {
                // as far as we're concerned name HAS to end with a dot.
                i = strlen(name);
                if (name[i - 1] != '.')
                {
                    if (i == SIZE_NAME)
                        i = SIZE_NAME - 1;
                    name[i++] = '.';
                    name[i] = '\0';
                }
            }
    }
    // and suffix
    if (suffix[0] != '\0')
        check_chars(suffix,FLAG_SUFFIX);

}
/*  This constructor creates an empty object suitable for later filling. */
File_Spec::File_Spec(void)
{
    // Set both ends of device, name, and suffix to NUL. Since strncpy()
    // is used later on this guarantees these three are always NUL-terminated.
    device[0] = device[SIZE_DEVICE] = '\0';
    name[0] = name[SIZE_NAME] = '\0';
    suffix[0] = suffix[SIZE_SUFFIX] = '\0';
    prefix = NULL;
    prefix_length = 0;
    request = NULL;
    request_length = 0;
    condition = INCOMPLETE;                 // everthing's incomplete
}
/* The so-called "copy" constructor actually calls a copy() method after
    doing some preliminary initialization.
*/
File_Spec::File_Spec(const File_Spec& original)
{
    prefix = NULL;
    prefix_length = 0;
    request = NULL;
    request_length = 0;
    copy(original);
}
/* The destructor simply releases the memory, if any, assigned to prefix
    and request.
*/
File_Spec::~File_Spec(void)
{
    if (prefix != NULL)
    {
        delete[prefix_length] prefix;
        prefix = NULL;
        prefix_length = 0;
    }
    if (request != NULL)
    {
        delete[request_length] request;
        request = NULL;
        request_length = 0;
    }
}
/* Tell 'em how we're doing */
unsigned int    File_Spec::status(void)
{
    return(condition);
}
/* This method's purpose is to return a string containing a complete file
    specification string for use by clients.
*/
char    *File_Spec::filespec(void)
{
    int     length;
    // first calculate the length of the file specification
    length = strlen(device);
    length += strlen(prefix);
    length += strlen(name);
    // + 2 to allow for the NUL-terminator and the colon
    length += strlen(suffix) + 2;
    // if request isn't already long enough then de-allocate the current
    // pointer and allocate a new one
    if (request_length < length)
        if (!realloc(&request,&request_length,length))
            return(NULL);
    // build the string
    strcpy(request,device);
    if (prefix != NULL)
        strcat(request,prefix);
    strcat(request,name);
    strcat(request,suffix);
    return(request);
}
/*  get_device(), get_prefix(), get_name(), and get_suffix() are all essen-
    tialy alike. if the request string isn't long enough then it is re-
    allocated, then the attribute that was requested is copied into request.
*/
char    *File_Spec::get_device(void)
{
    errno = 0;
    if (request_length < SIZE_DEVICE + 1)
        if (!realloc(&request,&request_length,SIZE_DEVICE + 1))
            return(NULL);
    strcpy(request,device);
    return(request);
}
/*  Returning the prefix is a bit more complicated than the other get
    routines.
*/
char    *File_Spec::get_prefix(void)
{
    errno = 0;
    if (prefix_length)
    {
        if (request_length < prefix_length)
            if (!realloc(&request,&request_length,prefix_length))
                return(NULL);
        strcpy(request,prefix);
    }
    else
    // even if the prefix is NULL we promised to return something. Here,
    // a string 1 character long consisting of a NUL-terminator
    {
        if (request_length == 0)
            if (realloc(&request,&request_length,1))
                *request = '\0';
    }
    return(request);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char    *File_Spec::get_name(void)
{
    errno = 0;
    if (request_length < SIZE_NAME + 1)
        if (!realloc(&request,&request_length,SIZE_NAME + 1))
            return(NULL);
    strcpy(request,name);
    return(request);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char    *File_Spec::get_suffix(void)
{
    errno = 0;
    if (request_length < SIZE_SUFFIX + 1)
        if (!realloc(&request,&request_length,SIZE_SUFFIX + 1))
            return(NULL);
    strcpy(request,suffix);
    return(request);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
    as with the get_XXXX() methods above, change_device(), change_prefix(),
    change_name(), and change_suffix() are basically the same. if the
    current condition is "read-only" then return a FALSE. if a NULL string
    is passed (note the default) the current object is truncated and the
    corresponding incomplete flag is set. otherwise SIZE_n characters are
    copied and the standard validity checks are made.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool    File_Spec::change_device(const char *string)
{
    if (condition & READ_ONLY)
        return(FALSE);
    if (string == NULL || *string == '\0')
        return(clear_attribute(device,FLAG_DEVICE));
    strncpy(device,string,SIZE_DEVICE);
    device[0] = toupper(device[0]);
    device[1] = ':';
    device[2] = '\0';
    if (device[0] < 'A' || device[0] > 'Z')
    {
        condition |= FLAG_DEVICE << 4;
        return(FALSE);
    }
    else
        condition &= ~(FLAG_DEVICE << 4);
    condition &= ~FLAG_DEVICE;
    return(TRUE);
}
/*  get_prefix(), like change_prefix(), is somewhat more complicated than the
    other change routines
*/
bool    File_Spec::change_prefix(const char *string)
{
    int     new_length;
    if (condition & READ_ONLY)
        return(FALSE);
    if (string == NULL || *string == '\0')
        return(clear_attribute(prefix,FLAG_PREFIX));
    errno = 0;
    // get the size of the new prefix and if the existing prefix isn't long
    // enough then re-allocate it
    new_length = strlen(string);
    if (new_length > SIZE_PREFIX)
        new_length = SIZE_PREFIX;
    if (prefix_length < new_length + 1)
        if (!realloc(&prefix,&prefix_length,new_length + 1))
            return(FALSE);
    // copy in the new string and validate it.
    strncpy(prefix,string,new_length);
    prefix[new_length] = '\0';
    if (check_chars(prefix,FLAG_PREFIX) == FALSE)
        return(FALSE);
    return(check_prefix());
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool    File_Spec::change_name(const char *string)
{
    int     i;
    if (condition & READ_ONLY)
        return(FALSE);
    if (string == NULL || *string == '\0')
        return(clear_attribute(name,FLAG_NAME));
    i = 0;
    while (string[i])
    {
        if (string[i] == '.')
        {
            change_suffix(&string[i + 1]);
            if (i == 0)
            {
                name[0] = '\0';
                condition |= FLAG_NAME;
                condition &= ~(FLAG_NAME << 4);
                return(FALSE);
            }
            ++i;
            break;
        }
        if (string[i] == ':' || string[i] == '\\')
        {
            condition |= FLAG_NAME << 4;
            return(FALSE);
        }
        if (++i == SIZE_NAME)
            break;
    }
    strncpy(name,string,i);
    name[i] = '\0';
    if (check_chars(name,FLAG_NAME) == FALSE)
        return(FALSE);
    i = strlen(name);
    // as far as we're concerned name HAS to end with a dot.
    if (name[i - 1] != '.')
    {
        if (i == SIZE_NAME)
            i = SIZE_NAME - 1;
        name[i++] = '.';
        name[i] = '\0';
    }
    condition &= ~FLAG_NAME;
    return(TRUE);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool    File_Spec::change_suffix(const char *string)
{
    if (condition & READ_ONLY)
        return(FALSE);
    if (string == NULL)
    {
        clear_attribute(suffix,FLAG_SUFFIX);
        condition &= ~FLAG_SUFFIX;          // unset the incomplete suffix flag
    }
    if (*string == '.')
    {
        ++string;
        strncpy(suffix,string,SIZE_SUFFIX);
    }
    else
        strncpy(suffix,string,SIZE_SUFFIX);
    if (check_chars(suffix,FLAG_SUFFIX) == FALSE)
        return(FALSE);
    condition &= ~FLAG_SUFFIX;
    return(TRUE);
}
/*  This method determines whether or not the prefix is complete. */
bool    File_Spec::check_prefix(void)
{
    int     i;

    // if the 1st character isn't a '\' then the prefix is relative to the
    // current working directory.
    if (prefix[0] != '\\')
    {
        condition |= FLAG_PREFIX;
        return(FALSE);
    }
    i = 0;
    // this loop checks for the presence of a dot followed by another dot
    // or a dot followed by a backslash. either one indicates the prefix
    // is relative the the current working directory.
    while (prefix[i + 1])
    {
        if ((prefix[i] == '.') &&
          (prefix[i + 1] == '\\' || prefix[i + 1] == '.'))
            {
                condition |= FLAG_PREFIX;
                return(FALSE);
            }
        if (++i == SIZE_PREFIX - 1)
            break;
    }
    // a prefix HAS to end with a '\'
    if (prefix[i] != '\\')
    {
        prefix[i++] = '\\';
        prefix[i] = '\0';
    }
    condition &= ~FLAG_PREFIX;
    return(TRUE);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void    File_Spec::read_only(bool flag)
{
    if (flag)
        condition |= READ_ONLY;
    else
        condition &= ~READ_ONLY;
}
/* This method actually attempts to complete a file specification. */
bool    File_Spec::complete(void)
{
    char    *tstr;
    char    drive;
    if (condition & READ_ONLY)
        return(FALSE);
    // an invalid character in any of the components is an automatic failure
    if (condition & INVALID_CHAR)
        return(FALSE);
    // no name is also an automatic failure
    if (condition & FLAG_NAME)
        return(FALSE);
    // if no device specified then get the current drive
    if (condition & FLAG_DEVICE)
    {
        drive = ll_get_drive();
        device[0] = drive + 'A';
        device[1] = ':';
        device[2] = '\0';
        condition &= ~FLAG_DEVICE;
    }
    // if the prefix isn't complete call parse_prefix()
    if (condition & FLAG_PREFIX)
        if (parse_prefix() == FALSE)
            return(FALSE);
    // everything's alright so give the client the go-ahead
    condition = 0;
    return(TRUE);
}
/*  Looks rather un-impressive doesn't it. */
File_Spec   File_Spec::operator=(const File_Spec& original)
{
    copy(original);
}
/*  As with the (char *string) constructor above, this routine uses a finite
    state machine to produce a complete path. the existing prefix (if any)
    is processed front to back while the current working directory (cwd) is
    processed back to front. the end result is that the partial prefix is
    appended at the correct point to the cwd.
*/
bool    File_Spec::parse_prefix(void)
{
    int     prefix_elem;
    int     cwd_elem;
    int     state;
    char    *cwd;
    errno = 0;
    // set the defaults
    if ((cwd = new char[SIZE_PREFIX + 1]) == NULL)
    {
        errno = ENOMEM;
        return(FALSE);
    }
    // since the directory returned by ll_get_cwd() doesn't begin with a
    // '\' we'll start by adding one to the beginning of cwd
    cwd[0] = '\\';
    cwd[1] = '\0';
    // get the current working directory for the specified drive
    if (ll_get_cwd(device[0] - 64,&cwd[1]) == ERR)
    {
        delete[SIZE_PREFIX + 1] cwd;
        return(FALSE);
    }
    // DOS doesn't append a '\' either so we will
    cwd_elem = strlen(cwd);
    if (cwd[1] != '\0')
    {
        cwd[cwd_elem] = '\\';
        cwd[cwd_elem + 1] = '\0';
    }
    // if there was no prefix, there is now. assign it and return
    if (prefix == NULL)
    {
        prefix_length = SIZE_PREFIX + 1;
        prefix = cwd;
        return(TRUE);
    }
    prefix_elem = 0;
    state = 0;
    do
    {
        switch(state)
        {
            case 0:
                if (prefix[prefix_elem] == '.')
                    // a dot means check for another dot or a '\'. goto state 1
                    state = 1;
                else
                    // DOS would give you a fit over this. since we're here,
                    // check_prefix() found a relative component in the path.
                    // however, the initial '\' means "start at the root".
                    // so we go to the root by setting cwd_elem to zero and
                    // start looking for a dot.
                    if (prefix[prefix_elem] == '\\')
                    {
                        state = 1;
                        cwd_elem = 0;
                    }
                    else
                    {
                        // our current character IS a character so get ready to
                        // append it to cwd by going to state 3 and backing up
                        // so we don't lose it.
                        state = 3;
                        --prefix_elem;
                    }
                break;
            case 1:                         // we have seen a dot (or a '\')
                if (prefix[prefix_elem] == '.')
                    // another dot means go up a directory. enter state 2.
                    state = 2;
                else
                    // a '\' means stay here. get ready to append to the
                    // cwd and enter state 3.
                    if(prefix[prefix_elem] == '\\')
                        state = 3;
                    else
                        // a character here means we just saw something like
                        // .s - remain in the current directory and pass the
                        // buck back to state 0.
                        state = 0;
                break;
            case 2:                         // two (or more) dots in a row
                if (prefix[prefix_elem] == '\\')
                {
                    if (cwd_elem > 0)
                        do
                        {
                            --cwd_elem;
                        } while (cwd[cwd_elem] != '\\');
                }
                else
                    // more than two dots in a row. maintain current state
                    if (prefix[prefix_elem] == '.')
                        break;
                    else
                        // this means we're seeing a "..s" type situation.
                        // treat it as a "..\s" and back up one so we don't
                        // lose the prefix character.
                        --prefix_elem;
                state = 3;
                break;
            case 3:                         // append the prefix to the cwd
                if (prefix[prefix_elem] == '.')
                    // whoops, another dot. go back to state 1
                    state = 1;
                else
                    // more than one '\' in a row. don't change state.
                    if (prefix[prefix_elem] == '\\')
                        break;
                    else
                    {
                        // the order of element increments is a bit peculiar
                        // but remember we've been moving in opposite
                        // directions
                        do
                        {
                            ++cwd_elem;
                            cwd[cwd_elem] = prefix[prefix_elem];
                            ++prefix_elem;
                        } while (prefix[prefix_elem] != '\\');
                        ++cwd_elem;
                        cwd[cwd_elem] = prefix[prefix_elem];
                    }
                break;
        };
        ++prefix_elem;
    } while (prefix[prefix_elem]);
    cwd[++cwd_elem] = '\0';
    // it worked! reset the prefix pointer and get out
    delete[prefix_length] prefix;
    prefix = cwd;
    prefix_length = SIZE_PREFIX + 1;
    return(TRUE);
}
/* This routine checks for valid DOS file name characters. */
bool    File_Spec::check_chars(char *string, unsigned int attrib_flag)
{
    while (*string)
    {
        if ((*string < '!') ||
            (*string == '"') ||
            (*string > ')' && *string < '-') ||
            (*string == '/') ||
            (*string > '9' && *string < '@') ||
            (*string == '[') ||
            (*string > '\\' && *string < '^') ||
            (*string == '|'))
        {
            condition |= attrib_flag << 4;
            return(FALSE);
        }
        ++string;
    }
    condition &= ~(attrib_flag << 4);
    return(TRUE);
}
/* Since two methods (the 2nd constructor and the "=" operator) need to
    make copies of other File_Spec instances, this private method is provided
    to avoid duplicating the code. this method is also the main reason for
    making File_Spec a friend of itself.
*/
void    File_Spec::copy(const File_Spec& original)
{
    errno = 0;
    strcpy(device,original.device);
    if (prefix_length < original.prefix_length)
        if (!realloc(&prefix,&prefix_length,original.prefix_length))
            return;
    strcpy(prefix,original.prefix);
    strcpy(name,original.name);
    strcpy(suffix,original.suffix);
    condition = original.condition;
}
/*  clear_attribute sets the first element of the attribute it is passed to
    a NUL and then resets the condition flags.
*/
bool    File_Spec::clear_attribute(char *attribute, unsigned int attrib_flag)
{
    if (attribute != NULL)
        *attribute = '\0';
    condition |= attrib_flag;
    condition &= ~(attrib_flag << 4);
    return(TRUE);
}
/* realloc() is used by the request and prefix attributes when they're changed
    to see if they require re-allocating and to handle the re-allocation
    if needed.
*/
bool    File_Spec::realloc(char **pointer, int *length, int new_length)
{
    if (*length)
        delete[*length] *pointer;
    if ((*pointer = new char[new_length]) == NULL)
    {
        *length = 0;
        *pointer = NULL;
        return(FALSE);
    }
    *length = new_length;
    return(TRUE);
}




<a name="013f_000d"><a name="013f_000d">
<a name="013f_000e">
[LISTING THREE]
<a name="013f_000e">

/* FILE.HPP Written by Kevin D. Weeks Released to the Public Domain  */

#ifndef FILE_HPP                            // prevent multiple #includes
#define FILE_HPP

#include "filespec.hpp"

// file access mode definitions
#define F_RDONLY    0x0000
#define F_WRONLY    0x0001
#define F_RDWR      0x0002
#define F_COMPAT    0x0000
#define F_DENYALL   0x0010
#define F_DENYWR    0x0020
#define F_DENYRD    0x0030
#define F_DENYNO    0x0040

// flag to determine whether reads and writes have a side-effect on the
// file pointer
#define F_ADVANCE   0x0100

class   File: public File_Spec
{
    // class attributes
    int         handle;                     // DOS file handle
    int         open_flags;                 // flags used to open or create
    long        file_pos;                   // DOS file position
    long        filelength;                 // length of file
  // public class methods
  public:
    // constructors for file objects
            File(void);
            File(const File_Spec& original, bool open_flag = FALSE);
            File(const char *name, bool open_flag = FALSE);
    // destroy the object
            ~File(void);
    bool    exists(void);                   // see if the file exists
    bool    create(int mode_flags = 2, bool exclusive = TRUE);
    bool    open(int mode_flags = 2);
    bool    close(void);
    unsigned int    read(void *buffer, unsigned int size);
    // guarantees to write size bytes or fail
    bool    write(const void *buffer, unsigned int size);
    bool    truncate(void);              // truncate file at current position
    // guarantees to position file pointer within file or fail
    bool    set_position(long new_file_pos);
    long    get_position(void);
    long    size(void);
    bool    rename(const char *newname);
    bool    erase(void);
    File    *copy(const char *newfile, bool overwrite = FALSE);
};
#endif




<a name="013f_000f"><a name="013f_000f">
<a name="013f_0010">
[LISTING FOUR]
<a name="013f_0010">

/* FILE.CPP Written by Kevin D. Weeks Released to the Public Domain  */

#include <errno.h>
#include <io.h>
#include <sys\stat.h>
#include <dos.h>
#include "file.hpp"

// this declaration instructs the compiler to NOT perform name-mangling
// on these functions.
extern "C"
{
    extern char     ll_get_drive(void);
    extern int      ll_get_cwd(int, char *);
    extern unsigned int ll_write(int, unsigned int, const void *);
}

extern volatile int errno;

/*  An empty file object seems silly but here it is anyway */
File::File(void)
{
    handle = -1;
    file_pos = 0L;
    open_flags = 0;
    filelength = 0L;
}

/*  This is the File version of the "copy" constructor. it is posible to
    open the file when the object is instantiated by passing TRUE as a
    second parameter.
*/
File::File(const File_Spec& original, bool open_file):(original)
{
    handle = -1;
    file_pos = 0L;
    open_flags = 0;
    if ((filelength = filesize(filespec())) == -1L)
        filelength = 0L;
    if (open_file == TRUE)
        open();
}

/*  This constructor is the same as the one above. The differences in pass
    parameters are handled by their respective ancestors.
*/
File::File(const char *name, bool open_file):(name)
{
    handle = -1;
    file_pos = 0L;
    open_flags = 0;
    if ((filelength = filesize(filespec())) == -1L)
        filelength = 0L;
    if (open_file == TRUE)
        open();
}

/*  File destructor */
File::~File(void)
{
    if (handle > 0)
        close();
}

/*  Does the file exist? */
bool    File::exists(void)
{
    if (!complete())                      // check for a completed file spec
        return(FALSE);                    // and either fail if not
    if (findfirst(filespec(),0) == NULL)  // or else check for directory entry
        return(FALSE);
    return(TRUE);
}

/* Create the file. */
bool    File::create(int mode_flags, bool exclusive)
{
    int     tmp_handle;
    if (!complete())                        // is the file spec complete?
        return(FALSE);
    if (handle > -1)                        // if the file is open
    {
        if (exclusive)
            return(FALSE);
        else
            close();
    }
    else
        if (exists())                       // if the file exists
            if (exclusive)                  // if this flag is TRUE
            {                               // return an error
                errno = EEXIST;
                return(FALSE);
            }
    // create the the file and then close it to re-open with the appropriate
    // mode flags set
    if ((tmp_handle = creat(filespec(),S_IWRITE | S_IREAD)) == ERR)
        return(FALSE);
    ::close(tmp_handle);                  // the :: means use the library close
    if (open(mode_flags) == FALSE)          // no :: - use the File method
        return(FALSE);
    set_position(0L);                       // position at the beginning
    filelength = 0L;
    read_only(TRUE);                        // tell File_Spec that it can't
                                            // be changed
    return(TRUE);
}

/* Open the file. */
bool    File::open(int mode_flags)
{
    if (handle > -1)                        // if the file is already open
         return(TRUE);                      //      don't re-open it
    if (!complete())                        // check for a complete file spec
        return(FALSE);
    // use the standard library to actually open it ( ::open(...) )
    if ((handle = ::open(filespec(),mode_flags)) == ERR)
        return(FALSE);
    open_flags = mode_flags;                // keep the mode flags
    read_only(TRUE);                        // tell File_Spec not to change a
                                            //      thing
    return(TRUE);
}

/* Close the file. */
bool    File::close(void)
{
    if (handle > -1)                        // if the file's open
        if (::close(handle) == ERR)         //     close it
            return(FALSE);
    handle = -1;                            // and re-initialize everything
    file_pos = 0L;
    open_flags = 0;
    read_only(FALSE);                       // File_Spec can change again
}

/*  Read the file. NOTE: bytes actually read may be less than requested.  */
unsigned int    File::read(void *buffer, unsigned int num_bytes)
{
    int     bytes_read;
    if (handle < 0)                         // make sure the file's open
    {
        errno = EBADF;
        return(FALSE);
    }
    // first set the file position. if auto-advance is on set_position()
    // will just return. otherwise it will move the file pointer to where
    // it should be. then use the standard read to read the file
    if (set_position(file_pos) != FALSE)
        if ((bytes_read = ::read(handle,buffer,num_bytes)) == ERR)
            return(FALSE);
    // if auto-advance is on we still need to keep ourselves current
    if (open_flags & F_ADVANCE)
        file_pos += (long)bytes_read;
    // return the number of bytes actually read
    return(bytes_read);
}

/*  Write to the file. In this case failure to write the number of bytes
    specified IS considered a failure.
*/
bool    File::write(const void *buffer, unsigned int num_bytes)
{
    if (handle < 0)                        // is the file open?
    {
        errno = EBADF;
        return(FALSE);
    }
    if (num_bytes == 0)                  // if zero bytes are to be written
        return(TRUE);                    // return WITHOUT truncating the file
    // make sure the file pointer is positioned right and then call our
    // low level write routine to write it. there's no reason to clutter up
    // the program with the library write()
    if (set_position(file_pos) != FALSE)
        if (ll_write(handle,num_bytes,buffer) < num_bytes)
        {
            // at this point we failed to write as many bytes as desired. to
            // eliminate side effects we truncate
            truncate();
            return(FALSE);
        }
    // if we wrote at the end of the file, increase its length
    if (file_pos == filelength)
        filelength += (unsigned long)num_bytes;
    if (open_flags & F_ADVANCE)             // check for auto-advance
        file_pos += (long)num_bytes;
    return(TRUE);
}

/*  Truncate chops a file off at the current file_position. */
bool    File::truncate(void)
{
    if (handle < 0)                         // don't bother if we're not open
    {
        errno = EBADF;
        return(FALSE);
    }
    // re-set the file pointer and write zero bytes
    if (set_position(file_pos) != ERR)
        if (!ll_write(handle,0,NULL))
            return(FALSE);
    filelength = file_pos;                  // re-set the length
    return(TRUE);
}

/*  Position the DOS file pointer */
bool    File::set_position(long new_file_pos)
{
    if (handle < 0)                         // guess!
    {
        errno = EBADF;
        return(FALSE);
    }
    // first make sure we're not attempting to set before the beginning or
    // after the end of the file.
    if (new_file_pos > filelength || new_file_pos < 0L)
        return(FALSE);
    // position it
    if (lseek(handle,new_file_pos,SEEK_SET) == -1L)
        return(FALSE);
    file_pos = new_file_pos;
    return(TRUE);
}

/*  Get the current file position */
long    File::get_position(void)
{
    return(file_pos);
}

/*  Get the file size */
long    File::size(void)
{
    long    length;
    if (handle > -1)
        return(filelength);
    else
    {
        if (open() == FALSE)
            return(0L);
        length = filelength;
        close();
        return(length);
    }
}

/*  If we attempt to rename an open file it is first closed and then
    re-opened after the rename.
 */
bool    File::rename(const char *newname)
{
    bool    reopen = FALSE;
    int     tmp_flags;
    int     i;
    if (handle > -1)                        // close the file if it's open
    {
        tmp_flags = open_flags;
        close();
        reopen = TRUE;
    }
    else
        if (exists() == FALSE)              // make sure the file exists
            return(FALSE);
    // create a new file spec just like this one (note that it's also
    // instantiated at this point)
    File_Spec newspec = *this;
    newspec.change_name(newname);           // and then change the name
    if (::rename(filespec(),newspec.filespec()) != 0)
    {
        if (reopen)
            // pass the existing open_flags in case the default wasn't used
            // when the file was originally opened.
            open(tmp_flags);
        return(FALSE);
    }
    change_name(newname);                   // now update this file name
    if (reopen)
        return(open(tmp_flags));
    return(TRUE);
}

/*  Erase the file. if it's open, close it first. */
bool    File::erase(void)
{
    if (handle > -1)
        close();
    if (unlink(filespec()) == ERR)
        return(FALSE);
    return(TRUE);
}

/*  This might also be a good opportunity for operator overloading.  */
File    *File::copy(const char *newname, bool overwrite)
{
    File            *newfile;               // file to copy to
    char            *buffer;                // I/O buffer
    unsigned int    buf_size;               // size of I/O buffer
    unsigned int    num_bytes;              // number of bytes transfered
    long            tmp_file_pos;           // temporary file position holder
    bool            re_close = FALSE;       // flag indicating if source file
                                            // should be closed following the
                                            // copy (to avoid side effects)
    int             tmp_old_flags;          // the original source open flags

    errno = 0;
    // first create the new object instance
    if ((newfile = new File(newname)) == NULL)
    {
        errno = ENOMEM;
        return(NULL);
    }
    // then create the new file (invert overwrite for create)
    if (!(newfile->create(F_ADVANCE | F_RDWR,(bool)!overwrite)))
    {
        delete newfile;
        return(NULL);
    }
    // attempt to allocate a buffer. loop until successful or fail at 1 char
    buf_size = 32768;
    while ((buffer = new char[buf_size]) == NULL)
    {
        buf_size /= 2;
        if (buf_size == 1)
        {
            errno = ENOMEM;
            delete newfile;
            return(NULL);
        }
    }
    if (handle < 0)                         // if the source file isn't open
    {
        if (!open())                        // open it
        {
            newfile->close();               // if we can't open the source
            newfile->erase();               // file we need to clean up
            delete newfile;
            delete[buf_size] buffer;
            return(NULL);
        }
        re_close = TRUE;                    // copy() opened it so copy()
    }                                       // should close it
    tmp_old_flags = open_flags;             // keep the original open flags
    open_flags |= F_ADVANCE;                // and turn auto-advance on
    tmp_file_pos = file_pos;                // keep the original file pointer
    set_position(0L);                       // go to the beginning of the file
    // loop until the entire file has been copied
    while (num_bytes = read(buffer,buf_size))
    {
        if (newfile->write(buffer,num_bytes) == FALSE)
        {                                   // if a write error occurs we
            newfile->close();               // need to clean up the mess
            newfile->erase();               // and return an error
            delete newfile;
            delete[buf_size] buffer;
            open_flags = tmp_old_flags;
            set_position(tmp_file_pos);
            if (re_close)
                close();
            return(NULL);
        }
    }
    // clean up and return the new file
    newfile->close();
    delete[buf_size] buffer;
    open_flags = tmp_old_flags;
    set_position(tmp_file_pos);
    if (re_close)
        close();
    return(newfile);
}




<a name="013f_0011"><a name="013f_0011">
<a name="013f_0012">
[LISTING FIVE]
<a name="013f_0012">

;***************************************************************************
;   LOWIO.ASM Written by Kevin D. Weeks Released to the Public Domain
;

include MACROS.ASM                          ; macro file provided by Zortech

; import errno
begdata
    extrn  _errno:word
enddata

;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
;   bool    ll_write(int file_handle, unsigned int num_bytes, void *buffer);
;   ll_write simply makes a call to DOS for a write. it varies in two ways
;   from the standard C write().
;   1. the order of pass parameters (to simplify dealing with 80x86 segments)
;   2. it WILL truncate a file
;
begcode ll_write
    c_public ll_write
func ll_write
    push    bp
    mov     bp,sp
    push    bx
    push    cx
    push    dx
    push    ds

    mov     bx,P[bp]                        ; get file handle from stack
    mov     cx,P[bp + 2]                    ; get number of bytes to write
    mov     dx,P[bp + 4]                    ; get offset of buffer
if LPTR                                     ; if large memory model
    mov     ds,P[bp + 6]                    ; get segment of buffer
endif
    mov     ax,4000h                        ; dos write file function
    int     21h                             ; call dos
    jc      write_err                       ; carry flag indicates error
    jmp     write_ret
write_err:
    mov     _errno,ax                       ; set errno to error

write_ret:
    pop     ds
    pop     dx
    pop     cx
    pop     bx
    mov     sp,bp
    pop     bp
    ret
c_endp  ll_write
endcode ll_write

;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
;   int     ll_get_drive(void);
;   ll_get_drive simply returns the current looged disk drive. there is no
;   error return.
;
begcode ll_get_drive
    c_public ll_get_drive
func ll_get_drive
    push    bp
    mov     bp,sp
    mov     ax,1900h                        ; dos get current drive function
    int     21h                             ; call dos
    xor     ah,ah                           ; clear high byte
    mov     sp,bp
    pop     bp
    ret
c_endp  ll_get_drive
endcode ll_get_drive

;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
;   bool    ll_get_cwd(int drive);
;   ll_get_cwd gets the current working directory for the specified drive.
;   0 means the current drive, 1 means drive A, 2 means drive B, etc. it
;   returns a 0 if an error occurs and errno is set.
;
begcode ll_get_cwd
    c_public ll_get_cwd
func ll_get_cwd
    push    bp
    mov     bp,sp
    push    dx
    push    si
    push    ds
    mov     dx,P[bp]                        ; get drive
    mov     si,P[bp + 2]                    ; get offset of buffer
if LPTR                                     ; if large memory model
    mov     ds,P[bp + 4]                    ; get segment of buffer
endif
    mov     ax,4700h                        ; dos get current cwd function
    int     21h                             ; call DOS
    jc      get_cwd_err                     ; carry flag indicates error
    xor     ax,ax
    jmp     get_cwd_ret
get_cwd_err:
    mov     _errno,ax                       ; set errno to error
    mov     ax,0ffffh                       ; & set ax to -1
get_cwd_ret:
    pop     ds
    pop     si
    pop     dx
    mov     sp,bp
    pop     bp
    ret
c_endp  ll_get_cwd
endcode ll_get_cwd

END




<a name="013f_0013"><a name="013f_0013">
<a name="013f_0014">
[LISTING SIX]
<a name="013f_0014">

/* FILETEST.CPP Written by Kevin D. Weeks Released to the Public Domain  */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "file.hpp"

// File_Spec test cases
char    *device_test[] =
{
    "",                                     // no device
    "a:",                                   // complete device
    ">:",                                   // invalid file char
    "ab:",                                  // device too long
    "a::",                                  // invalid char (double colon)
    NULL
};

char    *prefix_test[] =
{
  "",                                     // no prefix
  "\\sub\\",                              // complete prefix
  "\\>sub\\",                             // complete with invalid file char
  // prefix too long
  "\\0123456789\\0123456789\\0123456789\\0123456789\\0123456789\

\0123456789\\",
  "sub.dir\\",                            // incomplete prefix (no backslash)
  "\\\\sub\\",                            // double back slash
  "\\sub.dir\\",                          // complete prefix with extension
  "sub1\\sub2\\",                         // bi-level incomplete prefix
  "\\sub1\\sub2\\",                       // bi-level conplete prefix
  "..\\sub",                              // relative, incomplete prefix
  "..\\sub\\",                            //     "   ,      "       "
  "..\\",                                 //     "   ,      "       "
  ".\\sub\\",                             //     "   ,      "       "
  "\\..\\sub\\",                          // relative but starts at root
  "..\\>sub\\",                           // relative with invalid file char
  "\\",                                   // complete prefix
  NULL
};

char    *name_test[] =
{
    "",                                     // no name
    "file.",                                 // complete name
    "file>.",                               // complete with invalid file char
    "filetest1.",                           // name too long
    "file..",                               // invalid char (double dot)
    "file",                                // incomplete, no dot
    NULL
};

char    *suffix_test[] =
{
    "",                                     // no suffix
    ".tst",                                  // complete suffix
    ".ts>",                                  // complete with invalid file char
    ".tst1",                                 // suffix too long
    NULL
};

char    **test[] =
{
    device_test,
    prefix_test,
    name_test,
    suffix_test
};

char    *test_type[] =
{
    "DEVICE ",
    "PREFIX ",
    "NAME ",
    "SUFFIX "
};

extern volatile int  errno;
void    check_file_spec(void);
void    check_file(void);
void    make_filespec(char *test_case);
void    print_condition(File_Spec& file,char *test_name,char *test_case);

int     main(void)
{
    check_file_spec();
    check_file();
}

void    check_file_spec()
{
    int     i, j, k;
    char    test_case[81];
    char    title[81];

    printf("\nTESTING File_Spec...\n\n");
    File_Spec   file1;                      // check void constructor
    print_condition(file1,"void constructor"," ");

    for (i = 0; i < 4; i++)
    {
        printf("CHECKING %s\n",test_type[i]);
        j = 0;
        while (test[i][j] != NULL)
        {
            make_filespec(test[i][j]);
            ++j;
        }
    }

    // check first four complete combinations
    printf("\nCHECKING COMPLETE FILE SPECS\n");
    for (i = 0; i < 4; i++)
    {
        strcpy(test_case,test[0][i]);
        strcat(test_case,test[1][i]);
        strcat(test_case,test[2][i]);
        strcat(test_case,&test[3][i][1]);
        make_filespec(test_case);
    }

    for (i = 0; i < 4; i++)
    {
        printf("CHECKING change_%s\n",test_type[i]);
        j = 0;
        while (test[i][j] != NULL)
        {
            switch (i)
            {
                case 0:
                    if (file1.change_device(test[i][j]) == FALSE)
                        printf("Error changing device");
                    break;
                case 1:
                    if (file1.change_prefix(test[i][j]) == FALSE)
                        printf("Error changing prefix");
                    break;
                case 2:
                    if (file1.change_name(test[i][j]) == FALSE)
                        printf("Error changing name");
                    break;
                case 3:
                    if (file1.change_suffix(test[i][j]) == FALSE)
                        printf("Error changing suffix");
                    break;
            };
            print_condition(file1," ",test[i][j]);
            ++j;
        }
    }
    printf("\nCOMPLETION TEST\n");
    file1.change_device();                  // erase current device
    file1.change_prefix();                  // & prefix
    print_condition(file1,"BEFORE"," ");
    file1.complete();
    print_condition(file1,"AFTER"," ");

    File_Spec file2 = file1;
    print_condition(file2,"\nTEST '=' OPERATOR\n","file2 = file1");

}

void    make_filespec(char *test_case)
{
    File_Spec file2(test_case);
    print_condition(file2,"char constructor",test_case);
    File_Spec file3(file2);
    print_condition(file3,"copy constructor",test_case);
}

void    print_condition(File_Spec& file, char *test_name, char *test_case)
{
    unsigned int    status;
    char            *completion;
    char            *character;
    static char     incomplete[] = {"INCOMPLETE"};
    static char     complete[] = {"  complete"};
    static char     invalid[] = {"INVALID CHAR"};
    static char     valid[] = {"  chars ok  "};

    printf("%s\t%s\n",test_name,test_case);
    status = file.status();
    printf("file condition: %x\n",status);

    completion = (status & FLAG_DEVICE) ? incomplete : complete;
    character = (status & (FLAG_DEVICE << 4)) ? invalid : valid;
    printf("\tdevice: %-9s\t%s\t%s\n",file.get_device(),completion,character);

    completion = (status & FLAG_PREFIX) ? incomplete : complete;
    character = (status & (FLAG_PREFIX << 4)) ? invalid : valid;
    printf("\tprefix: %-9s\t%s\t%s\n",file.get_prefix(),completion,character);

    completion = (status & FLAG_NAME) ? incomplete : complete;
    character = (status & (FLAG_NAME << 4)) ? invalid : valid;
    printf("\t  name: %-9s\t%s\t%s\n",file.get_name(),completion,character);

    character = (status & (FLAG_SUFFIX << 4)) ? invalid : valid;
    printf("\tsuffix: %-9s\t%s\t%s\n",file.get_suffix(),"          ",character);

    printf("\tfilespec: %s\n\n",file.filespec());
}

void    check_file(void)
{
    char    buffer[81];
    int     i;

    printf("\n\n\nTESTING File...\n\n\n");

    /* we won't try to perform any constructor tests since most of the
        attributes are in-accessable and therefore best checked using
        either a source-level debugger or printf statements. */
    File file1("file.tst");

    printf("Creating %s\n",file1.filespec());
    if (file1.create() == FALSE)
    {
        printf("%s already exists. Re-creating it.\n",file1.filespec());
        if (file1.create(F_RDWR,FALSE) == FALSE)
        {
            printf("Error re-creating %s\n",file1.filespec());
            perror("");
            return;
        }
    }
    printf("File %s successfully created and opened\n",file1.filespec());

    strcpy(buffer,"this is a test file");
    i = strlen(buffer);
    if (file1.write(buffer,i) == FALSE)
    {
        perror("Error writing");
        printf("Closing file\n");
        return;
    }
    printf("\"%s\" written to file\n",buffer);
    printf("Current file position is: %ld\n",file1.get_position());
    printf("Current file length is: %ld\n",file1.size());
    if (file1.read(buffer,i) == FALSE)
    {
        perror("Error reading");
        printf("Closing file\n");
        return;
    }
    printf("\"%s\" read from file\n",buffer);
    if (file1.rename("test.fil") == FALSE)
    {
        perror("Error renaming");
        printf("Closing file\n");
        return;
    }
    printf("File renamed to %s\n",file1.filespec());

    File *file2 = file1.copy("test2.fil");
    if (errno)
    {
        perror("test2.fil");
        printf("Overwriting it.\n");
        file2 = file1.copy("test2.fil",TRUE);
  }
  if (file2->exists())
   printf("%s successfully copied to %s\n",file1.filespec(),file2->filespec());
  else
  {
        printf("Copy failed.\n");
        return;
    }

    delete file2;

}










Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.