diff --git a/stepmania/src/RageFile.cpp b/stepmania/src/RageFile.cpp index 27bf7e8539..409896c6af 100644 --- a/stepmania/src/RageFile.cpp +++ b/stepmania/src/RageFile.cpp @@ -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(); } diff --git a/stepmania/src/RageFile.h b/stepmania/src/RageFile.h index b981ed19fa..f9d82ce8d4 100644 --- a/stepmania/src/RageFile.h +++ b/stepmania/src/RageFile.h @@ -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; diff --git a/stepmania/src/RageFileDriver.h b/stepmania/src/RageFileDriver.h index abc1ee9e90..f657b91554 100644 --- a/stepmania/src/RageFileDriver.h +++ b/stepmania/src/RageFileDriver.h @@ -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 ); diff --git a/stepmania/src/RageFileDriverDirect.cpp b/stepmania/src/RageFileDriverDirect.cpp index be1d56573c..d8dfdc4f5e 100644 --- a/stepmania/src/RageFileDriverDirect.cpp +++ b/stepmania/src/RageFileDriverDirect.cpp @@ -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 ) diff --git a/stepmania/src/RageFileDriverDirect.h b/stepmania/src/RageFileDriverDirect.h index b968a40c7d..71981df251 100644 --- a/stepmania/src/RageFileDriverDirect.h +++ b/stepmania/src/RageFileDriverDirect.h @@ -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(); diff --git a/stepmania/src/RageFileDriverZip.cpp b/stepmania/src/RageFileDriverZip.cpp index 175c9e146e..edfd35cd0f 100644 --- a/stepmania/src/RageFileDriverZip.cpp +++ b/stepmania/src/RageFileDriverZip.cpp @@ -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 ) { diff --git a/stepmania/src/RageFileDriverZip.h b/stepmania/src/RageFileDriverZip.h index e907d899a7..c34e711a84 100644 --- a/stepmania/src/RageFileDriverZip.h +++ b/stepmania/src/RageFileDriverZip.h @@ -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: diff --git a/stepmania/src/RageFileManager.cpp b/stepmania/src/RageFileManager.cpp index c6fa389c55..88e9a0896f 100644 --- a/stepmania/src/RageFileManager.cpp +++ b/stepmania/src/RageFileManager.cpp @@ -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 diff --git a/stepmania/src/RageFileManager.h b/stepmania/src/RageFileManager.h index c1bcaab22f..f85e44f8a8 100644 --- a/stepmania/src/RageFileManager.h +++ b/stepmania/src/RageFileManager.h @@ -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; diff --git a/stepmania/src/RageLog.cpp b/stepmania/src/RageLog.cpp index 5bc7f885be..010a0f0062 100644 --- a/stepmania/src/RageLog.cpp +++ b/stepmania/src/RageLog.cpp @@ -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 );