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:
+27
-10
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
Reference in New Issue
Block a user