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:
@@ -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 )
|
||||
|
||||
Reference in New Issue
Block a user