home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
drdobbs
/
1990
/
06
/
weeks.lst
< prev
Wrap
File List
|
1990-05-02
|
54KB
|
1,635 lines
_C++ FILE OBJECTS_
by Kevin Weeks
[LISTING ONE]
/* 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
[LISTING TWO]
/* 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);
}
[LISTING THREE]
/* 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
[LISTING FOUR]
/* 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);
}
[LISTING FIVE]
;******************************************************************************
; 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
[LISTING SIX]
/* 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;
}