//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #ifdef _WIN32 #include #include #endif #include "basefilesystem.h" #include "packfile.h" #include "tier0/dbg.h" #include "tier0/threadtools.h" #ifdef _WIN32 #include "tier0/tslist.h" #elif defined(POSIX) #include #ifdef LINUX #include #endif #endif #include "tier1/convar.h" #include "tier0/vcrmode.h" #include "tier0/vprof.h" #include "tier1/fmtstr.h" #include "tier1/utlrbtree.h" #include "vstdlib/osversion.h" #ifdef _X360 #undef WaitForSingleObject #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ASSERT_INVARIANT( SEEK_CUR == FILESYSTEM_SEEK_CURRENT ); ASSERT_INVARIANT( SEEK_SET == FILESYSTEM_SEEK_HEAD ); ASSERT_INVARIANT( SEEK_END == FILESYSTEM_SEEK_TAIL ); //----------------------------------------------------------------------------- class CFileSystem_Stdio : public CBaseFileSystem { public: CFileSystem_Stdio(); ~CFileSystem_Stdio(); // Used to get at older versions void *QueryInterface( const char *pInterfaceName ); // Higher level filesystem methods requiring specific behavior virtual void GetLocalCopy( const char *pFileName ); virtual int HintResourceNeed( const char *hintlist, int forgetEverything ); virtual bool IsFileImmediatelyAvailable(const char *pFileName); virtual WaitForResourcesHandle_t WaitForResources( const char *resourcelist ); virtual bool GetWaitForResourcesProgress( WaitForResourcesHandle_t handle, float *progress /* out */ , bool *complete /* out */ ); virtual void CancelWaitForResources( WaitForResourcesHandle_t handle ); virtual bool IsSteam() const { return false; } virtual FilesystemMountRetval_t MountSteamContent( int nExtraAppId = -1 ) { return FILESYSTEM_MOUNT_OK; } bool GetOptimalIOConstraints( FileHandle_t hFile, unsigned *pOffsetAlign, unsigned *pSizeAlign, unsigned *pBufferAlign ); void *AllocOptimalReadBuffer( FileHandle_t hFile, unsigned nSize, unsigned nOffset ); void FreeOptimalReadBuffer( void *p ); protected: // implementation of CBaseFileSystem virtual functions virtual FILE *FS_fopen( const char *filename, const char *options, unsigned flags, int64 *size ); virtual void FS_setbufsize( FILE *fp, unsigned nBytes ); virtual void FS_fclose( FILE *fp ); virtual void FS_fseek( FILE *fp, int64 pos, int seekType ); virtual long FS_ftell( FILE *fp ); virtual int FS_feof( FILE *fp ); virtual size_t FS_fread( void *dest, size_t destSize, size_t size, FILE *fp ); virtual size_t FS_fwrite( const void *src, size_t size, FILE *fp ); virtual bool FS_setmode( FILE *fp, FileMode_t mode ); virtual size_t FS_vfprintf( FILE *fp, const char *fmt, va_list list ); virtual int FS_ferror( FILE *fp ); virtual int FS_fflush( FILE *fp ); virtual char *FS_fgets( char *dest, int destSize, FILE *fp ); virtual int FS_stat( const char *path, struct _stat *buf, bool *pbLoadedFromSteamCache=NULL ); virtual int FS_chmod( const char *path, int pmode ); virtual HANDLE FS_FindFirstFile(const char *findname, WIN32_FIND_DATA *dat); virtual bool FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat); virtual bool FS_FindClose(HANDLE handle); virtual int FS_GetSectorSize( FILE * ); private: bool CanAsync() const { return m_bCanAsync; } bool m_bMounted; bool m_bCanAsync; }; //----------------------------------------------------------------------------- // Per-file worker classes //----------------------------------------------------------------------------- abstract_class CStdFilesystemFile { public: virtual ~CStdFilesystemFile() {} virtual void FS_setbufsize( unsigned nBytes ) = 0; virtual void FS_fclose() = 0; virtual void FS_fseek( int64 pos, int seekType ) = 0; virtual long FS_ftell() = 0; virtual int FS_feof() = 0; virtual size_t FS_fread( void *dest, size_t destSize, size_t size ) = 0; virtual size_t FS_fwrite( const void *src, size_t size ) = 0; virtual bool FS_setmode( FileMode_t mode ) = 0; virtual size_t FS_vfprintf( const char *fmt, va_list list ) = 0; virtual int FS_ferror() = 0; virtual int FS_fflush() = 0; virtual char *FS_fgets( char *dest, int destSize ) = 0; virtual int FS_GetSectorSize() { return 1; } }; //--------------------------------------------------------- class CStdioFile : public CStdFilesystemFile { public: static CStdioFile *FS_fopen( const char *filename, const char *options, int64 *size ); virtual void FS_setbufsize( unsigned nBytes ); virtual void FS_fclose(); virtual void FS_fseek( int64 pos, int seekType ); virtual long FS_ftell(); virtual int FS_feof(); virtual size_t FS_fread( void *dest, size_t destSize, size_t size); virtual size_t FS_fwrite( const void *src, size_t size ); virtual bool FS_setmode( FileMode_t mode ); virtual size_t FS_vfprintf( const char *fmt, va_list list ); virtual int FS_ferror(); virtual int FS_fflush(); virtual char *FS_fgets( char *dest, int destSize ); #ifdef POSIX static CUtlMap< ino_t, CThreadMutex * > m_LockedFDMap; static CThreadMutex m_MutexLockedFD; #endif private: CStdioFile( FILE *pFile, bool bWriteable ) : m_pFile( pFile ), m_bWriteable( bWriteable ) { } FILE *m_pFile; bool m_bWriteable; }; #ifdef POSIX CUtlMap< ino_t, CThreadMutex * > CStdioFile::m_LockedFDMap; CThreadMutex CStdioFile::m_MutexLockedFD; #endif //----------------------------------------------------------------------------- #ifdef _WIN32 class CWin32ReadOnlyFile : public CStdFilesystemFile { public: static bool CanOpen( const char *filename, const char *options ); static CWin32ReadOnlyFile *FS_fopen( const char *filename, const char *options, int64 *size ); virtual void FS_setbufsize( unsigned nBytes ) {} virtual void FS_fclose(); virtual void FS_fseek( int64 pos, int seekType ); virtual long FS_ftell(); virtual int FS_feof(); virtual size_t FS_fread( void *dest, size_t destSize, size_t size); virtual size_t FS_fwrite( const void *src, size_t size ) { return 0; } virtual bool FS_setmode( FileMode_t mode ) { Error( "Can't set mode, open a second file in right mode\n" ); return false; } virtual size_t FS_vfprintf( const char *fmt, va_list list ) { return 0; } virtual int FS_ferror() { return 0; } virtual int FS_fflush() { return 0; } virtual char *FS_fgets( char *dest, int destSize ); virtual int FS_GetSectorSize() { return m_SectorSize; } private: CWin32ReadOnlyFile( HANDLE hFileUnbuffered, HANDLE hFileBuffered, int sectorSize, int64 fileSize, bool bOverlapped ) : m_hFileUnbuffered( hFileUnbuffered ), m_hFileBuffered( hFileBuffered ), m_ReadPos( 0 ), m_Size( fileSize ), m_SectorSize( sectorSize ), m_bOverlapped( bOverlapped ) { } int64 m_ReadPos; int64 m_Size; HANDLE m_hFileUnbuffered; HANDLE m_hFileBuffered; CThreadFastMutex m_Mutex; int m_SectorSize; bool m_bOverlapped; }; #endif //----------------------------------------------------------------------------- // singleton //----------------------------------------------------------------------------- CFileSystem_Stdio g_FileSystem_Stdio; #if defined(_WIN32) && defined(DEDICATED) CBaseFileSystem *BaseFileSystem_Stdio( void ) { return &g_FileSystem_Stdio; } #endif #ifdef DEDICATED // "hack" to allow us to not export a stdio version of the FILESYSTEM_INTERFACE_VERSION anywhere IFileSystem *g_pFileSystem = &g_FileSystem_Stdio; IBaseFileSystem *g_pBaseFileSystem = &g_FileSystem_Stdio; #else EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CFileSystem_Stdio, IFileSystem, FILESYSTEM_INTERFACE_VERSION, g_FileSystem_Stdio ); EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CFileSystem_Stdio, IBaseFileSystem, BASEFILESYSTEM_INTERFACE_VERSION, g_FileSystem_Stdio ); #endif //----------------------------------------------------------------------------- #ifndef _RETAIL bool UseOptimalBufferAllocation() { static bool bUseOptimalBufferAllocation = ( IsX360() || ( !IsLinux() && Q_stristr( Plat_GetCommandLine(), "-unbuffered_io" ) != NULL ) ); return bUseOptimalBufferAllocation; } ConVar filesystem_unbuffered_io( "filesystem_unbuffered_io", "1", 0, "" ); #define UseUnbufferedIO() ( UseOptimalBufferAllocation() && filesystem_unbuffered_io.GetBool() ) #else #define UseUnbufferedIO() true #endif ConVar filesystem_native( "filesystem_native", "1", 0, "Use native FS or STDIO" ); ConVar filesystem_max_stdio_read( "filesystem_max_stdio_read", IsX360() ? "64" : "16", 0, "" ); ConVar filesystem_report_buffered_io( "filesystem_report_buffered_io", "0" ); //----------------------------------------------------------------------------- // constructor //----------------------------------------------------------------------------- CFileSystem_Stdio::CFileSystem_Stdio() { m_bMounted = false; m_bCanAsync = true; #ifdef POSIX SetDefLessFunc( CStdioFile::m_LockedFDMap ); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CFileSystem_Stdio::~CFileSystem_Stdio() { #ifdef POSIX FOR_EACH_MAP_FAST( CStdioFile::m_LockedFDMap, i ) { Assert( CStdioFile::m_LockedFDMap[ i ] ); delete CStdioFile::m_LockedFDMap[ i ]; } CStdioFile::m_LockedFDMap.RemoveAll(); #endif Assert(!m_bMounted); } //----------------------------------------------------------------------------- // QueryInterface: //----------------------------------------------------------------------------- void *CFileSystem_Stdio::QueryInterface( const char *pInterfaceName ) { // We also implement the IMatSystemSurface interface if (!Q_strncmp( pInterfaceName, FILESYSTEM_INTERFACE_VERSION, Q_strlen(FILESYSTEM_INTERFACE_VERSION) + 1)) return (IFileSystem*)this; return CBaseFileSystem::QueryInterface( pInterfaceName ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CFileSystem_Stdio::GetOptimalIOConstraints( FileHandle_t hFile, unsigned *pOffsetAlign, unsigned *pSizeAlign, unsigned *pBufferAlign ) { unsigned sectorSize; if ( hFile && UseOptimalBufferAllocation() ) { CFileHandle *fh = ( CFileHandle *)hFile; sectorSize = fh->GetSectorSize(); if ( !sectorSize || ( fh->m_pPackFileHandle && ( fh->m_pPackFileHandle->AbsoluteBaseOffset() % sectorSize ) ) ) { sectorSize = 1; } } else { sectorSize = 1; } if ( pOffsetAlign ) { *pOffsetAlign = sectorSize; } if ( pSizeAlign ) { *pSizeAlign = sectorSize; } if ( pBufferAlign ) { if ( IsX360() ) { *pBufferAlign = 4; } else { *pBufferAlign = sectorSize; } } return ( sectorSize > 1 ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void *CFileSystem_Stdio::AllocOptimalReadBuffer( FileHandle_t hFile, unsigned nSize, unsigned nOffset ) { if ( !UseOptimalBufferAllocation() ) { return CBaseFileSystem::AllocOptimalReadBuffer( hFile, nSize, nOffset ); } unsigned sectorSize; if ( hFile != FILESYSTEM_INVALID_HANDLE ) { CFileHandle *fh = ( CFileHandle *)hFile; sectorSize = fh->GetSectorSize(); if ( !nSize ) { nSize = fh->Size(); } if ( fh->m_pPackFileHandle ) { nOffset += fh->m_pPackFileHandle->AbsoluteBaseOffset(); } } else { // an invalid handle gets a fake "optimal" but valid buffer // this path is for a caller that isn't doing i/o, // but needs an "optimal" buffer that can end up passed to FreeOptimalReadBuffer() sectorSize = 4; } bool bOffsetIsAligned = ( nOffset % sectorSize == 0 ); unsigned nAllocSize = ( bOffsetIsAligned ) ? AlignValue( nSize, sectorSize ) : nSize; if ( IsX360() ) { return malloc( nAllocSize ); } else { unsigned nAllocAlignment = ( bOffsetIsAligned ) ? sectorSize : 4; return _aligned_malloc( nAllocSize, nAllocAlignment ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CFileSystem_Stdio::FreeOptimalReadBuffer( void *p ) { if ( !UseOptimalBufferAllocation() ) { CBaseFileSystem::FreeOptimalReadBuffer( p ); return; } if ( p ) { if ( IsX360() ) { free( p ); } else { _aligned_free( p ); } } } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- FILE *CFileSystem_Stdio::FS_fopen( const char *filenameT, const char *options, unsigned flags, int64 *size ) { CStdFilesystemFile *pFile = NULL; char filename[ MAX_PATH ]; CBaseFileSystem::FixUpPath ( filenameT, filename, sizeof( filename ) ); #ifdef _WIN32 if ( CWin32ReadOnlyFile::CanOpen( filename, options ) ) { pFile = CWin32ReadOnlyFile::FS_fopen( filename, options, size ); if ( pFile ) { return (FILE *)pFile; } } #endif pFile = CStdioFile::FS_fopen( filename, options, size ); return (FILE *)pFile; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CFileSystem_Stdio::FS_setbufsize( FILE *fp, unsigned nBytes ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); pFile->FS_setbufsize( nBytes ); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CFileSystem_Stdio::FS_fclose( FILE *fp ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); pFile->FS_fclose(); delete pFile; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CFileSystem_Stdio::FS_fseek( FILE *fp, int64 pos, int seekType ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); pFile->FS_fseek( pos, seekType ); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- long CFileSystem_Stdio::FS_ftell( FILE *fp ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); return pFile->FS_ftell(); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CFileSystem_Stdio::FS_feof( FILE *fp ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); return pFile->FS_feof(); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- size_t CFileSystem_Stdio::FS_fread( void *dest, size_t destSize, size_t size, FILE *fp ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); if( ThreadInMainThread() ) { tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesRead" ); } CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); size_t nBytesRead = pFile->FS_fread( dest, destSize, size); Trace_FRead( nBytesRead, fp ); return nBytesRead; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- size_t CFileSystem_Stdio::FS_fwrite( const void *src, size_t size, FILE *fp ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %t", __FUNCTION__, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); if( ThreadInMainThread() ) { tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesWrite" ); } CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); size_t nBytesWritten = pFile->FS_fwrite(src, size); return nBytesWritten; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- bool CFileSystem_Stdio::FS_setmode( FILE *fp, FileMode_t mode ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); return pFile->FS_setmode( mode ); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- size_t CFileSystem_Stdio::FS_vfprintf( FILE *fp, const char *fmt, va_list list ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); return pFile->FS_vfprintf(fmt, list); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CFileSystem_Stdio::FS_ferror( FILE *fp ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); return pFile->FS_ferror(); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CFileSystem_Stdio::FS_fflush( FILE *fp ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); return pFile->FS_fflush(); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- char *CFileSystem_Stdio::FS_fgets( char *dest, int destSize, FILE *fp ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); return pFile->FS_fgets(dest, destSize); } //----------------------------------------------------------------------------- // Purpose: // Input : *path - // pmode - // Output : int //----------------------------------------------------------------------------- int CFileSystem_Stdio::FS_chmod( const char *pathT, int pmode ) { if ( !pathT ) return -1; char path[ MAX_PATH ]; CBaseFileSystem::FixUpPath ( pathT, path, sizeof( path ) ); int rt = _chmod( path, pmode ); #if defined(LINUX) if (rt==-1) { char caseFixedName[ MAX_PATH ]; const bool found = findFileInDirCaseInsensitive_safe( path, caseFixedName ); if ( found ) { rt=_chmod( caseFixedName, pmode ); } } #endif return rt; } //----------------------------------------------------------------------------- // Purpose: A replacement for _stat() backed by GetFileAttributesEx for XP users // // Workaround for: // https://connect.microsoft.com/VisualStudio/feedback/details/1600505/stat-not-working-on-windows-xp-using-v14-xp-platform-toolset-vs2015 // // This is not well tested or meant to be a proper implementation of stat(), but rather a band-aid for XP users only // until microsoft pushes a runtime update to fix above issue :-/ //----------------------------------------------------------------------------- #if defined(_WIN32) && defined(FILESYSTEM_MSVC2015_STAT_BUG_WORKAROUND) static int WindowsXPStatShim( const char *pathT, struct _stat *buf ) { WIN32_FILE_ATTRIBUTE_DATA fileAttributes; if ( !GetFileAttributesEx(pathT, GetFileExInfoStandard, &fileAttributes) ) { *_errno() = ENOENT; return -1; } memset( buf, 0, sizeof(struct _stat) ); // Mode unsigned short permBits = _S_IREAD; // If GetFileAttributes let us see it, we can read it. I think. if ( fileAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { buf->st_mode |= _S_IFDIR; permBits |= _S_IEXEC; } else { buf->st_mode |= _S_IFREG; const char *pExt = V_GetFileExtension( pathT ); if ( V_strcasecmp( pExt, "exe" ) == 0 || V_strcasecmp( pExt, "bat" ) == 0 || V_strcasecmp( pExt, "com" ) == 0 || V_strcasecmp( pExt, "cmd" ) == 0 ) { // Windows stat seems to set this flag for these extensions permBits |= _S_IEXEC; } } if ( fileAttributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY ) { permBits |= S_IWRITE; } // Duplicate permission bits to user/group/world (windows stat doesn't care) buf->st_mode |= permBits | permBits >> 3 | permBits >> 6; // Device is just drive-letter-index according to msdn char driveLetter = tolower( pathT[ 0 ] ); if ( driveLetter >= 'a' && driveLetter <= 'z' && pathT[1] == ':' ) { unsigned char driveIdx = driveLetter - 'a'; buf->st_dev = driveIdx; buf->st_rdev = driveIdx; } else { buf->st_dev = _getdrive(); buf->st_rdev = _getdrive(); } buf->st_nlink = 1; buf->st_size = (uint64_t)fileAttributes.nFileSizeHigh << 32 | fileAttributes.nFileSizeLow; // The 90s was a hell of a time I guess. uint64_t actualAccessTime = (uint64_t)fileAttributes.ftLastAccessTime.dwHighDateTime << 32 | (uint64_t)fileAttributes.ftLastAccessTime.dwLowDateTime; uint64_t actualModTime = (uint64_t)fileAttributes.ftLastWriteTime.dwHighDateTime << 32 | (uint64_t)fileAttributes.ftLastWriteTime.dwLowDateTime; uint64_t actualCreationTime = (uint64_t)fileAttributes.ftCreationTime.dwHighDateTime << 32 | (uint64_t)fileAttributes.ftCreationTime.dwLowDateTime; uint64_t ullMSUniverseToEveryoneElseUniverse = (369 * 365 + 89) * 86400ull; // okay buf->st_atime = actualAccessTime / 10000000ull - ullMSUniverseToEveryoneElseUniverse; buf->st_mtime = actualModTime / 10000000ull - ullMSUniverseToEveryoneElseUniverse; buf->st_ctime = actualCreationTime / 10000000ull - ullMSUniverseToEveryoneElseUniverse; // st_uid/st_gid always 0 on windows return 0; } #endif // defined(_WIN32) && defined(FILESYSTEM_MSVC2015_STAT_BUG_WORKAROUND) //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CFileSystem_Stdio::FS_stat( const char *pathT, struct _stat *buf, bool *pbLoadedFromSteamCache ) { if ( pbLoadedFromSteamCache ) *pbLoadedFromSteamCache = false; if ( !pathT ) { return -1; } char path[ MAX_PATH ]; CBaseFileSystem::FixUpPath ( pathT, path, sizeof( path ) ); int rt = _stat( path, buf ); // Workaround bug wherein stat() randomly fails on Windows XP. See comment on function. #if defined(_WIN32) && defined(FILESYSTEM_MSVC2015_STAT_BUG_WORKAROUND) if ( rt == -1 ) { EOSType eOSType = GetOSType(); if ( eOSType == k_eWin2000 || eOSType == k_eWinXP || eOSType == k_eWin2003 ) { rt = WindowsXPStatShim( path, buf ); } } #endif // defined(_WIN32) && defined(FILESYSTEM_MSVC2015_STAT_BUG_WORKAROUND) #if defined(LINUX) if ( rt == -1 ) { char caseFixedName[ MAX_PATH ]; bool found = findFileInDirCaseInsensitive_safe( path, caseFixedName ); if ( found ) { rt = _stat( caseFixedName, buf ); } } #endif return rt; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- HANDLE CFileSystem_Stdio::FS_FindFirstFile(const char *findnameT, WIN32_FIND_DATA *dat) { char findname[ MAX_PATH ]; CBaseFileSystem::FixUpPath ( findnameT, findname, sizeof( findname ) ); return ::FindFirstFile(findname, dat); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- bool CFileSystem_Stdio::FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat) { if (INVALID_HANDLE_VALUE == handle) // invalid handle should return false return false; return (::FindNextFile(handle, dat) != 0); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- bool CFileSystem_Stdio::FS_FindClose(HANDLE handle) { return (::FindClose(handle) != 0); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- int CFileSystem_Stdio::FS_GetSectorSize( FILE *fp ) { CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); return pFile->FS_GetSectorSize(); } //----------------------------------------------------------------------------- // Purpose: files are always immediately available on disk //----------------------------------------------------------------------------- bool CFileSystem_Stdio::IsFileImmediatelyAvailable(const char *pFileName) { return true; } // enable this if you want the stdio filesystem to pretend it's steam, and make people wait for resources //#define DEBUG_WAIT_FOR_RESOURCES_API #if defined(DEBUG_WAIT_FOR_RESOURCES_API) static float g_flDebugProgress = 0.0f; #endif //----------------------------------------------------------------------------- // Purpose: steam call, unnecessary in stdio //----------------------------------------------------------------------------- WaitForResourcesHandle_t CFileSystem_Stdio::WaitForResources( const char *resourcelist ) { #if defined(DEBUG_WAIT_FOR_RESOURCES_API) g_flDebugProgress = 0.0f; #endif return 1; } //----------------------------------------------------------------------------- // Purpose: steam call, unnecessary in stdio //----------------------------------------------------------------------------- bool CFileSystem_Stdio::GetWaitForResourcesProgress( WaitForResourcesHandle_t handle, float *progress /* out */ , bool *complete /* out */ ) { #if defined(DEBUG_WAIT_FOR_RESOURCES_API) g_flDebugProgress += 0.002f; if (g_flDebugProgress < 1.0f) { *progress = g_flDebugProgress; *complete = false; return true; } #endif // always return that we're complete *progress = 0.0f; *complete = true; return true; } //----------------------------------------------------------------------------- // Purpose: steam call, unnecessary in stdio //----------------------------------------------------------------------------- void CFileSystem_Stdio::CancelWaitForResources( WaitForResourcesHandle_t handle ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFileSystem_Stdio::GetLocalCopy( const char *pFileName ) { // do nothing. . everything is local. } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CFileSystem_Stdio::HintResourceNeed( const char *hintlist, int forgetEverything ) { // do nothing. . everything is local. return 0; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- CStdioFile *CStdioFile::FS_fopen( const char *filenameT, const char *options, int64 *size ) { FILE *pFile = NULL; char *p = NULL; char filename[MAX_PATH]; struct _stat buf; V_strncpy( filename, filenameT, sizeof(filename) ); // stop newline characters at end of filename p = strchr( filename, '\n' ); if ( p ) *p = '\0'; p = strchr( filename, '\r' ); if ( p ) *p = '\0'; pFile = fopen(filename, options); if (pFile && size) { // todo: replace with filelength()? int rt = _stat( filename, &buf ); if (rt == 0) { *size = buf.st_size; } } #if defined(LINUX) if(!pFile && !strchr(options,'w') && !strchr(options,'+') ) // try opening the lower cased version { char caseFixedName[ MAX_PATH ]; bool found = findFileInDirCaseInsensitive_safe( filename, caseFixedName ); if ( found ) { pFile = fopen( caseFixedName, options ); if (pFile && size) { // todo: replace with filelength()? struct _stat buf; int rt = _stat( caseFixedName, &buf ); if (rt == 0) { *size = buf.st_size; } } } } #endif if ( pFile ) { bool bWriteable = false; if ( strchr(options,'w') || strchr(options,'a') ) bWriteable = true; #if defined POSIX if ( bWriteable ) { CThreadMutex *pMutex = NULL; { AUTO_LOCK( m_MutexLockedFD ); // Win32 has an undocumented feature that is serialized ALL writes to a file across threads (i.e only 1 thread can open a file at a time) // so add a lock here to mimic that behavior int iLockID = m_LockedFDMap.Find( buf.st_ino ); if ( iLockID != m_LockedFDMap.InvalidIndex() ) { pMutex = m_LockedFDMap[iLockID]; } else { CThreadMutex *newMutex = new CThreadMutex; pMutex = m_LockedFDMap[m_LockedFDMap.Insert( buf.st_ino, newMutex )]; } } // grab the lock once we have UNLOCKED m_MutexLockedFD so we don't deadlock on a close pMutex->Lock(); rewind( pFile ); // we need to get the file size again after the lock returns if (pFile && size) { int rt = _stat( filename, &buf ); if (rt == 0) { *size = buf.st_size; } } } #endif return new CStdioFile( pFile, bWriteable ); } return NULL; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CStdioFile::FS_setbufsize( unsigned nBytes ) { #ifdef _WIN32 if ( nBytes ) { setvbuf( m_pFile, NULL, _IOFBF, 32768 ); } else { setvbuf( m_pFile, NULL, _IONBF, 0 ); #if defined(_MSC_VER) && ( _MSC_VER < 1900 ) // hack to make microsoft stdio not always read one stray byte on odd sized files m_pFile->_bufsiz = 1; #endif } #endif } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CStdioFile::FS_fclose() { #ifdef POSIX if ( m_bWriteable ) { AUTO_LOCK( m_MutexLockedFD ); struct _stat buf; int fd = fileno_unlocked( m_pFile ); fstat( fd, &buf ); fflush( m_pFile ); int iLockID = m_LockedFDMap.Find( buf.st_ino ); if ( iLockID != m_LockedFDMap.InvalidIndex() ) { m_LockedFDMap[iLockID]->Unlock(); } } #endif fclose(m_pFile); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CStdioFile::FS_fseek( int64 pos, int seekType ) { fseek( m_pFile, pos, seekType ); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- long CStdioFile::FS_ftell() { return ftell(m_pFile); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CStdioFile::FS_feof() { return feof(m_pFile); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- size_t CStdioFile::FS_fread( void *dest, size_t destSize, size_t size ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %t", __FUNCTION__, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); if( ThreadInMainThread() ) { tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesRead" ); } // read (size) of bytes to ensure truncated reads returns bytes read and not 0 return fread( dest, 1, size, m_pFile ); } #define WRITE_CHUNK (256 * 1024) //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper // // This routine breaks data into chunks if the amount to be written is beyond WRITE_CHUNK (256kb) // Windows can fail on monolithic writes of ~12MB or more, so we work around that here //----------------------------------------------------------------------------- size_t CStdioFile::FS_fwrite( const void *src, size_t size ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %t", __FUNCTION__, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); if( ThreadInMainThread() ) { tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesWrite" ); } if ( size > WRITE_CHUNK ) { size_t remaining = size; const byte* current = (const byte *) src; size_t total = 0; while ( remaining > 0 ) { size_t bytesToCopy = min(remaining, (size_t)WRITE_CHUNK); total += fwrite(current, 1, bytesToCopy, m_pFile); remaining -= bytesToCopy; current += bytesToCopy; } Assert( total == size ); return total; } return fwrite(src, 1, size, m_pFile);// return number of bytes written (because we have size = 1, count = bytes, so it return bytes) } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- bool CStdioFile::FS_setmode( FileMode_t mode ) { #ifdef _WIN32 int fd = _fileno( m_pFile ); int newMode = ( mode == FM_BINARY ) ? _O_BINARY : _O_TEXT; return ( _setmode( fd, newMode) != -1 ); #else return false; #endif } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- size_t CStdioFile::FS_vfprintf( const char *fmt, va_list list ) { return vfprintf(m_pFile, fmt, list); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CStdioFile::FS_ferror() { return ferror(m_pFile); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CStdioFile::FS_fflush() { return fflush(m_pFile); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- char *CStdioFile::FS_fgets( char *dest, int destSize ) { return fgets(dest, destSize, m_pFile); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- #ifdef _WIN32 ConVar filesystem_use_overlapped_io( "filesystem_use_overlapped_io", "1", 0, "" ); #define UseOverlappedIO() filesystem_use_overlapped_io.GetBool() //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- int GetSectorSize( const char *pszFilename ) { if ( ( !pszFilename[0] || !pszFilename[1] ) || ( pszFilename[0] == '\\' && pszFilename[1] == '\\' ) || ( pszFilename[0] == '/' && pszFilename[1] == '/' ) ) { // Cannot determine sector size with a UNC path (need volume identifier) return 0; } if ( IsX360() ) { // purposely dvd centric, which is also the worst case return XBOX_DVD_SECTORSIZE; } #if defined( _WIN32 ) && !defined( FILESYSTEM_STEAM ) && !defined( _X360 ) char szAbsoluteFilename[MAX_FILEPATH]; if ( pszFilename[1] != ':' ) { Q_MakeAbsolutePath( szAbsoluteFilename, sizeof(szAbsoluteFilename), pszFilename ); pszFilename = szAbsoluteFilename; } DWORD sectorSize = 1; struct DriveSectorSize_t { char volume; DWORD sectorSize; }; static DriveSectorSize_t cachedSizes[4]; char volume = tolower( *pszFilename ); int i; for ( i = 0; i < ARRAYSIZE(cachedSizes) && cachedSizes[i].volume; i++ ) { if ( cachedSizes[i].volume == volume ) { sectorSize = cachedSizes[i].sectorSize; break; } } if ( sectorSize == 1 ) { char root[4] = "X:\\"; root[0] = *pszFilename; DWORD ignored; if ( !GetDiskFreeSpace( root, &ignored, §orSize, &ignored, &ignored ) ) { sectorSize = 0; } if ( i < ARRAYSIZE(cachedSizes) ) { cachedSizes[i].volume = volume; cachedSizes[i].sectorSize = sectorSize; } } return sectorSize; #else return 0; #endif } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- class CThreadIOEventPool { public: ~CThreadIOEventPool() { CThreadEvent *pEvent; while ( m_Events.PopItem( &pEvent ) ) { delete pEvent; } } CThreadEvent *GetEvent() { CThreadEvent *pEvent; if ( m_Events.PopItem( &pEvent ) ) { return pEvent; } return new CThreadEvent; } void ReleaseEvent( CThreadEvent *pEvent ) { m_Events.PushItem( pEvent ); } private: CTSList m_Events; }; CThreadIOEventPool g_ThreadIOEvents; //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CWin32ReadOnlyFile::CanOpen( const char *filename, const char *options ) { return ( options[0] == 'r' && options[1] == 'b' && options[2] == 0 && filesystem_native.GetBool() ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static HANDLE OpenWin32File( const char *filename, bool bOverlapped, bool bUnbuffered, int64 *pFileSize ) { HANDLE hFile; DWORD createFlags = FILE_ATTRIBUTE_NORMAL; if ( bOverlapped ) { createFlags |= FILE_FLAG_OVERLAPPED; } if ( bUnbuffered ) { createFlags |= FILE_FLAG_NO_BUFFERING; } hFile = ::CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, createFlags, NULL ); if ( hFile != INVALID_HANDLE_VALUE && !*pFileSize ) { LARGE_INTEGER fileSize; if ( !GetFileSizeEx( hFile, &fileSize ) ) { CloseHandle( hFile ); hFile = INVALID_HANDLE_VALUE; } *pFileSize = fileSize.QuadPart; } return hFile; } CWin32ReadOnlyFile *CWin32ReadOnlyFile::FS_fopen( const char *filename, const char *options, int64 *size ) { Assert( CanOpen( filename, options ) ); int sectorSize = 0; bool bTryUnbuffered = ( UseUnbufferedIO() && ( sectorSize = GetSectorSize( filename ) ) != 0 ); bool bOverlapped = UseOverlappedIO(); HANDLE hFileUnbuffered = INVALID_HANDLE_VALUE; int64 fileSize = 0; if ( bTryUnbuffered ) { hFileUnbuffered = OpenWin32File( filename, bOverlapped, true, &fileSize ); if ( hFileUnbuffered == INVALID_HANDLE_VALUE ) { return NULL; } } HANDLE hFileBuffered = OpenWin32File( filename, bOverlapped, false, &fileSize ); if ( hFileBuffered == INVALID_HANDLE_VALUE ) { if ( hFileUnbuffered != INVALID_HANDLE_VALUE ) { CloseHandle( hFileUnbuffered ); } return NULL; } if ( size ) { *size = fileSize; } return new CWin32ReadOnlyFile( hFileUnbuffered, hFileBuffered, ( sectorSize ) ? sectorSize : 1, fileSize, bOverlapped ); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CWin32ReadOnlyFile::FS_fclose() { if ( m_hFileUnbuffered != INVALID_HANDLE_VALUE ) { CloseHandle( m_hFileUnbuffered ); } if ( m_hFileBuffered != INVALID_HANDLE_VALUE ) { CloseHandle( m_hFileBuffered ); } } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CWin32ReadOnlyFile::FS_fseek( int64 pos, int seekType ) { switch ( seekType ) { case SEEK_SET: m_ReadPos = pos; break; case SEEK_CUR: m_ReadPos += pos; break; case SEEK_END: m_ReadPos = m_Size - pos; break; } } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- long CWin32ReadOnlyFile::FS_ftell() { return m_ReadPos; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CWin32ReadOnlyFile::FS_feof() { return ( m_ReadPos >= m_Size ); } // ends up on a thread's stack, don't blindly increase without awareness of that implication // 360 threads have small stacks, using small buffer of the worst case quantum sector size #if !defined( _X360 ) #define READ_TEMP_BUFFER ( 32*1024 ) #else #define READ_TEMP_BUFFER ( 2*XBOX_DVD_SECTORSIZE ) #endif //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- size_t CWin32ReadOnlyFile::FS_fread( void *dest, size_t destSize, size_t size ) { VPROF_BUDGET( "CWin32ReadOnlyFile::FS_fread", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %t", __FUNCTION__, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); if( ThreadInMainThread() ) { tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesRead" ); } if ( !size || ( m_hFileUnbuffered == INVALID_HANDLE_VALUE && m_hFileBuffered == INVALID_HANDLE_VALUE ) ) { return 0; } CThreadEvent *pEvent = NULL; if ( destSize == (size_t)-1 ) { destSize = size; } byte tempBuffer[READ_TEMP_BUFFER]; HANDLE hReadFile = m_hFileBuffered; int nBytesToRead = size; byte *pDest = (byte *)dest; int64 offset = m_ReadPos; if ( m_hFileUnbuffered != INVALID_HANDLE_VALUE ) { const int destBaseAlign = ( IsX360() ) ? 4 : m_SectorSize; bool bDestBaseIsAligned = ( (DWORD)dest % destBaseAlign == 0 ); bool bCanReadUnbufferedDirect = ( bDestBaseIsAligned && ( destSize % m_SectorSize == 0 ) && ( m_ReadPos % m_SectorSize == 0 ) ); if ( bCanReadUnbufferedDirect ) { // fastest path, unbuffered nBytesToRead = AlignValue( size, m_SectorSize ); hReadFile = m_hFileUnbuffered; } else { // not properly aligned, snap to alignments // attempt to perform single unbuffered operation using stack buffer int64 alignedOffset = AlignValue( ( m_ReadPos - m_SectorSize ) + 1, m_SectorSize ); unsigned int alignedBytesToRead = AlignValue( ( m_ReadPos - alignedOffset ) + size, m_SectorSize ); if ( alignedBytesToRead <= sizeof( tempBuffer ) - destBaseAlign ) { // read operation can be performed as unbuffered follwed by a post fixup nBytesToRead = alignedBytesToRead; offset = alignedOffset; pDest = AlignValue( tempBuffer, destBaseAlign ); hReadFile = m_hFileUnbuffered; } } } OVERLAPPED overlapped = { 0 }; if ( m_bOverlapped ) { pEvent = g_ThreadIOEvents.GetEvent(); overlapped.hEvent = *pEvent; } #ifdef REPORT_BUFFERED_IO if ( hReadFile == m_hFileBuffered && filesystem_report_buffered_io.GetBool() ) { Msg( "Buffered Operation :(\n" ); } #endif // some disk drivers will fail if read is too large static int MAX_READ = filesystem_max_stdio_read.GetInt()*1024*1024; const int MIN_READ = 64*1024; bool bReadOk = true; DWORD nBytesRead = 0; size_t result = 0; int64 currentOffset = offset; while ( bReadOk && nBytesToRead > 0 ) { int nCurBytesToRead = min( nBytesToRead, MAX_READ ); DWORD nCurBytesRead = 0; overlapped.Offset = currentOffset & 0xFFFFFFFF; overlapped.OffsetHigh = ( currentOffset >> 32 ) & 0xFFFFFFFF; bReadOk = ( ::ReadFile( hReadFile, pDest + nBytesRead, nCurBytesToRead, &nCurBytesRead, &overlapped ) != 0 ); if ( !bReadOk ) { if ( m_bOverlapped && GetLastError() == ERROR_IO_PENDING ) { bReadOk = true; } } if ( bReadOk ) { if ( !m_bOverlapped || GetOverlappedResult( hReadFile, &overlapped, &nCurBytesRead, true ) ) { nBytesRead += nCurBytesRead; nBytesToRead -= nCurBytesRead; currentOffset += nCurBytesRead; } else { if ( m_bOverlapped ) { if ( GetLastError() == ERROR_HANDLE_EOF ) { nBytesToRead = 0; // we have hit the end of the file } else { bReadOk = false; } } else { bReadOk = false; } } if ( !m_bOverlapped && nCurBytesRead == 0 ) { nBytesToRead = 0; // we have hit the end of the file } } if ( nBytesToRead > 0 && nCurBytesRead == 0 ) // if you failed to ready anything this time then bail the loop { DevMsg( "Got zero length read" ); bReadOk = false; } else if ( !bReadOk ) { DWORD dwError = GetLastError(); if ( IsX360() ) { if ( dwError == ERROR_DISK_CORRUPT || dwError == ERROR_FILE_CORRUPT ) { FSDirtyDiskReportFunc_t func = g_FileSystem_Stdio.GetDirtyDiskReportFunc(); if ( func ) { func(); result = 0; } } } if ( dwError == ERROR_NO_SYSTEM_RESOURCES && MAX_READ > MIN_READ ) { MAX_READ /= 2; bReadOk = true; DevMsg( "ERROR_NO_SYSTEM_RESOURCES: Reducing max read to %d bytes\n", MAX_READ ); } else { DevMsg( "Unknown read error %d\n", dwError ); } } } if ( bReadOk ) { if ( nBytesRead && hReadFile == m_hFileUnbuffered && pDest != dest ) { int nBytesExtra = ( m_ReadPos - offset ); nBytesRead -= nBytesExtra; if ( nBytesRead ) { memcpy( dest, (byte *)pDest + nBytesExtra, size ); } } result = min( (size_t)nBytesRead, size ); } if ( m_bOverlapped ) { pEvent->Reset(); g_ThreadIOEvents.ReleaseEvent( pEvent ); } m_ReadPos += result; return result; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- char *CWin32ReadOnlyFile::FS_fgets( char *dest, int destSize ) { if ( FS_feof() ) { return NULL; } int nStartPos = m_ReadPos; int nBytesRead = FS_fread( dest, destSize, destSize ); if ( !nBytesRead ) { return NULL; } dest[min( nBytesRead, destSize - 1)] = 0; char *pNewline = strchr( dest, '\n' ); if ( pNewline ) { // advance past, leave \n pNewline++; *pNewline = 0; } else { pNewline = &dest[min( nBytesRead, destSize - 1)]; } m_ReadPos = nStartPos + ( pNewline - dest ) + 1; return dest; } #endif // _WIN32