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.
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;
}