campo-sirio/arch/filestor.cpp

838 lines
27 KiB
C++
Executable File

//
// FILESTOR.CPP
//
// Source file for ArchiveLib 1.0
//
// Copyright (c) Greenleaf Software, Inc. 1994
// All Rights Reserved
//
// CONTENTS
//
// ALFile::operator new()
// ALFile::ALFile()
// ALFile::~ALFile()
// ALFile::LoadBuffer()
// ALFile::FlushBuffer()
// ALFile::Seek()
// ALFile::Open()
// ALFile::MakeTempName()
// ALFile::Create()
// ALFile::Close()
// ALFile::RenameToBackup()
// ALFile::Rename()
// ALFile::UnRename()
// ALFile::Delete()
//
// DESCRIPTION
//
// This file contains the C++ member functions to support class
// ALFile. This class works very closely with the parent class,
// ALStorage, found in STORAGE.CPP. You will find in many cases
// the virtual functions found here in the derived class call
// the same function in the parent class to help out with some
// of the work.
// We don't really do anything exciting in the WEP, it is just
//
// REVISION HISTORY
//
// May 22, 1994 1.0A : First release
//
//
#include "arclib.h"
#pragma hdrstop
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
//#include <dos.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "filestor.h"
//
// void * ALFile::operator new( size_t size )
//
// ARGUMENTS:
//
// size : The number of bytes needed to create a new ALFile object.
//
// RETURNS
//
// A pointer to the newly allocated storage area, or 0 if no storage
// was available.
//
// DESCRIPTION
//
// When using a DLL, it is easy to get into a dangerous situation when
// creating objects whose ctor and dtor are both in the DLL. The problem
// arises because when you create an object using new, the memory for
// the object will be allocated from the EXE. However, when you destroy
// the object using delete, the memory is freed inside the DLL. Since
// the DLL doesn't really own that memory, bad things can happen.
//
// But, you say, won't the space just go back to the Windows heap regardless
// of who tries to free it? Maybe, but maybe not. If the DLL is using
// a subsegment allocation scheme, it might do some sort of local free
// before returning the space to the windows heap. That is the point where
// you could conceivably cook your heap.
//
// By providing our own version of operator new inside this class, we
// ensure that all memory allocation for the class will be done from
// inside the DLL, not the EXE calling the DLL.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
#if defined( AL_BUILDING_DLL )
void AL_DLL_FAR * AL_PROTO ALFile::operator new( size_t size )
{
return ::new char[ size ];
}
#endif
//
// ALFile::ALFile( const char *file_name = "",
// int buffer_size = 4096,
// ALCase name_case = AL_LOWER)
//
// ARGUMENTS:
//
// file_name : The initial file name of the ALFile object you are
// creating. By default, this is a blank string, which
// will get converted to a temporary name before opening
// the actual disk file.
//
// buffer_size : The size of the object's I/O buffer. The default of 4096
// should give very good performance.
//
// name_case : This parameter determines whether the file names will
// always be converted to upper case, lower case, or left
// in mixed case. Under MS-DOS, you shouldn't use mixed
// case, because the O/S file naming convention is case
// insensitive. ArchiveLib will think "TEMP.BAK" and
// "temp.bak" are different, when they really aren't.
//
// RETURNS
//
// Nothing, this is a constructor.
//
// DESCRIPTION
//
// This constructor is used to create a new ALFile object, which will
// usually be treated as an ALStorage object by ArchiveLib functions.
// It is important to note that not much happens during construction of
// this object, the real activity happens after you call the Open()
// function. Just creating this object *does not* create a file on disk!
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
AL_PROTO ALFile::ALFile( const char AL_DLL_FAR *file_name /* = "" */,
int buffer_size /* = 4096 */,
ALCase name_case /* = AL_LOWER */)
// Note: if non-msdos, change case parameter to AL_MIXED
: ALStorage( file_name, buffer_size, AL_FILE_OBJECT, name_case ) {
miHandle = -1;
}
//
// ALFile::~ALFile()
//
// ARGUMENTS:
//
// None.
//
// RETURNS
//
// Nothing.
//
// DESCRIPTION
//
// The destructor for an ALFile object doesn't have to do much work.
// The base class destructor will take care of freeing the I/O buffer,
// and any other loose ends. All we have to do here is make sure
// the file gets closed, and that its buffers get flushed to the disk
// file.
//
// Note that in debug mode, the destructor also checks this for the
// correct class type. This helps flag erroneous or duplicated
// destructor calls.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
AL_PROTO ALFile::~ALFile()
{
AL_ASSERT( GoodTag(), "~ALFile: attempting to delete invalid object" );
if ( miHandle != -1 )
Close();
}
//
// int ALFile::LoadBuffer( long address )
//
// ARGUMENTS:
//
// address : The long offset into the physical storage object. A
// seek/read combination will be executed at this location,
// so that subsequent calls to read data will start at
// the given address.
//
// RETURNS
//
// AL_SUCCESS, AL_SEEK_ERROR, AL_END_OF_FILE, or possibly another
// error code < AL_SUCCESS.
//
// DESCRIPTION
//
// This function is used in the library whenever a byte needs to be read
// that isn't present in the current I/O buffer. It has to use
// the C RTL function lseek() to go to the correct position in the library.
// If that works, it uses the C RTL function read() to read in an I/O
// buffer full of data.
//
// After that operation is performed, muReadIndex is set to 0, indicating
// that the next read from the I/O buffer will take place at location 0.
// mlFilePointer is set to address plus the number of bytes read, so
// we know where the next read from the file will take place. And
// muBufferValidData is set to the count of bytes read in from this
// location. That lets us know how far we can read in the I/O buffer
// before we run out of space.
//
// Note that if CRC checking has been turned on, we will update the
// current working CRC value with the new data that has been read
// in from the buffer.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::LoadBuffer( long address )
{
if ( mStatus < AL_SUCCESS )
return mStatus;
if ( mlFilePointer != address ) {
long result = lseek( miHandle, address, SEEK_SET );
if ( result == -1L )
return mStatus.SetError( AL_SEEK_ERROR,
"Seek failure on %s. errno = %d",
mName.GetName(),
errno );
}
int result = read( miHandle, mpcBuffer, muBufferSize );
if ( result == 0 )
return AL_END_OF_FILE;
if ( result < 0 )
return mStatus.SetError( AL_READ_ERROR,
"Read failure on %s. errno = %d",
mName.GetName(),
errno );
if ( miUpdateCrcFlag )
UpdateCrc( result );
muReadIndex = 0; //Reading can resume at this location in the I/O buffer
mlFilePointer += result;
muBufferValidData = result;
YieldTime();
return result;
}
//
// int ALFile::FlushBuffer()
//
// ARGUMENTS:
//
// None.
//
// RETURNS
//
// An integer status value, AL_SUCCESS, AL_WRITE_ERROR, or possibly some
// status code < AL_SUCCESS.
//
// DESCRIPTION
//
// This function is the counterpart to LoadBuffer(). It gets called
// when a write operation is poised to overflow the I/O buffer. This
// means we need to flush the buffer out to disk, then reset some
// data members.
//
// Unlike LoadBuffer(), this function doesn't have an address argument,
// so we don't have to perform a seek(). Instead, the data will be
// written out to the current position of the file pointer. If the
// write is successful, muWriteIndex is set to 0, indicating that the
// next write to the I/O buffer can go to position 0. mlFilePointer is
// incremented by the length of the write, so we know where the next read
// or write will occur. Finally, muBufferValidData is set to 0, indicating
// that there is no data in the I/O buffer that has been written, and
// there is no data that can be read.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::FlushBuffer()
{
if ( mStatus < 0 )
return mStatus;
if ( muWriteIndex != 0 ) {
if ( miUpdateCrcFlag )
UpdateCrc( muWriteIndex );
int result = write( miHandle, mpcBuffer, muWriteIndex );
muWriteIndex = 0;
if ( result == -1L )
return mStatus.SetError( AL_WRITE_ERROR,
"Write failure on %s. errno = %d",
mName.GetName(),
errno );
mlFilePointer += result;
}
muReadIndex = 0;
muBufferValidData = 0;
YieldTime();
return AL_SUCCESS;
}
//
// int ALFile::Seek( long address )
//
// ARGUMENTS:
//
// address : The address in the physical disk to seek to.
//
// RETURNS
//
// AL_SUCCESS, AL_SEEK_ERROR, or possibly some other status code < AL_SUCCESS.
//
// DESCRIPTION
//
// This is a function the user can call to position the read/write pointer
// to a new location in the disk file. If there is any data that has been
// written to the I/O buffer, it gets flushed first. After that, we do
// a seek, and update mlFilePointer to reflect the new reality. Note that
// the other important data members will have been updated by FlushBuffer().
//
// And no, this guy doesn't do a LoadBuffer(). Which is fine if you are
// going to do a bunch of writes afterwards. If you are going to read data
// immediately after Seek(), you would have been better of calling
// LoadBuffer().
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::Seek( long address )
{
FlushBuffer();
if ( mStatus < 0 )
return mStatus;
if ( mlFilePointer != address ) {
long result = lseek( miHandle, address, SEEK_SET );
if ( result == -1L )
return mStatus.SetError( AL_SEEK_ERROR,
"Seek failure on %s. errno = %d",
mName.GetName(),
errno );
}
mlFilePointer = address;
return AL_SUCCESS;
}
//
// int ALFile::Open()
//
// ARGUMENTS:
//
// None.
//
// RETURNS
//
// AL_CANT_OPEN_FILE, AL_SUCCESS, or possibly some other error code
// < AL_SUCCESS.
//
// DESCRIPTION
//
// This is an important function, because it converts the ALFile
// object from a dinky little unimportant object, to a big massive
// thing that is ready to do serious work.
//
// The first thing we do here is see if we can open the file. We try
// to open it with READ/WRITE privileges, but we give up and drop back
// to READ only if that doesn't work out.
//
// We then call the base class ALStorage::Open() who takes care of
// allocating buffers and initializing data members.
//
// Finally, we have to get the protection attributes and time date
// stamps for the file. After those are stored off, the file is ready
// for abuse.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::Open()
{
if ( mStatus < AL_SUCCESS )
return mStatus;
miHandle = open( mName, O_BINARY | O_RDWR );
if ( miHandle == -1 && errno == EACCES )
miHandle = open( mName, O_BINARY | O_RDONLY );
if ( miHandle == -1 )
return mStatus.SetError( AL_CANT_OPEN_FILE,
"File open failure. Open of %s returned "
"errno = %d",
mName.GetName(),
errno );
ALStorage::Open();
struct stat buf;
struct tm *tblock;
if ( stat( mName, &buf ) == -1 )
return mStatus.SetError( AL_CANT_OPEN_FILE,
"Couldn't get time, date, and size "
"information for %s. errno = %d.",
mName.GetName(),
errno );
mlSize = buf.st_size;
tblock = localtime( &buf.st_mtime );
mTimeDate.SetTimeDate( tblock );
#if defined( AL_WIN32S )
DWORD attributes = GetFileAttributes( mName );
if ( attributes == 0xFFFFFFFF )
return mStatus.SetError( AL_CANT_OPEN_FILE,
"Couldn't get Win32 file attribute "
"information for %s. GetLastError = %d.",
mName.GetName(),
GetLastError() );
mAttributes.SetFromWin32Attributes( attributes );
#else
unsigned attributes;
if ( _dos_getfileattr( mName, &attributes ) != 0 )
return mStatus.SetError( AL_CANT_OPEN_FILE,
"Couldn't get DOS attribute "
"information for %s. errno = %d.",
mName.GetName(),
errno );
mAttributes.SetFromDosAttributes( attributes );
#endif
return AL_SUCCESS;
}
//
// void ALFile::MakeTempName( int i )
//
// ARGUMENTS:
//
// i : A numeric argument that can somehow be incorporated into
// the temporary file name. Create() will call this function
// while incrementing this number in an attempt to find a unique
// name.
//
// RETURNS
//
// Nothing.
//
// DESCRIPTION
//
// This function is called by Create() and other functions when they
// decide they need to cook up a temporary file name. The single parameter
// i is incremented by the calling program so that repeated calls should
// eventually produce a unique name.
//
// All this function does to create that unique name is perform a sprintf()
// into a buffer using a simple template. The result is copied into the
// mName member, and is ready to be tried out.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
void AL_PROTO ALFile::MakeTempName( int i )
{
char name[ 21 ];
sprintf( name, "~al~%03d.tmp", i );
mName = name;
}
//
// int ALFile::Create()
//
// ARGUMENTS:
//
// None.
//
// RETURNS
//
// AL_SUCCESS, AL_CANT_OPEN_FILE, or possibly some other error code
// < AL_SUCCESS.
//
// DESCRIPTION
//
// This function is used to create a new file storage object. Since
// we are creating a new object, we must be able to open it with read
// access. We will also rudely obliterate any existing file.
//
// The first thing we do here is call the base class Create() function.
// It takes care of setting up the I/O buffer and initializing the
// data members used to support the class.
//
// Next, function checks to see if we have a valid filename. If
// not, a search is made for a valid temporary file name. In either
// case, the file is then opened with R/W access, in O_CREAT
// mode, obliterating any existing file with the same name.
//
// Once the file is open, everything is ready to go, and you can write
// to the file at will. Don't expect much to happen if you try to
// read, however.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::Create()
{
ALStorage::Create();
if ( mStatus < AL_SUCCESS )
return mStatus;
if ( (char *) mName == 0 || strlen( mName ) == 0 ) {
for ( int i = 0 ; i < 999 ; i++ ) {
MakeTempName( i );
miHandle = open( mName,
O_CREAT | O_RDWR | O_BINARY | O_EXCL,
S_IREAD | S_IWRITE );
if ( miHandle != -1 )
break;
else if ( errno != EEXIST && errno != EACCES ) {
mStatus.SetError( AL_CANT_OPEN_FILE,
"Temporary file creation failure. "
"Open of %s returned errno = %d",
mName.GetName(),
errno );
mName = "";
return AL_CANT_OPEN_FILE;
}
}
if ( i == 1000 ) {
mStatus.SetError( AL_CANT_OPEN_FILE,
"Temporary file creation failure. "
"Tried 1000 times to open %s "
"(or a name something like that).",
mName.GetName() );
mName = "";
return AL_CANT_OPEN_FILE;
}
} else {
miHandle = open( mName,
O_CREAT | O_RDWR | O_BINARY | O_TRUNC,
S_IREAD | S_IWRITE );
}
if ( miHandle == -1 )
return mStatus.SetError( AL_CANT_OPEN_FILE,
"File creation failure. "
"Open of %s returned errno = %d",
mName.GetName(),
errno );
return AL_SUCCESS;
}
//
// int ALFile::Close()
//
// ARGUMENTS:
//
// None.
//
// RETURNS
//
// Any status code, hopefully AL_SUCCESS.
//
// DESCRIPTION
//
// This function is called when you are done accessing a file, and want
// to free up its resources. The first thing it does is check to see
// if the file was ever actually opened. If it was, we flush the output
// buffer, then calculate and store the file length. Finally, we close
// the disk file, then call the base class Close() function to clean up
// the buffers and deal with other miscellaneous dirty work.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::Close()
{
if ( miHandle == -1 )
return mStatus;
FlushBuffer();
mlSize = filelength( miHandle );
if ( miCreated && mTimeDate.Valid() ) {
#if defined( AL_WIN32S )
// Can you do this under NT? I don't know how.
#else
_dos_setftime( miHandle, mTimeDate.GetDosDate(), mTimeDate.GetDosTime() );
#endif
}
close( miHandle );
miHandle = -1;
ALStorage::Close();
if ( miCreated && mTimeDate.Valid() ) {
#if defined( AL_WIN32S )
SetFileAttributes( mName, mAttributes.GetWin32Attributes() );
#else
_dos_setfileattr( mName, mAttributes.GetDosAttributes() );
#endif
}
return mStatus;
}
//
// int ALFile::RenameToBackup( int delete_on_clash = 1 )
//
// ARGUMENTS:
//
// delete_on_clash : If this flag is set, it means that we will overwrite
// an existing file with this file if the names clash.
// For example, if I am renaming TEMP.DAT to TEMP.BAK,
// and a TEMP.BAK already exists, I will delete it
// before renaming if this arg is set.
//
// RETURNS
//
// AL_SUCCESS or AL_RENAME_ERROR.
//
// DESCRIPTION
//
// This function is a quick way to rename a storage object. The new
// name created is the default name, which usually means changing the
// file extension to ".BAK", from whatever it was.
//
// You don't see it here, but both the mName member and the physical file
// name are both updated. That all happens in the Rename() function.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::RenameToBackup( int delete_on_clash /* = 1 */ )
{
mName.ChangeExtension();
return Rename( 0, delete_on_clash );
}
//
// int ALFile::Rename( const char *new_name /* = 0 */,
// int delete_on_clash /* = 1 */ )
//
// ARGUMENTS:
//
// new_name : A character pointer to a new file name. If a name is
// defined here, the file is renamed to this new value.
// If this value is 0, it means that we expect that
// the mName member has already been updated with a
// new name. In this case, the old name of the
// file is renamed to the new name.
//
// delete_on_clash : If this flag is set, it means that we will overwrite
// an existing file with this file if the names clash.
// For example, if I am renaming TEMP.DAT to TEMP.BAK,
// and a TEMP.BAK already exists, I will delete it
// before renaming if this arg is set.
//
// RETURNS
//
// AL_SUCCESS or AL_RENAME_ERROR.
//
// DESCRIPTION
//
// This virtual function provides a way to rename a storage object's
// physical implementation. It first updates the mName member if a
// new_name argument is provided. After that, we unlink the clash file
// if one exists, then do a simple rename of mName.mszOldName to
// new_name.
//
// Note that this function does a lot of error checking in debug mode.
// It also does a little error checking in release mode.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::Rename( const char AL_DLL_FAR *new_name /* = 0 */,
int delete_on_clash /* = 1 */ )
{
AL_ASSERT( miHandle == -1, "Rename: attempting to rename open file" );
AL_ASSERT( mName.GetName() != 0, "Rename: attempting to rename file with null name" );
AL_ASSERT( strlen( mName ) > 0, "Rename: attempting to rename file with 0 length name" );
int status;
const char *real_old_name;
const char *real_new_name;
if ( new_name ) {
real_old_name = mName.GetSafeName();
real_new_name = new_name;
} else {
real_old_name = mName.GetSafeOldName();
real_new_name = mName.GetSafeName();
}
#if !defined( AL_WIN32S )
const char *p = strchr( real_new_name, '.' );
if ( p && strlen( p ) > 4 )
return mStatus.SetError( AL_RENAME_ERROR,
"Error trying to rename %s. It has a long "
"extension, which could lead to inadvertent "
"deletion of a file when trying to rename.",
real_old_name );
#endif
if ( delete_on_clash ) {
if ( mName.mCase == AL_MIXED )
status = strcmp( real_new_name, real_old_name );
else
status = stricmp( real_new_name, real_old_name );
if ( status == 0 )
return mStatus.SetError( AL_RENAME_ERROR,
"Error attempting to rename %s to %s. "
"Can't rename to the same name!",
real_new_name,
real_old_name );
status = unlink( real_new_name );
if ( status != 0 && errno != ENOENT )
return mStatus.SetError( AL_RENAME_ERROR,
"Error deleting %s before renaming %s. "
"errno = %d",
real_new_name,
real_old_name,
errno );
}
status = rename( real_old_name, real_new_name );
if ( status != 0 )
return mStatus.SetError( AL_RENAME_ERROR,
"Error renaming %s to %s. errno = %d",
real_old_name,
real_new_name,
errno );
if ( new_name != 0 )
mName = new_name;
return AL_SUCCESS;
}
//
// int ALFile::UnRename( int delete_on_clash /* = 1 */ )
//
// ARGUMENTS:
//
// delete_on_clash : If this flag is set, it means that we will overwrite
// an existing file with this file if the names clash.
// For example, if I am renaming TEMP.BAK to TEMP.DAT,
// and a TEMP.DAT already exists, I will delete it
// before renaming if this arg is set.
//
// RETURNS
//
// AL_SUCCESS or AL_RENAME_ERROR.
//
// DESCRIPTION
//
// This virtual function provides a way to undo a previous renaming of
// a storage object's physical name. We can do this because the mName
// member of ALStorage keeps track both of the current name of the file,
// and the old name. In this case, we just rename the current name to
// the old name. Then we update the mName member so it is accurate.
//
// Note that this function does a lot of error checking in debug mode.
// It also does a little error checking in release mode.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::UnRename( int delete_on_clash /* = 1 */ )
{
AL_ASSERT( miHandle == -1, "UnRename: attempting to rename open file" );
AL_ASSERT( mName.GetName() != 0, "UnRename: attempting to rename file with null name" );
AL_ASSERT( mName.GetOldName() != 0, "UnRename: attempting to rename file with null old name" );
AL_ASSERT( strlen( mName ) > 0, "UnRename: attempting to rename file with 0 length name" );
AL_ASSERT( strlen( mName.GetOldName() ) > 0, "UnRename: attempting to rename file with 0 length old name" );
int status;
if ( delete_on_clash ) {
status = unlink( mName.GetOldName() );
if ( status != 0 && errno != ENOENT )
return mStatus.SetError( AL_RENAME_ERROR,
"Error deleting %s before renaming %s. "
"errno = %d",
mName.GetOldName(),
mName.GetName(),
errno );
}
status = rename( mName, mName.GetOldName() );
if ( status != 0 && errno != ENOENT )
return mStatus.SetError( AL_RENAME_ERROR,
"Error renaming %s to %s. errno = %d",
mName.GetName(),
mName.GetOldName(),
errno );
ALStorage::mName = mName.GetOldName();
return AL_SUCCESS;
}
//
// int ALFile::Delete()
//
// ARGUMENTS:
//
// None.
//
// RETURNS
//
// AL_DELETE_ERROR or AL_SUCCESS.
//
// DESCRIPTION
//
// This function is called to delete the physical object associated with
// a file. This simply means calling the unlink() function for the
// given name.
//
// REVISION HISTORY
//
// May 26, 1994 1.0A : First release
//
int AL_PROTO ALFile::Delete()
{
AL_ASSERT( miHandle == -1, "Delete: attempting to delete open file" );
AL_ASSERT( mName.GetName() != 0, "Delete: attempting to delete file with null name" );
AL_ASSERT( strlen( mName ) > 0, "Delete: attempting to delete file with 0 length name" );
int status = unlink( mName );
if ( status != 0 )
return mStatus.SetError( AL_DELETE_ERROR,
"Error deleting file %s, errno = %d ",
mName.GetName(),
errno );
return AL_SUCCESS;
}