Write files in such a way that a program crash, system crash, powerdown,

etc. won't cause data loss if we're running on a journalling filesystem.
This commit is contained in:
Glenn Maynard
2003-12-21 07:23:29 +00:00
parent 50f5ddc5ea
commit 20792a51bb
10 changed files with 178 additions and 38 deletions
+27 -10
View File
@@ -25,7 +25,7 @@ RageFile::RageFile()
m_FilePos = 0;
}
RageFile::RageFile( const CString& path, RageFile::OpenMode mode )
RageFile::RageFile( const CString& path, int mode )
{
m_File = NULL;
m_BufUsed = 0;
@@ -57,10 +57,9 @@ CString RageFile::GetPath() const
return m_File->GetDisplayPath();
}
bool RageFile::Open( const CString& path, RageFile::OpenMode mode )
bool RageFile::Open( const CString& path, int mode )
{
ASSERT( FILEMAN );
Close();
m_Path = path;
@@ -71,6 +70,18 @@ bool RageFile::Open( const CString& path, RageFile::OpenMode mode )
m_FilePos = 0;
ResetBuf();
if( (m_Mode&READ) && (m_Mode&WRITE) )
{
SetError( "Reading and writing are mutually exclusive" );
return false;
}
if( !(m_Mode&READ) && !(m_Mode&WRITE) )
{
SetError( "Neither reading nor writing specified" );
return false;
}
int error;
m_File = FILEMAN->Open( path, mode, *this, error );
@@ -104,7 +115,7 @@ int RageFile::GetLine( CString &out )
if ( !IsOpen() )
RageException::Throw("\"%s\" is not open.", m_Path.c_str());
if( m_Mode != READ )
if( !(m_Mode&READ) )
RageException::Throw("\"%s\" is not open for reading", GetPath().c_str());
bool GotData = false;
@@ -209,7 +220,7 @@ int RageFile::Read( void *buffer, size_t bytes )
if( !IsOpen() )
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
if( m_Mode != READ )
if( !(m_Mode&READ) )
RageException::Throw("\"%s\" is not open for reading", GetPath().c_str());
int ret = 0;
@@ -244,7 +255,7 @@ int RageFile::Seek( int offset )
if( !IsOpen() )
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
if( m_Mode != READ )
if( !(m_Mode&READ) )
RageException::Throw("\"%s\" is not open for reading; can't seek", GetPath().c_str());
m_EOF = false;
@@ -284,7 +295,7 @@ int RageFile::SeekCur( int offset )
if( !IsOpen() )
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
if( m_Mode != READ )
if( !(m_Mode&READ) )
RageException::Throw("\"%s\" is not open for reading; can't seek", GetPath().c_str());
if( !offset || m_EOF )
@@ -312,7 +323,7 @@ int RageFile::GetFileSize()
if( !IsOpen() )
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
if( m_Mode != READ )
if( !(m_Mode&READ) )
RageException::Throw("\"%s\" is not open for reading; can't GetFileSize", GetPath().c_str());
return m_File->GetFileSize();
@@ -323,7 +334,7 @@ void RageFile::Rewind()
if( !IsOpen() )
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
if( m_Mode != READ )
if( !(m_Mode&READ) )
RageException::Throw("\"%s\" is not open for reading; can't GetFileSize", GetPath().c_str());
m_EOF = false;
@@ -347,7 +358,7 @@ int RageFile::Write( const void *buffer, size_t bytes )
if( !IsOpen() )
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
if( m_Mode != WRITE )
if( !(m_Mode&WRITE) )
RageException::Throw("\"%s\" is not open for writing", GetPath().c_str());
return m_File->Write( buffer, bytes );
@@ -365,6 +376,12 @@ int RageFile::Write( const void *buffer, size_t bytes, int nmemb )
int RageFile::Flush()
{
if( !m_File )
{
SetError( "Not open" );
return -1;
}
return m_File->Flush();
}
+13 -5
View File
@@ -21,9 +21,17 @@ class RageFile
friend class RageFileObj;
public:
enum OpenMode { READ, WRITE };
enum
{
READ = 0x1,
WRITE = 0x2,
/* Always write directly to the destination file; don't do a safe write. (for logs) */
STREAMED = 0x4
};
RageFile();
RageFile( const CString& path, OpenMode mode = READ );
RageFile( const CString& path, int mode = READ );
~RageFile() { Close(); }
RageFile( const RageFile &cpy );
@@ -36,11 +44,11 @@ public:
const CString &GetRealPath() const { return m_Path; }
CString GetPath() const;
bool Open( const CString& path, OpenMode mode = READ );
bool Open( const CString& path, int mode = READ );
void Close();
bool IsOpen() const { return m_File != NULL; }
OpenMode GetOpenMode() const { return m_Mode; }
int GetOpenMode() const { return m_Mode; }
bool AtEOF() const { return m_EOF; }
CString GetError() const { return m_Error; }
void ClearError() { m_Error = ""; }
@@ -76,7 +84,7 @@ private:
RageFileObj *m_File;
CString m_Path;
OpenMode m_Mode;
int m_Mode;
CString m_Error;
bool m_EOF;
+1 -1
View File
@@ -11,7 +11,7 @@ class RageFileDriver
public:
RageFileDriver( FilenameDB *db ) { FDB = db; }
virtual ~RageFileDriver();
virtual RageFileObj *Open( const CString &path, RageFile::OpenMode mode, RageFile &p, int &err ) = 0;
virtual RageFileObj *Open( const CString &path, int mode, RageFile &p, int &err ) = 0;
virtual void GetDirListing( const CString &sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo );
virtual RageFileManager::FileType GetFileType( const CString &sPath );
virtual int GetFileSizeInBytes( const CString &sFilePath );
+127 -12
View File
@@ -273,18 +273,33 @@ static bool CreateDirectories( CString Path )
}
RageFileObj *MakeFileObjDirect( CString sPath, RageFile::OpenMode mode, RageFile &p, int &err )
static CString MakeTempFilename( const CString &sPath )
{
int flags = O_BINARY;
if( mode == RageFile::READ )
flags |= O_RDONLY;
/* "Foo/bar/baz" -> "Foo/bar/new.baz". Prepend to the basename, so if we're
* writing something that might be wildcard-searched, these temp files won't
* match. */
return Dirname(sPath) + "new." + Basename(sPath);
}
RageFileObj *MakeFileObjDirect( CString sPath, int mode, RageFile &p, int &err )
{
int fd;
if( mode & RageFile::READ )
{
fd = DoOpen( sPath, O_BINARY|O_RDONLY, 0644 );
}
else
{
flags |= O_WRONLY|O_CREAT|O_TRUNC;
CString out;
if( mode & RageFile::STREAMED )
out = sPath;
else
out = MakeTempFilename(sPath);
/* Open a temporary file for writing. */
fd = DoOpen( out, O_BINARY|O_WRONLY|O_CREAT|O_TRUNC, 0644 );
}
int fd = DoOpen( sPath, flags, 0644 );
if( fd == -1 )
{
err = errno;
@@ -294,7 +309,7 @@ RageFileObj *MakeFileObjDirect( CString sPath, RageFile::OpenMode mode, RageFile
return new RageFileObjDirect( sPath, fd, p );
}
RageFileObj *RageFileDriverDirect::Open( const CString &path, RageFile::OpenMode mode, RageFile &p, int &err )
RageFileObj *RageFileDriverDirect::Open( const CString &path, int mode, RageFile &p, int &err )
{
CString sPath = path;
@@ -304,7 +319,7 @@ RageFileObj *RageFileDriverDirect::Open( const CString &path, RageFile::OpenMode
/* XXX: does it? */
FDB->ResolvePath( sPath );
if( mode == RageFile::WRITE )
if( mode & RageFile::WRITE )
{
const CString dir = Dirname(sPath);
if( this->GetFileType(dir) != RageFileManager::TYPE_DIR )
@@ -417,16 +432,116 @@ RageFileObjDirect::RageFileObjDirect( const CString &path_, int fd_, RageFile &p
fd = fd_;
ASSERT( fd != -1 );
if( parent.GetOpenMode() == RageFile::WRITE )
if( parent.GetOpenMode() & RageFile::WRITE )
write_buf.reserve( BUFSIZE );
}
RageFileObjDirect::~RageFileObjDirect()
{
Flush();
bool failed = false;
if( parent.GetOpenMode() & RageFile::WRITE )
{
/* Flush the output buffer. */
Flush();
if( !(parent.GetOpenMode() & RageFile::STREAMED) )
{
/* Force a kernel buffer flush. */
if( fsync( fd ) == -1 )
{
LOG->Warn("Error synchronizing %s: %s", this->path.c_str(), strerror(errno) );
SetError( strerror(errno) );
failed = true;
}
}
}
if( fd != -1 )
close( fd );
{
if( close( fd ) == -1 )
{
LOG->Warn("Error closing %s: %s", this->path.c_str(), strerror(errno) );
SetError( strerror(errno) );
failed = true;
}
}
/* If we failed to flush the file properly, something's amiss--don't touch the original file! */
if( !failed &&
(parent.GetOpenMode()&RageFile::WRITE) &&
!(parent.GetOpenMode() & RageFile::STREAMED) )
{
/*
* We now have path written to MakeTempFilename(path). Rename the temporary
* file over the real path. This should be an atomic operation with a journalling
* filesystem. That is, there should be no intermediate state a JFS might restore
* the file we're writing (in the case of a crash/powerdown) to an empty or partial
* file.
*
* If we want to keep a backup, we can move the old file to "path.old" and then
* the new file to "path". However, this leaves an intermediate state between
* the renames where no "path" exists, so if we're restored to that, then we
* won't be able to find the file. The data is still there--as path.old, provided
* it's not overwritten again--but the user will have to recover it on his own.
* A safer (but much slower) way to do this is to simply CopyFile a backup first.
*/
CString sOldPath = MakeTempFilename(path);
CString sNewPath = path;
#if defined(XBOX)
sOldPath.Replace( "/", "\\" );
sNewPath.Replace( "/", "\\" );
#endif
#if defined(WIN32)
/* Windows botches rename: it returns error if the file exists. In NT,
* we can use MoveFileEx( new, old, MOVEFILE_REPLACE_EXISTING ) (though I
* don't know if it has similar atomicity guarantees to rename). In
* 9x, we're screwed, so just delete any existing file (we aren't going
* to be robust on 9x anyway). */
int err = MoveFileEx( sOldPath, sNewPath, MOVEFILE_REPLACE_EXISTING )? ERROR_SUCCESS:GetLastError();
if( err == ERROR_ACCESS_DENIED )
{
/* Try turning off the read-only bit on the file we're overwriting. */
SetFileAttributes( sNewPath, FILE_ATTRIBUTE_NORMAL );
err = MoveFileEx( sOldPath, sNewPath, MOVEFILE_REPLACE_EXISTING )? ERROR_SUCCESS:GetLastError();
}
if( err == ERROR_NOT_SUPPORTED )
{
/* We're in 9x. Delete the old file and rename. */
if( DoRemove(sOldPath) == -1 && errno != ENOENT )
{
LOG->Warn( "Error removing %s: %s", sOldPath.c_str(), strerror(errno) );
SetError( strerror(errno) );
}
else if( rename( sOldPath, sNewPath ) == -1 )
{
LOG->Warn( "Error renaming \"%s\" to \"%s\": %s",
sOldPath.c_str(), sNewPath.c_str(), strerror(errno) );
SetError( strerror(errno) );
}
}
else if( err != ERROR_SUCCESS )
{
/* We failed. */
const CString error = werr_ssprintf( err, "Error renaming \"%s\" to \"%s\"",
sOldPath.c_str(), sNewPath.c_str() );
LOG->Warn( "%s", error.c_str() );
SetError( error );
}
#else
if( rename( sOldPath, sNewPath ) == -1 )
{
LOG->Warn( "Error renaming \"%s\" to \"%s\": %s",
sOldPath.c_str(), sNewPath.c_str(), strerror(errno) );
SetError( strerror(errno) );
}
#endif
}
}
int RageFileObjDirect::Read( void *buf, size_t bytes )
+1 -1
View File
@@ -8,7 +8,7 @@ class RageFileDriverDirect: public RageFileDriver
public:
RageFileDriverDirect( CString root );
RageFileObj *Open( const CString &path, RageFile::OpenMode mode, RageFile &p, int &err );
RageFileObj *Open( const CString &path, int mode, RageFile &p, int &err );
bool Remove( const CString &sPath );
bool Ready();
+1 -1
View File
@@ -432,7 +432,7 @@ RageFileDriverZip::~RageFileDriverZip()
delete Files[i];
}
RageFileObj *RageFileDriverZip::Open( const CString &path, RageFile::OpenMode mode, RageFile &p, int &err )
RageFileObj *RageFileDriverZip::Open( const CString &path, int mode, RageFile &p, int &err )
{
if( mode == RageFile::WRITE )
{
+1 -1
View File
@@ -11,7 +11,7 @@ public:
RageFileDriverZip( CString path );
virtual ~RageFileDriverZip();
RageFileObj *Open( const CString &path, RageFile::OpenMode mode, RageFile &p, int &err );
RageFileObj *Open( const CString &path, int mode, RageFile &p, int &err );
void FlushDirCache( const CString &sPath );
private:
+3 -3
View File
@@ -21,7 +21,7 @@ class RageFileDriverMountpoints: public RageFileDriver
{
public:
RageFileDriverMountpoints(): RageFileDriver( new FilenameDB ) { }
RageFileObj *Open( const CString &path, RageFile::OpenMode mode, RageFile &p, int &err )
RageFileObj *Open( const CString &path, int mode, RageFile &p, int &err )
{
err = (mode == RageFile::WRITE)? EINVAL:ENOENT;
return NULL;
@@ -343,7 +343,7 @@ void RemoveReference( const RageFileObj *obj )
}
/* Used only by RageFile: */
RageFileObj *RageFileManager::Open( const CString &sPath, RageFile::OpenMode mode, RageFile &p, int &err )
RageFileObj *RageFileManager::Open( const CString &sPath, int mode, RageFile &p, int &err )
{
err = ENOENT;
@@ -389,7 +389,7 @@ RageFileObj *RageFileManager::CopyFileObj( const RageFileObj *cpy, RageFile &p )
return ret;
}
RageFileObj *RageFileManager::OpenForWriting( const CString &sPath, RageFile::OpenMode mode, RageFile &p, int &err )
RageFileObj *RageFileManager::OpenForWriting( const CString &sPath, int mode, RageFile &p, int &err )
{
/*
* The value for a driver to open a file is the number of directories and/or files
+2 -2
View File
@@ -30,12 +30,12 @@ public:
void FlushDirCache( const CString &sPath );
/* Used only by RageFile: */
RageFileObj *Open( const CString &sPath, RageFile::OpenMode mode, RageFile &p, int &err );
RageFileObj *Open( const CString &sPath, int mode, RageFile &p, int &err );
void Close( RageFileObj *obj );
RageFileObj *CopyFileObj( const RageFileObj *cpy, RageFile &p );
private:
RageFileObj *OpenForWriting( const CString &sPath, RageFile::OpenMode mode, RageFile &p, int &err );
RageFileObj *OpenForWriting( const CString &sPath, int mode, RageFile &p, int &err );
};
extern RageFileManager *FILEMAN;
+2 -2
View File
@@ -100,10 +100,10 @@ RageLog::RageLog()
remove( INFO_PATH );
// Open log file and leave it open.
if( !g_fileLog.Open( LOG_PATH, RageFile::WRITE ) )
if( !g_fileLog.Open( LOG_PATH, RageFile::WRITE|RageFile::STREAMED ) )
fprintf( stderr, "Couldn't open %s: %s", LOG_PATH, g_fileLog.GetError().c_str() );
if( !g_fileInfo.Open( INFO_PATH, RageFile::WRITE ) )
if( !g_fileInfo.Open( INFO_PATH, RageFile::WRITE|RageFile::STREAMED ) )
fprintf( stderr, "Couldn't open %s: %s", INFO_PATH, g_fileInfo.GetError().c_str() );
this->Info( PRODUCT_NAME_VER );