You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1610 lines
46 KiB
1610 lines
46 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
#ifdef _WIN32 |
|
#include <io.h> |
|
#include <fcntl.h> |
|
#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 <fcntl.h> |
|
#ifdef LINUX |
|
#include <sys/file.h> |
|
#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) || defined(PLATFORM_BSD) |
|
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) || defined(PLATFORM_BSD) |
|
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) || defined(PLATFORM_BSD) |
|
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; |
|
#ifdef ANDROID |
|
int fd = fileno( m_pFile ); // need to test this |
|
#else |
|
int fd = fileno_unlocked( m_pFile ); |
|
#endif |
|
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<CThreadEvent *> 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
|
|
|