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
+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 )