ConfigurationFile.cpp [plain text]
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fstream>
#include <cassert>
#include <new>
#include "syslog.h"
#include "ConfigurationFile.h"
using namespace std;
static const mode_t kFilePermissions = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
const char *ConfigurationFile::kBackupSuffix = "~";
const char *ConfigurationFile::kDefaultSuffix = ".default";
const char *ConfigurationFile::kTmpSuffix = ".tmp";
ConfigurationFile::ConfigurationFile( const char *fileName )
throw( std::bad_alloc, std::runtime_error )
: fd( -1 ), file( NULL ), tmpFile( NULL ), kFileName( NULL ),
backupFileName(NULL), defaultFileName(NULL), tmpFileName(NULL),
permissions( kFilePermissions )
{
if( !fileName )
throw std::runtime_error( "NULL filename for ConfigurationFile" );
kFileName = strdup( fileName );
if( !this->kFileName ) {
throw std::bad_alloc();
}
struct stat stats;
int error = stat( kFileName, &stats );
if( error != -1 ) {
permissions = stats.st_mode;
chmod( kFileName, kFilePermissions );
}
try {
backupFileName = new char[strlen(kFileName) + strlen(kBackupSuffix) + 1] ;
defaultFileName = new char[strlen(kFileName) + strlen(kDefaultSuffix) + 1];
tmpFileName = new char[ strlen(kFileName) + strlen(kTmpSuffix) + 1 ];
strcpy( backupFileName, kFileName );
strcpy( defaultFileName, kFileName );
strcpy( tmpFileName, kFileName );
strcat( backupFileName, kBackupSuffix );
strcat( defaultFileName, kDefaultSuffix );
strcat( tmpFileName, kTmpSuffix );
fd = open( kFileName,
O_RDWR | O_EXLOCK | O_NONBLOCK,
kFilePermissions );
if( fd != -1 ) {
file = fdopen( fd, "r+" );
}
else {
file = AttemptRestore( kFileName, backupFileName );
if( !file )
file=AttemptRestore(kFileName,defaultFileName);
if( !file ) {
fd = open( kFileName, O_RDWR | O_EXLOCK | O_CREAT | O_NONBLOCK, kFilePermissions );
file = fdopen( fd, "r+" );
}
}
chmod( kFileName, permissions );
if( !file )
throw runtime_error( UNABLE_TO_OPEN_FILE_ERR );
}
catch( ... ) {
chmod( kFileName, permissions );
CleanupMemory();
throw;
}
}
void ConfigurationFile::CleanupMemory() throw()
{
delete[] backupFileName;
delete[] defaultFileName;
delete[] tmpFileName;
if( kFileName )
free( (char *)kFileName );
kFileName = backupFileName = defaultFileName = tmpFileName = NULL;
}
std::FILE *ConfigurationFile::AttemptRestore( const char *newFileName,
const char *backupFileName )
{
assert( newFileName );
assert( backupFileName );
int backupFD = open( backupFileName, O_RDONLY | O_SHLOCK | O_NONBLOCK,
kFilePermissions );
if( backupFD == -1 ) {
struct stat backupStats;
int error = stat( backupFileName, &backupStats );
if( error == -1 )
return NULL;
mode_t backupPermissions = backupStats.st_mode | S_IRUSR;
chmod( backupFileName, backupPermissions );
backupFD = open( backupFileName,
O_RDONLY | O_SHLOCK | O_NONBLOCK,
kFilePermissions );
chmod( backupFileName, backupStats.st_mode );
if( backupFD == -1 )
return NULL;
}
FILE *backup = fdopen( backupFD, "r" );
if( backup == NULL )
return NULL;
int newFD = open( newFileName, O_RDWR | O_CREAT | O_EXCL | O_EXLOCK,
kFilePermissions );
if( newFD == -1 ) {
flock( backupFD, LOCK_UN );
fclose( backup );
return NULL;
}
FILE *newFile = fdopen( newFD, "r+" );
if( newFile == NULL ) {
flock( backupFD, LOCK_UN );
fclose( backup );
return NULL;
}
#define CLEANUP_FILES() \
do { \
flock( backupFD, LOCK_UN ); \
flock( newFD, LOCK_UN ); \
fclose( backup ); \
fclose( newFile ); \
} while(0)
if( ferror( newFile ) || ferror( backup ) ) {
CLEANUP_FILES();
unlink( newFileName );
return NULL;
}
int pageSize = getpagesize();
assert( pageSize > 0 );
char *buffer = (char *) alloca( pageSize );
assert( buffer != NULL );
while( !feof( backup ) && !ferror( newFile ) && !ferror( backup ) ) {
size_t readSize = fread( buffer, sizeof( buffer[0] ),
sizeof( buffer ), backup );
size_t writeSize = fwrite( buffer, sizeof( buffer[0] ),
readSize, newFile );
if( readSize != writeSize ) {
CLEANUP_FILES();
unlink( newFileName );
return NULL;
}
}
flock( backupFD, LOCK_UN );
fclose( backup );
file = newFile;
fd = newFD;
rewind( file );
fflush( file );
clearerr( file );
return file;
#undef CLEANUP_FILES
}
ConfigurationFile::~ConfigurationFile() throw()
{
if( fd != -1 )
flock( fd, LOCK_UN );
if( file != NULL )
fclose( file );
CleanupMemory();
}
FILE *ConfigurationFile::GetFileForReading() throw()
{
return file;
}
FILE *ConfigurationFile::StartWrite()
{
unlink( tmpFileName );
assert( fd != -1 );
assert( file != NULL );
assert( tmpFile == NULL );
int tmpFD = open( tmpFileName,
O_RDWR | O_TRUNC | O_EXLOCK | O_CREAT | O_NONBLOCK,
kFilePermissions );
if( tmpFD == -1 ) {
return NULL;
}
tmpFile = fdopen( tmpFD, "r+" );
if( tmpFile == NULL ) {
flock( tmpFD, LOCK_UN );
close( tmpFD );
tmpFD = -1 ;
unlink( tmpFileName );
return NULL;
}
return tmpFile;
}
bool ConfigurationFile::StopWrite()
{
assert( tmpFile != NULL );
fflush( tmpFile ) ;
struct stat backupStats;
int error = stat( backupFileName, &backupStats );
mode_t backupPermissions =
error != -1 ? backupStats.st_mode : kFilePermissions;
error = rename( kFileName, backupFileName );
if( error != 0 ) {
flock( fileno( tmpFile ), LOCK_UN );
fclose( tmpFile );
unlink( tmpFileName );
tmpFile = NULL;
return false;
}
chmod( backupFileName, backupPermissions );
error = rename( tmpFileName, kFileName );
if( error != 0 ) {
flock( fileno( tmpFile ), LOCK_UN );
fclose( tmpFile );
unlink( tmpFileName );
tmpFile = NULL;
return false;
}
chmod( kFileName, permissions );
error = flock( fd, LOCK_UN );
error = fclose( file );
file = tmpFile;
fd = fileno( tmpFile );
rewind( file );
tmpFile = NULL;
clearerr(file);
return true;
}