//====== Copyright 1996-2005, Valve Corporation, All rights reserved. =======// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "tier0/platform.h" #ifdef _WIN32 #include #include #endif #include "basefilesystem.h" #ifndef _PS3 #include "filesystemasync.h" #endif #include "tier0/dbg.h" #include "tier0/threadtools.h" #include "tier0/icommandline.h" #ifdef _WIN32 #include "tier0/tslist.h" #elif defined(POSIX) #include #ifdef LINUX #include #endif #endif #include "tier1/convar.h" #include "tier0/vprof.h" #include "tier1/fmtstr.h" #include "tier1/utlrbtree.h" #define GAMEINFO_FILENAME "GAMEINFO.TXT" bool ShouldFailIo() { #if defined( _CERT ) || !defined( _PS3 ) return false; #else static float s_flFailIoAfter = CommandLine()->ParmValue( "-failioafter", 0.0f ); return ( s_flFailIoAfter > 0 && Plat_FloatTime() > s_flFailIoAfter ); #endif } #if defined( _PS3 ) #include #include #include #include #include #include #include #include "ps3/ps3_console.h" // #include "ps3/ps3_gamedata.h" #include "tls_ps3.h" #include "ps3_pathinfo.h" #include #include #if 0 // defined( _PS3 ) #include "MemMgr/inc/MemMgr.h" #include "FileGroup.h" #include "const.h" #include #include "memmgr\inc\PS3VirtualAlloc.h" char gSrcGameDataPath[MAX_PATH]; bool g_bUseBdvdGameData = false; extern uint g_ioThreadId; CFileGroupSystem g_fileGroupSystem; int g_levelLoadGroup = -1; #endif //_PS3 #endif #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 ); #ifdef _PS3 /// A bunch of little subroutines to handle all the ickyness necessary /// in emulating the FindFirstFile() function (use of which is a WTF /// in itself). namespace // unnamed namespaces are a convenient way to mark a whole bunch of stuff as "static" ie internal linkage { int scandir(const char *dir, struct dirent ***namelist, int (*select)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)) { DIR *d; struct dirent *entry; register int i=0; size_t entrysize; if ((d=opendir(dir)) == NULL) return(-1); *namelist=NULL; while ((entry=readdir(d)) != NULL) { if (select == NULL || (select != NULL && (*select)(entry))) { *namelist=(struct dirent **)realloc((void *)(*namelist), (size_t)((i+1)*sizeof(struct dirent *))); if (*namelist == NULL) return(-1); entrysize=sizeof(struct dirent)-sizeof(entry->d_name)+strlen(entry->d_name)+1; (*namelist)[i]=(struct dirent *)malloc(entrysize); if ((*namelist)[i] == NULL) return(-1); memcpy((*namelist)[i], entry, entrysize); i++; } } if (closedir(d)) return(-1); if (i == 0) return(-1); // if (compar != NULL) // qsort((void *)(*namelist), (size_t)i, sizeof(struct dirent *), compar); return(i); } int alphasort(const struct dirent **a, const struct dirent **b) { return(strcmp((*a)->d_name, (*b)->d_name)); } char selectBuf[PATH_MAX]; int FileSelect(const struct dirent *ent) { const char *mask=selectBuf; const char *name=ent->d_name; //DEBUG_PRINTF("Test:%s %s\n",mask,name); if(!strcmp(name,".") || !strcmp(name,"..") ) return 0; if(!strcmp(selectBuf,"*.*")) return 1; while( *mask && *name ) { if(*mask=='*') { mask++; // move to the next char in the mask if(!*mask) // if this is the end of the mask its a match { return 1; } while(*name && toupper(*name)!=toupper(*mask)) { // while the two don't meet up again name++; } if(!*name) { // end of the name break; } } else if (*mask!='?') { if( toupper(*mask) != toupper(*name) ) { // mismatched! return 0; } else { mask++; name++; if( !*mask && !*name) { // if its at the end of the buffer return 1; } } } else /* mask is "?", we don't care*/ { mask++; name++; } } return( !*mask && !*name ); // both of the strings are at the end } int FillDataStruct(FIND_DATA *dat) { struct stat fileStat; if(dat->numMatches<0) return -1; Q_strncpy(dat->cFileName,dat->namelist[dat->numMatches]->d_name, sizeof( dat->cFileName ) ); if(!stat(dat->cFileName,&fileStat)) { dat->dwFileAttributes=fileStat.st_mode; } else { dat->dwFileAttributes=0; } //DEBUG_PRINTF("%s\n", dat->namelist[dat->numMatches]->d_name); free(dat->namelist[dat->numMatches]); dat->numMatches--; return 1; } const char *GetSonyFSErrorString( int errorcode ) { switch( errorcode ) { case CELL_FS_SUCCEEDED: return "Normal termination"; case CELL_FS_ENOTMOUNTED: return "File system corresponding to pathis not mounted"; case CELL_FS_ENOENT: return "File specified by path does not exist"; case CELL_FS_EIO: return "I/O error has occurred"; case CELL_FS_ENOMEM: return "Memory is insufficient "; case CELL_FS_ENOTDIR: return "Components in path contain something other than a directory"; case CELL_FS_ENAMETOOLONG: return "path or components in the path exceed the maximum length "; case CELL_FS_EFSSPECIFIC: return "File system specific internal error has occurred"; case CELL_FS_EFAULT: return "pathor sb is NULL"; case CELL_FS_EACCES: return "Search permission is denied for a component of path. "; default: return "Unknown error code"; } } } HANDLE FindFirstFile(char *fileName, FIND_DATA *dat) { char nameStore[PATH_MAX]; char *dir=NULL; int n,iret=-1; Q_strncpy(nameStore,fileName, sizeof( nameStore ) ); FixUpPathCaseForPS3(nameStore); if(strrchr(nameStore,'/') ) { dir=nameStore; while(strrchr(dir,'/') ) { struct stat dirChk; // zero this with the dir name dir=strrchr(nameStore,'/'); *dir='\0'; dir=nameStore; stat(dir,&dirChk); if( dirChk.st_mode & _S_IFDIR ) { break; } } } else { // couldn't find a dir seperator... return ( void * ) INVALID_HANDLE_VALUE; } if( strlen(dir)>0 ) { Q_strncpy(selectBuf,fileName+strlen(dir)+1, sizeof( selectBuf ) ); n = scandir(dir, &dat->namelist, FileSelect, alphasort); if (n < 0) { // silently return, nothing interesting } else { dat->numMatches=n-1; // n is the number of matches iret=FillDataStruct(dat); if(iret<0) { free(dat->namelist); } } } return reinterpret_cast(iret); } bool FindNextFile(HANDLE handle, FIND_DATA *dat) { AssertMsg( false, "WARNING: untested\n" ); if(dat->numMatches<0) { free(dat->namelist); return false; // no matches left } FillDataStruct(dat); return true; } bool FindClose(HANDLE handle) { AssertMsg( false, "WARNING: untested\n" ); return true; } #endif #if 0 // defined(_PS3) #define DebugPrint(fmt, ...) Msg( fmt, ## __VA_ARGS__ ) static bool ThreadInIoThread() { return( ThreadGetCurrentId() == g_ioThreadId ); } #endif //_PS3 #if __DARWIN_64_BIT_INO_T #error badness #endif #if _DARWIN_FEATURE_64_BIT_INODE #error additional badness #endif //----------------------------------------------------------------------------- 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, CFileLoadInfo *pInfo ); 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 ); 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 ); #if defined( POSIX ) && !defined( _PS3 ) static CUtlMap< int, CInterlockedInt > m_LockedFDMap; #endif private: CStdioFile( FILE *pFile, bool bWriteable ) : m_pFile( pFile ), m_bWriteable( bWriteable ) { } FILE *m_pFile; bool m_bWriteable; }; #if defined( POSIX ) && !defined( _PS3 ) CUtlMap< int, CInterlockedInt > CStdioFile::m_LockedFDMap; #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 #if IsPlatformPS3() class CFiosReadOnlyFile : public CStdFilesystemFile { public: static bool CanOpen( const char *filename, const char *options ); static CFiosReadOnlyFile *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 2048; } private: CFiosReadOnlyFile( cell::fios::filehandle * pFileHandle, int64 nFileSize ) : m_pHandle( pFileHandle ), m_nSize( nFileSize ), m_nReadPos( 0 ) { // Do nothing... } cell::fios::filehandle * m_pHandle; int64 m_nSize; int64 m_nReadPos; }; #endif //----------------------------------------------------------------------------- // singleton //----------------------------------------------------------------------------- CFileSystem_Stdio g_FileSystem_Stdio; #ifndef _PS3 CAsyncFileSystem g_FileSystem_Async; #endif #if defined(_WIN32) && defined(DEDICATED) CBaseFileSystem *BaseFileSystem_Stdio( void ) { return &g_FileSystem_Stdio; } #endif #if defined( DEDICATED ) && defined( LAUNCHERONLY ) // "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 _PS3 EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CAsyncFileSystem, IAsyncFileSystem, ASYNCFILESYSTEM_INTERFACE_VERSION, g_FileSystem_Async ); #endif // _PS3 //----------------------------------------------------------------------------- bool UseOptimalBufferAllocation() { static bool bUseOptimalBufferAllocation = ( IsX360() || ( !IsPosix() && 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() ) 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" ); #if IsPlatformPS3() extern bool g_bUseFiosHddCache; ConVar fs_fios_enable_hdd_cache( "fs_fios_enable_hdd_cache", "0", 0, "Use fios HDD cache, disable this to have normal BluRay speed. 1 to enable it, 0 to disable it." ); #endif //----------------------------------------------------------------------------- // constructor //----------------------------------------------------------------------------- CFileSystem_Stdio::CFileSystem_Stdio() { m_bMounted = false; m_bCanAsync = true; #if defined( POSIX ) && !defined( _PS3 ) SetDefLessFunc( CStdioFile::m_LockedFDMap ); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CFileSystem_Stdio::~CFileSystem_Stdio() { 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; CFileHandle *fh = ( CFileHandle *)hFile; #ifdef SUPPORT_VPK if ( fh && fh->m_VPKHandle ) { return false; } #endif if ( hFile && UseOptimalBufferAllocation() ) { 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 ); } // was from launcher.cpp in EA PS3 port, but can't do that in a PRX const char* GetGameMode() { return "portal2"; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- 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 *filename, const char *options, unsigned flags, int64 *size, CFileLoadInfo *pInfo ) { if( ShouldFailIo() ) return NULL; CStdFilesystemFile *pFile = NULL; if ( pInfo ) pInfo->m_bLoadedFromSteamCache = false; #ifdef _WIN32 if ( CWin32ReadOnlyFile::CanOpen( filename, options ) ) { pFile = CWin32ReadOnlyFile::FS_fopen( filename, options, size ); return (FILE *)pFile; } #endif #if IsPlatformPS3() if ( CFiosReadOnlyFile::CanOpen( filename, options ) ) { pFile = CFiosReadOnlyFile::FS_fopen( filename, options, size ); 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); if ( m_WhitelistFileTrackingEnabled ) m_FileTracker2.RecordFileClose( 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); if ( m_WhitelistFileTrackingEnabled ) m_FileTracker2.RecordFileSeek( fp, pos, seekType ); 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 ) { if( ShouldFailIo() ) return 0; CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); size_t nBytesRead = pFile->FS_fread( dest, destSize, size); Trace_FRead( nBytesRead, fp ); if ( m_WhitelistFileTrackingEnabled ) m_FileTracker2.RecordFileRead( dest, nBytesRead, size, fp ); return nBytesRead; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- size_t CFileSystem_Stdio::FS_fwrite( const void *src, size_t size, FILE *fp ) { if( ShouldFailIo() ) return 0; 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 ) { if( ShouldFailIo() ) return 0; 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 *path, int pmode ) { if ( !path ) return -1; int rt = _chmod( path, pmode ); #if defined( LINUX ) if (rt==-1) { char file[MAX_PATH]; if ( findFileInDirCaseInsensitive(path, file) ) { rt=_chmod(file,pmode); } } #endif return rt; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CFileSystem_Stdio::FS_stat( const char *path, struct _stat *buf ) { if ( !path ) { return -1; } int rt; #ifdef _PS3 CellFsStat cellBuf; CellFsErrno retFs = cellFsStat(path, &cellBuf); if(retFs == CELL_FS_SUCCEEDED) { buf->st_atime = cellBuf.st_atime; buf->st_blksize = cellBuf.st_blksize; buf->st_ctime = cellBuf.st_ctime; buf->st_gid = cellBuf.st_gid; buf->st_mode = cellBuf.st_mode; buf->st_mtime = cellBuf.st_mtime; buf->st_size = cellBuf.st_size; buf->st_uid = cellBuf.st_uid; buf->st_dev = 0; buf->st_ino = 0; buf->st_nlink = 0; buf->st_rdev = 0; buf->st_blocks = 0; rt = 0; } else { rt = -1; //TBD: SET ERRNO } #else rt = _stat( path, buf ); #endif #if defined(LINUX) if ( rt == -1 ) { char file[MAX_PATH]; if ( findFileInDirCaseInsensitive( path, file ) ) { rt = _stat( file, buf ); } } #endif return rt; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- HANDLE CFileSystem_Stdio::FS_FindFirstFile(const char *findname, WIN32_FIND_DATA *dat) { return ::FindFirstFile(const_cast(findname), dat); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- bool CFileSystem_Stdio::FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat) { 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 */ ) { VPROF_BUDGET( "GetWaitForResourcesProgress (stdio)", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); #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 *filename, const char *options, int64 *size ) { MEM_ALLOC_CREDIT(); FILE *pFile = NULL; // stop newline characters at end of filename Assert(!strchr(filename, '\n') && !strchr(filename, '\r')); pFile = fopen(filename, options); if (pFile && size) { // todo: replace with filelength()? struct _stat buf; 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 file[MAX_PATH]; if ( findFileInDirCaseInsensitive(filename, file ) ) { pFile = fopen( file, options ); if (pFile && size) { // todo: replace with filelength()? struct _stat buf; int rt = _stat( file, &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 ) && !defined( _PS3 ) if ( bWriteable ) { // 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 use flock here to mimic that behavior ThreadId_t curThread = ThreadGetCurrentId(); int fd = fileno_unlocked( pFile ); int iLockID = m_LockedFDMap.Find( fd ); int ret = flock( fd, LOCK_EX | LOCK_NB ); if ( ret < 0 ) { if ( errno == EWOULDBLOCK ) { if ( iLockID != m_LockedFDMap.InvalidIndex() && m_LockedFDMap[iLockID] != -1 && curThread != m_LockedFDMap[iLockID] ) { ret = flock( fd, LOCK_EX ); if ( ret < 0 ) { fclose( pFile ); return NULL; } } } else { fclose( pFile ); return NULL; } } if ( iLockID != m_LockedFDMap.InvalidIndex() ) m_LockedFDMap[iLockID] = curThread; else m_LockedFDMap.Insert( fd, curThread ); rewind( pFile ); } #endif return new CStdioFile( pFile, bWriteable ); } return NULL; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CStdioFile::FS_setbufsize( unsigned nBytes ) { #ifdef _PS3 if ( nBytes ) { setvbuf( m_pFile, NULL, _IOFBF, nBytes ); } #elif defined _WIN32 if ( nBytes ) { setvbuf( m_pFile, NULL, _IOFBF, 32768 ); } else { setvbuf( m_pFile, NULL, _IONBF, 0 ); // hack to make microsoft stdio not always read one stray byte on odd sized files // hopefully this isn't needed on vs2015?? #if (defined(_MSC_VER) && (_MSC_VER < 1900)) m_pFile->_bufsiz = 1; #endif } #endif } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CStdioFile::FS_fclose() { #if defined( POSIX ) && !defined( _PS3 ) if ( m_bWriteable ) { fflush( m_pFile ); int fd = fileno_unlocked( m_pFile ); flock( fd, LOCK_UN ); int iLockID = m_LockedFDMap.Find( fd ); if ( iLockID != m_LockedFDMap.InvalidIndex() ) m_LockedFDMap[ iLockID ] = -1; } #endif fclose(m_pFile); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CStdioFile::FS_fseek( int64 pos, int seekType ) { int nNewSeekType = seekType; // Handle values outside the 32-bit signed range. // Only 0 or 1 of the while loops will execute inthe same call. while ( pos > INT_MAX ) { fseek( m_pFile, INT_MAX, nNewSeekType ); pos -= INT_MAX; nNewSeekType = SEEK_CUR; // Now all seeks need to be relative } while ( pos < INT_MIN ) { fseek( m_pFile, INT_MIN, nNewSeekType ); pos -= INT_MIN; nNewSeekType = SEEK_CUR; // Now all seeks need to be relative } fseek( m_pFile, pos, nNewSeekType ); } //----------------------------------------------------------------------------- // 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 ) { // 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 ) { 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, 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 *GetEvent() { return m_Events.GetObject(); } void ReleaseEvent( CThreadEvent *pEvent ) { m_Events.PutObject( pEvent ); } private: CTSPool 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 ); 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 ) { // Read is pending, we should block until the OS is finished. Otherwise this loop is just a evil spinloop. // (Why are we even using asynchronous I/O in this loop?) if ( GetOverlappedResult( hReadFile, &overlapped, &nCurBytesRead, TRUE ) ) { bReadOk = true; } } } if ( bReadOk ) { nBytesRead += nCurBytesRead; nBytesToRead -= nCurBytesToRead; currentOffset += nCurBytesRead; } 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( 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 #if IsPlatformPS3() const char * GetSupportedPrefix() { return g_pPS3PathInfo->GameImagePath(); } int GetSupportedPrefixLength() { return strlen( GetSupportedPrefix() ); } bool CFiosReadOnlyFile::CanOpen( const char *filename, const char *options ) { if( ShouldFailIo() ) return false; extern ConVar fs_fios_enabled; if ( fs_fios_enabled.GetBool() ) { bool bSupported = ( options[0] == 'r' && options[1] == 'b' && options[2] == 0 && filesystem_native.GetBool() ); bSupported &= ( memcmp( filename, GetSupportedPrefix(), GetSupportedPrefixLength() ) == 0 ); return bSupported; } else { return false; } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static cell::fios::filehandle * OpenFiosFile( const char *filename, int64 *pFileSize ) { if( ShouldFailIo() ) return NULL; cell::fios::scheduler * pScheduler = cell::fios::scheduler::getDefaultScheduler(); cell::fios::filehandle * pFileHandle; cell::fios::err_t err; Assert( memcmp( filename, GetSupportedPrefix(), GetSupportedPrefixLength() ) == 0); filename += GetSupportedPrefixLength(); // Skip the prefix, FIOS already takes it in account err = pScheduler->getFileSizeSync( NULL, filename, pFileSize ); if ( err != cell::fios::CELL_FIOS_NOERROR ) { Warning( "[FIOS] Failed to get size of file '%s'.\n", filename ); return NULL; } err = pScheduler->openFileSync( NULL, filename, cell::fios::kO_RDONLY, &pFileHandle ); if ( err != cell::fios::CELL_FIOS_NOERROR ) { Warning( "[FIOS] Failed to open file '%s'.\n", filename ); return NULL; } return pFileHandle; } CFiosReadOnlyFile * CFiosReadOnlyFile::FS_fopen( const char *filename, const char *options, int64 *size ) { if( ShouldFailIo() ) return NULL; Assert( CanOpen( filename, options ) ); int64 nFileSize; cell::fios::filehandle * pFileHandle = OpenFiosFile( filename, &nFileSize ); if ( pFileHandle == NULL ) { return NULL; } if ( size ) { *size = nFileSize; } return new CFiosReadOnlyFile( pFileHandle, nFileSize ); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CFiosReadOnlyFile::FS_fclose() { if ( m_pHandle != NULL ) { cell::fios::err_t err = cell::fios::scheduler::getDefaultScheduler()->closeFileSync( NULL, m_pHandle ); if ( err != cell::fios::CELL_FIOS_NOERROR ) { Warning( "[FIOS] Failed to close file.\n" ); } } } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- void CFiosReadOnlyFile::FS_fseek( int64 pos, int seekType ) { switch ( seekType ) { case SEEK_SET: m_nReadPos = pos; break; case SEEK_CUR: m_nReadPos += pos; break; case SEEK_END: m_nReadPos = m_nSize - pos; break; } } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- long CFiosReadOnlyFile::FS_ftell() { return m_nReadPos; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- int CFiosReadOnlyFile::FS_feof() { return ( m_nReadPos >= m_nSize ); } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- // Set the flag to true if any of the op is processing. void IsAnyOpProcessing( void *pContext, cell::fios::op *pOp ) { bool * pBool = ( bool * )pContext; bool bFinished = pOp->isDone() || pOp->isCancelled(); *pBool |= ( bFinished == false ); // Mark the ops that are still working } size_t CFiosReadOnlyFile::FS_fread( void *dest, size_t destSize, size_t size ) { VPROF_BUDGET( "CFiosReadOnlyFile::FS_fread", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); if( ShouldFailIo() ) return 0; if ( size == 0 ) { return 0; } cell::fios::opattr_t opattr = FIOS_OPATTR_INITIALIZER; // The user can disable usage of HDD cache with the ConVar fs_fios_enable_hdd_cache. // However in some case, the game will disable its usage temporarily too (with g_bUseFiosHddCache). // This can happen if the game is saving. We want to avoid the save system and FIOS to compete for the HDD usage. // In that case, IO accesses will be done on the BluRay. It is only temporary (few seconds), and most data should be in memory anyway. // We just want to avoid cases where a single data takes several seconds to load. if ( fs_fios_enable_hdd_cache.GetBool() && ( g_bUseFiosHddCache == false ) ) { // Display a message so we can detect prolonged incorrect state. static uint32 nLastSpew = 0; const int SPEW_EVERY_N_MILLISECONDS = 5 * 1000; // Don't need to spew too much. Every 5 seconds is enough for us to detect potential issue. uint32 nCurrentTime = Plat_MSTime(); if ( nCurrentTime > nLastSpew + SPEW_EVERY_N_MILLISECONDS ) { Msg( "Fios HDD accesses disabled as a save is occurring.\n" ); nLastSpew = nCurrentTime; } } cell::fios::scheduler *pScheduler = cell::fios::scheduler::getDefaultScheduler(); uint32_t opFlags = cell::fios::kOPF_DONTFILLDISKCACHE; // By default, full cache usage // Again another FIOS function "pScheduler->isIdle()" does not work as expected. Implement another work around. bool bWorkingOps = false; pScheduler->iterateOps( &IsAnyOpProcessing, &bWorkingOps ); if ( bWorkingOps ) { // It is not idle, it is probably prefetching or doing something else. Let's reduce the HDD usage (read but don't write). // If the data is really important, it will be cached later when the scheduler is idle. opFlags = cell::fios::kOPF_DONTFILLCACHE; } opattr.opflags = ( fs_fios_enable_hdd_cache.GetBool() && g_bUseFiosHddCache ) ? opFlags : cell::fios::kOPF_NOCACHE; opattr.deadline = kDEADLINE_NOW; // Consider using kDEADLINE_ASAP // By using ASAP, the hope is that FIOS will schedule the read in the best manner to reduce seeks // NOW could help serve this read better but could reduce overall performance. // We use NOW, as the non-persistent prefetches are ASAP // And persistent prefetches are LATER (the priority doesn't really apply between prefetches otherwise). opattr.priority = cell::fios::kPRIO_DEFAULT; opattr.pCallback = 0; opattr.opflags = 0; opattr.pLicense = 0; cell::fios::err_t err = pScheduler->readFileSync( &opattr, m_pHandle, dest, size, m_nReadPos ); if ( err != cell::fios::CELL_FIOS_NOERROR ) { return 0; } m_nReadPos += size; return size; } //----------------------------------------------------------------------------- // Purpose: low-level filesystem wrapper //----------------------------------------------------------------------------- char *CFiosReadOnlyFile::FS_fgets( char *dest, int destSize ) { if( ShouldFailIo() ) return NULL; if ( FS_feof() ) { return NULL; } int nStartPos = m_nReadPos; int nBytesRead = FS_fread( dest, destSize, destSize ); if ( !nBytesRead ) { return NULL; } dest[imin( nBytesRead, destSize - 1)] = 0; char *pNewline = strchr( dest, '\n' ); if ( pNewline ) { // advance past, leave \n pNewline++; *pNewline = 0; } else { pNewline = &dest[imin( nBytesRead, destSize - 1)]; } m_nReadPos = nStartPos + ( pNewline - dest ) + 1; return dest; } #endif