|
|
//========= Copyright 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Purpose: A collection of utility classes to simplify file I/O, and
// as much as possible contain portability problems. Here avoiding
// including windows.h.
//
//=============================================================================
#if defined(_WIN32)
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0502 // ReadDirectoryChangesW
#endif
#include <sys/stat.h>
#if defined(OSX)
#include <CoreServices/CoreServices.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/time.h>
#endif
#define ASYNC_FILEIO
#if defined( LINUX ) || defined ( OSX )
// Linux hasn't got a good AIO library that we have found yet, so lets punt for now
#undef ASYNC_FILEIO
#endif
#if defined(_WIN32)
//#include <direct.h>
#include <io.h>
// unset to force to use stdio implementation
#define WIN32_FILEIO
#if defined(ASYNC_FILEIO)
#if defined(_WIN32) && !defined(WIN32_FILEIO)
#error "trying to use async io without win32 filesystem API usage, that isn't doable"
#endif
#endif
#else /* not defined (_WIN32) */
#include <utime.h>
#include <dirent.h>
#include <unistd.h> // for unlink
#include <limits.h> // defines PATH_MAX
#include <alloca.h> // 'cause we like smashing the stack
#if defined( _PS3 )
#include <fcntl.h>
#else
#include <sys/fcntl.h>
#include <sys/statvfs.h>
#endif
#include <sched.h>
#define int64 int64_t
// On OSX the native API file offset is always 64-bit
// and things like stat64 are deprecated.
// PS3 doesn't have anything other than the native API.
#if defined(OSX) || defined(_PS3)
typedef off_t offBig_t; typedef struct stat statBig_t; typedef struct statvfs statvfsBig_t; typedef struct dirent direntBig_t; #define openBig open
#define lseekBig lseek
#define preadBig pread
#define pwriteBig pwrite
#define statBig stat
#define lstatBig lstat
#define readdirBig readdir
#define scandirBig scandir
#define alphasortBig alphasort
#define fopenBig fopen
#define fseekBig fseeko
#define ftellBig ftello
#define ftruncateBig ftruncate
#define fstatBig fstat
#define statvfsBig statvfs
#define mmapBig mmap
#else
// Use the 64-bit file I/O API.
typedef off64_t offBig_t; typedef struct stat64 statBig_t; typedef struct statvfs64 statvfsBig_t; typedef struct dirent64 direntBig_t; #define openBig open64
#define lseekBig lseek64
#define preadBig pread64
#define pwriteBig pwrite64
#define statBig stat64
#define lstatBig lstat64
#define readdirBig readdir64
#define scandirBig scandir64
#define alphasortBig alphasort64
#define fopenBig fopen64
#define fseekBig fseeko64
#define ftellBig ftello64
#define ftruncateBig ftruncate64
#define fstatBig fstat64
#define statvfsBig statvfs64
#define mmapBig mmap64
#endif
struct _finddata_t { _finddata_t() { name[0] = '\0'; dirBase[0] = '\0'; curName = 0; numNames = 0; namelist = NULL; } // public data
char name[PATH_MAX]; // the file name returned from the call
char dirBase[PATH_MAX]; offBig_t size; mode_t attrib; time_t time_write; time_t time_create; int curName; int numNames; direntBig_t **namelist; };
#define _A_SUBDIR S_IFDIR
// FUTURE map _A_HIDDEN via checking filename against .*
#define _A_HIDDEN 0
// FUTURE check 'read only' by checking mode against S_IRUSR
#define _A_RDONLY 0
// no files under posix are 'system' or 'archive'
#define _A_SYSTEM 0
#define _A_ARCH 0
int _findfirst( const char *pchBasePath, struct _finddata_t *pFindData ); int _findnext( const int64 hFind, struct _finddata_t *pFindData ); bool _findclose( int64 hFind ); static int FileSelect( const char *name, const char *mask );
#endif
#include "tier1/fileio.h"
#include "tier1/utlbuffer.h"
#include "tier1/strtools.h"
#include <errno.h>
#include "vstdlib/vstrtools.h"
#if defined( WIN32_FILEIO )
#include "winlite.h"
#endif
#if defined( ASYNC_FILEIO )
#ifdef _WIN32
#include "winlite.h"
#elif defined(_PS3)
// bugbug ps3 - see some aio files under libfs.. skipping for the moment
#elif defined(POSIX)
#include <aio.h>
#else
#error "aio please"
#endif
#endif
#if defined ( POSIX )
#define INVALID_HANDLE_VALUE NULL
#define _rmdir rmdir
#if !defined( _PS3 )
#define _S_IREAD S_IREAD
#define _S_IWRITE S_IWRITE
#else
#define _S_IREAD S_IRUSR
#define _S_IWRITE S_IWUSR
#endif
#endif
#define PvAlloc( cub ) malloc( cub )
#define PvRealloc( pv, cub ) realloc( pv, cub )
#define FreePv( pv ) free( pv )
//-----------------------------------------------------------------------------
// Purpose: Constructor from UTF8
//-----------------------------------------------------------------------------
CPathString::CPathString( const char *pchUTF8Path ) { // Need to first turn into an absolute path, so \\?\ pre-pended paths will be ok
m_pchUTF8Path = new char[ MAX_UNICODE_PATH_IN_UTF8 ]; m_pwchWideCharPathPrepended = NULL;
// First, convert to absolute path, which also does Q_FixSlashes for us.
Q_MakeAbsolutePath( m_pchUTF8Path, MAX_UNICODE_PATH * 4, pchUTF8Path );
// Second, fix any double slashes
V_FixDoubleSlashes( m_pchUTF8Path ); }
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CPathString::~CPathString() { if ( m_pwchWideCharPathPrepended ) { delete[] m_pwchWideCharPathPrepended; m_pwchWideCharPathPrepended = NULL; }
if ( m_pchUTF8Path ) { delete[] m_pchUTF8Path; m_pchUTF8Path = NULL; } }
//-----------------------------------------------------------------------------
// Purpose: Access UTF8 path
//-----------------------------------------------------------------------------
const char * CPathString::GetUTF8Path() { return m_pchUTF8Path; }
//-----------------------------------------------------------------------------
// Purpose: Gets wchar_t based path, with \\?\ pre-pended (allowing long paths
// on Win32, should only be used with unicode extended path aware filesystem calls)
//-----------------------------------------------------------------------------
const wchar_t *CPathString::GetWCharPathPrePended() { PopulateWCharPath(); return m_pwchWideCharPathPrepended; }
//-----------------------------------------------------------------------------
// Purpose: Builds wchar path string
//-----------------------------------------------------------------------------
void CPathString::PopulateWCharPath() { if ( m_pwchWideCharPathPrepended ) return;
// Check if the UTF8 path starts with \\, which on Win32 means it's a UNC path, and then needs a different prefix
if ( m_pchUTF8Path[0] == '\\' && m_pchUTF8Path[1] == '\\' ) { m_pwchWideCharPathPrepended = new wchar_t[MAX_UNICODE_PATH+8]; Q_memcpy( m_pwchWideCharPathPrepended, L"\\\\?\\UNC\\", 8*sizeof(wchar_t) ); #ifdef DBGFLAG_ASSERT
int cchResult = #endif
Q_UTF8ToUnicode( m_pchUTF8Path+2, m_pwchWideCharPathPrepended+8, MAX_UNICODE_PATH*sizeof(wchar_t) ); Assert( cchResult );
// Be sure we NULL terminate within our allocated region incase Q_UTF8ToUnicode failed, though we're already in bad shape then.
m_pwchWideCharPathPrepended[MAX_UNICODE_PATH+7] = 0; } else { m_pwchWideCharPathPrepended = new wchar_t[MAX_UNICODE_PATH+4]; Q_memcpy( m_pwchWideCharPathPrepended, L"\\\\?\\", 4*sizeof(wchar_t) ); #ifdef DBGFLAG_ASSERT
int cchResult = #endif
Q_UTF8ToUnicode( m_pchUTF8Path, m_pwchWideCharPathPrepended+4, MAX_UNICODE_PATH*sizeof(wchar_t) ); Assert( cchResult );
// Be sure we NULL terminate within our allocated region incase Q_UTF8ToUnicode failed, though we're already in bad shape then.
m_pwchWideCharPathPrepended[MAX_UNICODE_PATH+3] = 0; } }
//-----------------------------------------------------------------------------
// Purpose: Helper on PS3 to find next entry that matches the provided pattern
//-----------------------------------------------------------------------------
#if defined( _PS3 )
bool CDirIterator::BFindNextPS3() { while (true) { uint32 unDataCount = 0; if (cellFsGetDirectoryEntries( m_hFind, m_pDirEntry, sizeof(CellFsDirectoryEntry), &unDataCount ) != CELL_FS_SUCCEEDED || unDataCount == 0) return false;
// if we found a new file/directory, need to make sure it matches our desired pattern
if (FileSelect( m_pDirEntry->entry_name.d_name, m_strPattern.String() ) != 0) return true; } } #endif
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
#if defined( _PS3 )
CDirIterator::CDirIterator( const char *pchPath, const char *pchPattern ) { // init for failure
m_bOpenHandle = false; m_bNoFiles = true; m_bUsedFirstFile = true;
// always create a new entry.. matches win32/posix (guessing so BCurrent functions won't crash?)
m_pDirEntry = new CellFsDirectoryEntry; memset( m_pDirEntry, 0, sizeof(CellFsDirectoryEntry) );
if (!pchPath || !pchPattern) return;
// fix up path
CPathString strPath( pchPath );
// save pattern
m_strPattern = pchPattern;
// we have a path.. init
CellFsErrno e = cellFsOpendir( strPath.GetUTF8Path(), &m_hFind ); if (e != CELL_FS_SUCCEEDED) return;
m_bOpenHandle = true;
// find first entry
if (!BFindNextPS3()) return;
// found at least 1 file
m_bNoFiles = false;
// if we're pointing at . or .., set it as used
// so we'll look for the next item when BNextFile() is called
m_bUsedFirstFile = !BValidFilename(); }
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CDirIterator::~CDirIterator() { if (m_bOpenHandle) cellFsClosedir( m_hFind );
if (m_pDirEntry) delete m_pDirEntry; }
#else
CDirIterator::CDirIterator( const char *pchPath, const char *pchPattern ) { CPathString strPath( pchPath ); m_pFindData = NULL;
// +2 so we can potentially add path separator as well as null termination
char *pchPathAndPattern = new char[Q_strlen( strPath.GetUTF8Path() ) + Q_strlen( pchPattern ) + 2];
// be resilient about whether the caller passes us a path with a terminal path separator or not.
// put in the path
if (pchPath) { Q_strncpy( pchPathAndPattern, strPath.GetUTF8Path(), Q_strlen( strPath.GetUTF8Path() ) + 1 );
// identify whether we've got a terminal separator. add one if not.
char *pchRest = pchPathAndPattern + Q_strlen( pchPathAndPattern ) - 1; if (*pchRest != CORRECT_PATH_SEPARATOR) { *++pchRest = CORRECT_PATH_SEPARATOR; } pchRest++;
// now put in the search pattern.
Q_strncpy( pchRest, pchPattern, Q_strlen( pchPattern ) + 1 );
Init( pchPathAndPattern ); } else { pchPathAndPattern[0] = 0; m_bNoFiles = true; m_bUsedFirstFile = true;
#if defined(_WIN32)
m_hFind = INVALID_HANDLE_VALUE; m_pFindData = new WIN32_FIND_DATAW; m_rgchFileName[0] = 0; #else
m_hFind = -1; m_pFindData = new _finddata_t; #endif
memset( m_pFindData, 0, sizeof(*m_pFindData) );
} delete[] pchPathAndPattern; }
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CDirIterator::CDirIterator( const char *pchSearchPath ) { Init( pchSearchPath ); }
//-----------------------------------------------------------------------------
// Purpose: Initialize iteration structure
//-----------------------------------------------------------------------------
void CDirIterator::Init( const char *pchSearchPath ) { CPathString strBasePath( pchSearchPath );
#if defined(_WIN32)
m_pFindData = new WIN32_FIND_DATAW; memset( m_pFindData, 0, sizeof(*m_pFindData) );
m_rgchFileName[0] = 0; m_hFind = FindFirstFileW( strBasePath.GetWCharPathPrePended(), m_pFindData ); bool bSuccess = (m_hFind != INVALID_HANDLE_VALUE); // Conversion should never fail with valid filenames...
if (bSuccess && !Q_UnicodeToUTF8( m_pFindData->cFileName, m_rgchFileName, sizeof(m_rgchFileName) )) { AssertMsg( false, "Q_UnicodeToUTF8 failed on m_pFindData->cFileName in CDirIterator" ); bSuccess = false; } #else
m_pFindData = new _finddata_t; memset( m_pFindData, 0, sizeof(*m_pFindData) );
m_hFind = _findfirst( strBasePath.GetUTF8Path(), m_pFindData ); bool bSuccess = (m_hFind != -1); #endif
if (!bSuccess) { m_bNoFiles = true; m_bUsedFirstFile = true; } else { m_bNoFiles = false; // if we're pointing at . or .., set it as used
// so we'll look for the next item when BNextFile() is called
m_bUsedFirstFile = !BValidFilename(); } }
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CDirIterator::~CDirIterator() { #if defined(_WIN32)
if (m_hFind != INVALID_HANDLE_VALUE) { FindClose( m_hFind ); } delete m_pFindData; #else
if (m_hFind != -1) { _findclose( m_hFind ); } if (m_pFindData) { for (int i = 0; i < m_pFindData->numNames; i++) { // scandir allocates with malloc, so free with free
free( m_pFindData->namelist[i] ); } free( m_pFindData->namelist ); delete m_pFindData; } #endif
}
#endif // _PS3
//-----------------------------------------------------------------------------
// Purpose: Check for successful construction
//-----------------------------------------------------------------------------
bool CDirIterator::IsValid() const { #if defined(_WIN32)
return m_hFind != INVALID_HANDLE_VALUE; #elif defined(_PS3)
return m_bOpenHandle; #else
return m_hFind != -1; #endif
}
//-----------------------------------------------------------------------------
// Purpose: Filter out . and ..
//-----------------------------------------------------------------------------
bool CDirIterator::BValidFilename() { #if defined( _WIN32 )
const char *pch = m_rgchFileName; #elif defined( _PS3 )
const char *pch = m_pDirEntry->entry_name.d_name; #else
const char *pch = m_pFindData->name; #endif
if ((pch[0] == '.' && pch[1] == 0) || (pch[0] == '.' && pch[1] == '.' && pch[2] == 0)) return false;
return true; }
//-----------------------------------------------------------------------------
// Purpose: returns true if there is a file to read
//-----------------------------------------------------------------------------
bool CDirIterator::BNextFile() { if (m_bNoFiles) return false;
// use the first result
if (!m_bUsedFirstFile) { m_bUsedFirstFile = true; return true; }
// find the next item
for (;;) { #if defined( _WIN32 )
bool bFound = (FindNextFileW( m_hFind, m_pFindData ) != FALSE); // Conversion should never fail with valid filenames...
if (bFound && !Q_UnicodeToUTF8( m_pFindData->cFileName, m_rgchFileName, sizeof(m_rgchFileName) )) { AssertMsg( false, "Q_UnicodeToUTF8 failed on m_pFindData->cFileName in CDirIterator" ); bFound = false; } #elif defined( _PS3 )
bool bFound = BFindNextPS3(); #else
bool bFound = (_findnext( m_hFind, m_pFindData ) == 0); #endif
if (!bFound) { // done
m_bNoFiles = true; return false; }
// skip over the '.' and '..' paths
if (!BValidFilename()) continue;
break; }
// have one more file
return true; }
//-----------------------------------------------------------------------------
// Purpose: returns name (filename portion only) of the current file.
// Name is emitted in UTF-8 encoding.
// NOTE: This method returns a pointer into a static buffer, either a member
// or the buffer inside the _finddata_t.
//-----------------------------------------------------------------------------
const char *CDirIterator::CurrentFileName() { #if defined( _WIN32 )
return m_rgchFileName; #elif defined( _PS3 )
return m_pDirEntry->entry_name.d_name; #else
return m_pFindData->name; #endif
}
//-----------------------------------------------------------------------------
// Purpose: returns size of the file
//-----------------------------------------------------------------------------
int64 CDirIterator::CurrentFileLength() const { #if defined( _WIN32 )
LARGE_INTEGER li = { { m_pFindData->nFileSizeLow, m_pFindData->nFileSizeHigh } }; return li.QuadPart; #elif defined( _PS3 )
return m_pDirEntry->attribute.st_size; #else
return (int64)m_pFindData->size; #endif
}
#if defined( _WIN32 )
//-----------------------------------------------------------------------------
// Purpose: utility for converting a system filetime to a regular time
//-----------------------------------------------------------------------------
time64_t FileTimeToUnixTime( FILETIME filetime ) { long long int t = filetime.dwHighDateTime; t <<= 32; t += (unsigned long)filetime.dwLowDateTime; t -= 116444736000000000LL; return t / 10000000; } #endif
//-----------------------------------------------------------------------------
// Purpose: returns last write time of the file
//-----------------------------------------------------------------------------
time64_t CDirIterator::CurrentFileWriteTime() const { #if defined( _WIN32 )
return FileTimeToUnixTime( m_pFindData->ftLastWriteTime ); #elif defined( _PS3 )
return m_pDirEntry->attribute.st_mtime; #else
return m_pFindData->time_write; #endif
}
//-----------------------------------------------------------------------------
// Purpose: returns the creation time of the file
//-----------------------------------------------------------------------------
time64_t CDirIterator::CurrentFileCreateTime() const { #if defined( _WIN32 )
return FileTimeToUnixTime( m_pFindData->ftCreationTime ); #elif defined( _PS3 )
return m_pDirEntry->attribute.st_ctime; #else
return m_pFindData->time_create; #endif
}
//-----------------------------------------------------------------------------
// Purpose: returns whether current item under examination is a directory
//-----------------------------------------------------------------------------
bool CDirIterator::BCurrentIsDir() const { #if defined( _WIN32 )
return (m_pFindData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; #elif defined( _PS3 )
return (m_pDirEntry->attribute.st_mode & CELL_FS_S_IFDIR ? true : false); #else
return (m_pFindData->attrib & _A_SUBDIR ? true : false); #endif
}
//-----------------------------------------------------------------------------
// Purpose: returns whether current item under examination is a hidden file
//-----------------------------------------------------------------------------
bool CDirIterator::BCurrentIsHidden() const { #if defined( _WIN32 )
return (m_pFindData->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0; #elif defined( _PS3 )
return false; #else
return (m_pFindData->attrib & _A_HIDDEN ? true : false); #endif
}
//-----------------------------------------------------------------------------
// Purpose: returns whether current item under examination is read-only
//-----------------------------------------------------------------------------
bool CDirIterator::BCurrentIsReadOnly() const { #if defined( _WIN32 )
return (m_pFindData->dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0; #elif defined( _PS3 )
// assume this is windows version of read only.. can execute. Is it writable?
return (m_pDirEntry->attribute.st_mode & CELL_FS_S_IWUSR == 0); #else
return (m_pFindData->attrib & _A_RDONLY ? true : false); #endif
}
//-----------------------------------------------------------------------------
// Purpose: returns whether current item under examination is marked as a system file
//-----------------------------------------------------------------------------
bool CDirIterator::BCurrentIsSystem() const { #if defined( _WIN32 )
return (m_pFindData->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0; #elif defined( _PS3 )
return false; #else
return (m_pFindData->attrib & _A_SYSTEM ? true : false); #endif
}
//-----------------------------------------------------------------------------
// Purpose: returns whether current item under examination is marked for archiving
//-----------------------------------------------------------------------------
bool CDirIterator::BCurrentIsMarkedForArchive() const { #if defined( _WIN32 )
return (m_pFindData->dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) != 0; #elif defined( _PS3 )
return false; #else
return (m_pFindData->attrib & _A_ARCH ? true : false); #endif
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CFileWriter::CFileWriter( bool bAsync ) { #ifdef ASYNC_FILEIO
m_bDefaultAsync = bAsync; #else
m_bDefaultAsync = false; #endif
m_hFileDest = INVALID_HANDLE_VALUE; m_bAsync = m_bDefaultAsync; m_cPendingCallbacksFromOtherThreads = 0; m_cubOutstanding = 0; m_cubWritten = 0; m_unThreadID = 0; }
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CFileWriter::~CFileWriter() { Close(); }
#ifdef ASYNC_FILEIO
#ifdef _WIN32
// our own version of overlapped structure passed through async writes
struct FileWriterOverlapped_t : public OVERLAPPED { CFileWriter *m_pFileWriter; void *m_pvData; size_t m_cubData; }; #elif defined(_PS3)
// bugbug ps3 - impement?
#elif defined(POSIX)
// our own version of overlapped structure passed through async writes
struct FileWriterOverlapped_t : public aiocb { CFileWriter *m_pFileWriter; void *m_pvData; size_t m_cubData; }; #else
#error "struct me"
#endif
#endif
//-----------------------------------------------------------------------------
// Purpose: sets which file to write to
//-----------------------------------------------------------------------------
bool CFileWriter::BSetFile( const char *pchFile, bool bAllowOpenExisting ) { CPathString strPath( pchFile );
// make sure the full path to file exists
CUtlString strCopyUTF8 = strPath.GetUTF8Path(); Q_StripFilename( const_cast<char*>(strCopyUTF8.Access()) ); CreateDirRecursive( strCopyUTF8.Access() ); Close(); m_bAsync = m_bDefaultAsync; m_cubWritten = 0; m_cubOutstanding = 0; m_unThreadID = 0; m_cPendingCallbacksFromOtherThreads = 0;
#ifdef _WIN32
DWORD dwFlags = FILE_ATTRIBUTE_NORMAL; if ( m_bAsync ) dwFlags |= FILE_FLAG_OVERLAPPED;
// First try to open existing file, if specified that we should allow that
if ( bAllowOpenExisting ) { m_hFileDest = ::CreateFileW( strPath.GetWCharPathPrePended(), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, dwFlags, NULL ); if ( m_hFileDest == INVALID_HANDLE_VALUE ) { // clear overlapped and try again
dwFlags &= ~FILE_FLAG_OVERLAPPED; m_hFileDest = ::CreateFileW( strPath.GetWCharPathPrePended(), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, dwFlags, NULL ); if ( m_hFileDest != INVALID_HANDLE_VALUE ) { m_bAsync = false; } }
if ( m_hFileDest != INVALID_HANDLE_VALUE ) { LARGE_INTEGER liOffset; liOffset.QuadPart = 0; LARGE_INTEGER liFilePtr;
::SetFilePointerEx( m_hFileDest, liOffset, &liFilePtr, FILE_END ); m_cubWritten = liFilePtr.QuadPart; } }
// If we didn't already open existing, then move on to creation
if ( m_hFileDest == INVALID_HANDLE_VALUE ) { // make sure the full path to file exists
CUtlString strPathCopyUTF8 = strPath.GetUTF8Path(); Q_StripFilename( const_cast<char*>(strPathCopyUTF8.Access()) ); CreateDirRecursive( strPathCopyUTF8.Access() );
// Reset back to try overlapped below incase we tried without above
if ( m_bAsync ) dwFlags |= FILE_FLAG_OVERLAPPED;
m_hFileDest = ::CreateFileW( strPath.GetWCharPathPrePended(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, dwFlags, NULL );
if ( m_hFileDest == INVALID_HANDLE_VALUE ) { // clear overlapped and try again
m_bAsync = false; dwFlags &= ~FILE_FLAG_OVERLAPPED; m_hFileDest = ::CreateFileW( strPath.GetWCharPathPrePended(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, dwFlags, NULL ); if ( m_hFileDest == INVALID_HANDLE_VALUE ) return false; } }
#elif defined(POSIX)
int flags = O_WRONLY; if ( bAllowOpenExisting ) flags |= O_CREAT; else flags |= O_CREAT | O_TRUNC;
m_hFileDest = (HANDLE)open( strPath.GetUTF8Path(), flags, S_IRWXU ); if ( bAllowOpenExisting ) { off_t offset = lseek( (intptr_t)m_hFileDest, 0, SEEK_END ); m_cubWritten = offset; } #else
#error
#endif
m_unThreadID = ThreadGetCurrentId(); return ( m_hFileDest != INVALID_HANDLE_VALUE ); }
void CFileWriter::Sleep( uint nMSec ) { #ifdef _WIN32
::SleepEx( nMSec, TRUE ); #elif PLATFORM_PS3
sys_timer_usleep( nMSec * 1000 ); #elif defined(POSIX)
if ( nMSec == 0 ) sched_yield(); else usleep( nMSec * 1000 ); #else
#error
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Seeks to a specific location in the file
//-----------------------------------------------------------------------------
bool CFileWriter::Seek( uint64 offset, ESeekOrigin eOrigin ) { if ( m_bAsync ) { AssertMsg( false, "Seeking to a position not supported with async io" ); return false; }
bool bSuccess = false;
#ifdef _WIN32
DWORD dwMoveMethod = FILE_BEGIN; switch( eOrigin ) { case k_ESeekCur: dwMoveMethod = FILE_CURRENT; break; case k_ESeekEnd: dwMoveMethod = FILE_END; break; default: dwMoveMethod = FILE_BEGIN; }
LARGE_INTEGER largeIntOffset; largeIntOffset.QuadPart = offset;
if ( ::SetFilePointerEx( m_hFileDest, largeIntOffset, NULL, dwMoveMethod ) ) bSuccess = true;
#elif defined(POSIX)
int orgin = SEEK_SET; switch( eOrigin ) { case k_ESeekCur: orgin = SEEK_CUR; break; case k_ESeekEnd: orgin = SEEK_END; break; default: orgin = SEEK_SET; }
// fseeko will work on 64 bit file offsets if _FILE_OFFSET_BITS 64 is defined, is this the best way
// to do this on posix builds?
bSuccess = lseek( (intptr_t)m_hFileDest, (off_t)offset, orgin ) != -1; #else
#error
#endif
return bSuccess; }
//-----------------------------------------------------------------------------
// Purpose: posts a buffer to be written to the file
//-----------------------------------------------------------------------------
bool CFileWriter::Write( const void *pvData, uint32 cubData ) { if ( cubData == 0 ) return true;
#if defined( _PS3 )
if ( write( (int)m_hFileDest, pvData, cubData ) == cubData ) return true;
return false; #else
BOOL bRet = 0; #ifdef ASYNC_FILEIO
if ( m_bAsync ) { // get any outstanding write callbacks
if ( m_cubOutstanding > 0 ) { ::SleepEx( 0, TRUE ); }
// make sure we don't have too much data outstanding
while ( m_cubOutstanding > (10*k_nMegabyte) ) { ::SleepEx( 10, TRUE ); } // build the overlapped info that will get passed through the write
FileWriterOverlapped_t *pFileWriterOverlapped = new FileWriterOverlapped_t; memset( pFileWriterOverlapped, 0x0, sizeof(FileWriterOverlapped_t) ); pFileWriterOverlapped->m_pFileWriter = this; pFileWriterOverlapped->m_pvData = PvAlloc( cubData ); pFileWriterOverlapped->m_cubData = cubData; memcpy( pFileWriterOverlapped->m_pvData, pvData, cubData );
// work out where to write to
#ifdef _WIN32
pFileWriterOverlapped->Offset = ( uint32 ) ( m_cubWritten & 0xffffffff ); pFileWriterOverlapped->OffsetHigh = ( uint32 ) ( m_cubWritten >> 32 ); #elif defined(POSIX)
pFileWriterOverlapped->aio_offset = m_cubWritten; pFileWriterOverlapped->aio_buf = pFileWriterOverlapped->m_pvData; pFileWriterOverlapped->aio_nbytes = pFileWriterOverlapped->m_cubData; pFileWriterOverlapped->aio_fildes = (int)m_hFileDest; /* Link the AIO request with a thread callback */ pFileWriterOverlapped->aio_sigevent.sigev_notify = SIGEV_THREAD; pFileWriterOverlapped->aio_sigevent.sigev_notify_function = &CFileWriter::ThreadedWriteFileCompletionFunc; pFileWriterOverlapped->aio_sigevent.sigev_notify_attributes = NULL; pFileWriterOverlapped->aio_sigevent.sigev_value.sival_ptr = pFileWriterOverlapped; #else
#error
#endif
#ifdef _WIN32
// post write
bRet = ::WriteFileEx( m_hFileDest, pFileWriterOverlapped->m_pvData, cubData, pFileWriterOverlapped, &CFileWriter::ThreadedWriteFileCompletionFunc ); #elif defined(POSIX)
bRet = aio_write( pFileWriterOverlapped ); bRet = !bRet; // aio_read returns 0 on success, this func returns success if bRet != 0
#else
#error
#endif
if ( bRet ) { ThreadInterlockedExchangeAdd( &m_cubOutstanding, cubData ); if ( ThreadGetCurrentId() != m_unThreadID ) { // this is not the main thread so we have to wait here
ThreadInterlockedIncrement( &m_cPendingCallbacksFromOtherThreads );
while ( m_cPendingCallbacksFromOtherThreads ) { // we have to wait here since the OS can signal us only
// on this current thread
::SleepEx( 10, TRUE ); } } } } else #endif // ASYNC_FILEIO
{ #ifdef _WIN32
// normal write
DWORD dwBytesWritten = 0; ::WriteFile( m_hFileDest, pvData, cubData, &dwBytesWritten, NULL ); bRet = ( dwBytesWritten == cubData ); #elif defined(POSIX)
bRet = write( (intptr_t)m_hFileDest, pvData, cubData ); #else
#error
#endif
}
// increment
m_cubWritten += cubData;
return ( bRet != 0 ); #endif // _PS3
}
//-----------------------------------------------------------------------------
// Purpose: Convenient printf with no dynamic memory allocation
//-----------------------------------------------------------------------------
int CFileWriter::Printf( char *pDest, int bufferLen, char const *pFormat, ... ) { va_list marker;
va_start( marker, pFormat ); // _vsnprintf will not write a terminator if the output string uses the entire buffer you provide
int len = _vsnprintf( pDest, bufferLen-1, pFormat, marker ); va_end( marker );
// Len < 0 represents an overflow on windows; len > buffer length on posix
if (( len < 0 ) || (len >= bufferLen ) ) { len = bufferLen-1; } pDest[len] = 0;
if ( !Write( pDest, len ) ) return 0;
return len; }
//-----------------------------------------------------------------------------
// Purpose: ensures any writes have been completed
//-----------------------------------------------------------------------------
void CFileWriter::Flush() { #ifdef WIN32
FlushFileBuffers( m_hFileDest ); #endif
if ( m_unThreadID == ThreadGetCurrentId() ) { // wait for all writes to be complete
int cWaits = 0; const int k_nMaxWaits = 60000; /* roughly one minute */
while ( m_cubOutstanding && cWaits < k_nMaxWaits ) { Sleep( 10 ); cWaits++; } AssertMsg1( cWaits < k_nMaxWaits, "Waited 60k iterations in CFileWriter::Flush - m_cubOutstanding = %u", m_cubOutstanding ); } }
//-----------------------------------------------------------------------------
// Purpose: check if file is open
//-----------------------------------------------------------------------------
bool CFileWriter::BFileOpen() { if ( m_hFileDest != INVALID_HANDLE_VALUE ) return true;
return false; }
//-----------------------------------------------------------------------------
// Purpose: closes the file
//-----------------------------------------------------------------------------
void CFileWriter::Close() { if ( m_hFileDest != INVALID_HANDLE_VALUE ) { Flush();
// temp handle to avoid double close in threaded environment
HANDLE hFileDest = m_hFileDest; m_hFileDest = INVALID_HANDLE_VALUE; #ifdef _WIN32
::CloseHandle( hFileDest ); #elif defined(POSIX)
close( (intptr_t)hFileDest ); #else
#error
#endif
}
// Close has to be called from thread that called BSetFile
Assert( m_cPendingCallbacksFromOtherThreads == 0 ); }
//-----------------------------------------------------------------------------
// Purpose: async callback for when a file write has completed
//-----------------------------------------------------------------------------
#ifdef ASYNC_FILEIO
#ifdef _WIN32
void CFileWriter::ThreadedWriteFileCompletionFunc( unsigned long dwErrorCode, unsigned long dwBytesTransfered, struct _OVERLAPPED *pOverlapped ) { FileWriterOverlapped_t *pFileWriterOverlapped = (FileWriterOverlapped_t *)pOverlapped; ThreadInterlockedExchangeAdd( &pFileWriterOverlapped->m_pFileWriter->m_cubOutstanding, (int)(0-pFileWriterOverlapped->m_cubData) ); if ( pFileWriterOverlapped->m_pFileWriter->m_unThreadID != ThreadGetCurrentId() ) { // this was not the main thread, reduce counter
ThreadInterlockedDecrement( &pFileWriterOverlapped->m_pFileWriter->m_cPendingCallbacksFromOtherThreads ); } FreePv( pFileWriterOverlapped->m_pvData ); delete pFileWriterOverlapped;
} #elif defined( _PS3 )
// bugbug PS3
#elif defined(POSIX)
void CFileWriter::ThreadedWriteFileCompletionFunc( sigval sigval ) { FileWriterOverlapped_t *pFileWriterOverlapped = (FileWriterOverlapped_t *)sigval.sival_ptr; if ( aio_error( pFileWriterOverlapped ) == 0 ) { uint nBytesWrite = aio_return( pFileWriterOverlapped ); Assert( nBytesWrite == pFileWriterOverlapped->m_cubData );
pFileWriterOverlapped->m_pFileWriter->m_cOutstandingWrites--; pFileWriterOverlapped->m_pFileWriter->m_cubOutstanding += ( 0 - pFileWriterOverlapped->m_cubData ); FreePv( pFileWriterOverlapped->m_pvData ); delete pFileWriterOverlapped; } } #else
#error
#endif
#endif // ASYNC_FILEIO
#ifdef WIN32
struct DirWatcherOverlapped : public OVERLAPPED { CDirWatcher *m_pDirWatcher; }; #endif
#if !defined(_PS3) && !defined(_X360)
// a buffer full of file names
static const int k_cubDirWatchBufferSize = 8 * 1024;
//-----------------------------------------------------------------------------
// Purpose: directory watching
//-----------------------------------------------------------------------------
CDirWatcher::CDirWatcher() { m_hFile = NULL; m_pOverlapped = NULL; m_pFileInfo = NULL; #ifdef OSX
m_WatcherStream = 0; #endif
}
//-----------------------------------------------------------------------------
// Purpose: directory watching
//-----------------------------------------------------------------------------
CDirWatcher::~CDirWatcher() { #ifdef WIN32
if ( m_pOverlapped ) { // mark the overlapped structure as gone
DirWatcherOverlapped *pDirWatcherOverlapped = (DirWatcherOverlapped *)m_pOverlapped; pDirWatcherOverlapped->m_pDirWatcher = NULL; }
if ( m_hFile ) { // make sure we flush any pending I/O's on the handle
::CancelIo( m_hFile ); ::SleepEx( 0, TRUE ); // close the handle
::CloseHandle( m_hFile ); } #elif defined(OSX)
if ( m_WatcherStream ) { FSEventStreamStop( (FSEventStreamRef)m_WatcherStream ); FSEventStreamInvalidate( (FSEventStreamRef)m_WatcherStream ); FSEventStreamRelease( (FSEventStreamRef)m_WatcherStream ); m_WatcherStream = 0; } #endif
if ( m_pFileInfo ) { free( m_pFileInfo ); } if ( m_pOverlapped ) { free( m_pOverlapped ); } }
#ifdef WIN32
//-----------------------------------------------------------------------------
// Purpose: callback watch
// gets called on the same thread whenever a SleepEx() occurs
//-----------------------------------------------------------------------------
class CDirWatcherFriend { public: static void WINAPI DirWatchCallback( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, OVERLAPPED *pOverlapped ) { DirWatcherOverlapped *pDirWatcherOverlapped = (DirWatcherOverlapped *)pOverlapped;
// see if we've been cancelled
if ( !pDirWatcherOverlapped->m_pDirWatcher ) return;
// parse and pass back
if ( dwNumberOfBytesTransfered > sizeof(FILE_NOTIFY_INFORMATION) ) { FILE_NOTIFY_INFORMATION *pFileNotifyInformation = (FILE_NOTIFY_INFORMATION *)pDirWatcherOverlapped->m_pDirWatcher->m_pFileInfo; do { // null terminate the string and turn it to UTF-8
int cNumWChars = pFileNotifyInformation->FileNameLength / sizeof(wchar_t); wchar_t *pwchT = new wchar_t[cNumWChars + 1]; memcpy( pwchT, pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength ); pwchT[cNumWChars] = 0; CStrAutoEncode strAutoEncode( pwchT );
// add it to our list
pDirWatcherOverlapped->m_pDirWatcher->AddFileToChangeList( strAutoEncode.ToString() ); delete[] pwchT; if ( pFileNotifyInformation->NextEntryOffset == 0 ) break;
// move to the next file
pFileNotifyInformation = (FILE_NOTIFY_INFORMATION *)(((byte*)pFileNotifyInformation) + pFileNotifyInformation->NextEntryOffset); } while ( 1 ); }
// watch again
pDirWatcherOverlapped->m_pDirWatcher->PostDirWatch(); } }; #elif defined(OSX)
void CheckDirectoryForChanges( const char *path_buff, CDirWatcher *pDirWatch, bool bRecurse ) { DIR *dir = opendir(path_buff); char fullpath[MAX_PATH]; struct dirent *dirent; struct timespec ts = { 0, 0 }; bool bTimeSet = false; while ( (dirent = readdir(dir)) != NULL ) { if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) continue; snprintf( fullpath, PATH_MAX, "%s/%s", path_buff, dirent->d_name ); struct stat st; if (lstat(fullpath, &st) != 0) continue; if ( S_ISDIR(st.st_mode) && bRecurse ) { CheckDirectoryForChanges( fullpath, pDirWatch, bRecurse ); } else if ( st.st_mtimespec.tv_sec > pDirWatch->m_modTime.tv_sec || ( st.st_mtimespec.tv_sec == pDirWatch->m_modTime.tv_sec && st.st_mtimespec.tv_nsec > pDirWatch->m_modTime.tv_nsec ) ) { ts = st.st_mtimespec; bTimeSet = true; // the win32 size only sends up the dir relative to the watching dir, so replicate that here
pDirWatch->AddFileToChangeList( fullpath + pDirWatch->m_BaseDir.Length() + 1 ); } }
if ( bTimeSet ) pDirWatch->m_modTime = ts; closedir(dir); }
static void fsevents_callback( ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents,void *eventPaths, const FSEventStreamEventFlags eventMasks[], const FSEventStreamEventId eventIDs[] ) { char path_buff[PATH_MAX]; for (int i=0; i < numEvents; i++) { char **paths = (char **)eventPaths; strcpy(path_buff, paths[i]); int len = strlen(path_buff); if (path_buff[len-1] == '/') { // chop off a trailing slash
path_buff[--len] = '\0'; } bool bRecurse = false; if (eventMasks[i] & kFSEventStreamEventFlagMustScanSubDirs || eventMasks[i] & kFSEventStreamEventFlagUserDropped || eventMasks[i] & kFSEventStreamEventFlagKernelDropped) { bRecurse = true; } CDirWatcher *pDirWatch = (CDirWatcher *)clientCallBackInfo; // make sure its in our subdir
if ( !V_strnicmp( path_buff, pDirWatch->m_BaseDir.String(), pDirWatch->m_BaseDir.Length() ) ) CheckDirectoryForChanges( path_buff, pDirWatch, bRecurse ); } }
#endif
//-----------------------------------------------------------------------------
// Purpose: only one directory can be watched at a time
//-----------------------------------------------------------------------------
void CDirWatcher::SetDirToWatch( const char *pchDir ) { if ( !pchDir || !*pchDir ) return; CPathString strPath( pchDir ); #ifdef WIN32
// open the directory
m_hFile = ::CreateFileW( strPath.GetWCharPathPrePended(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS, NULL );
// create our buffers
m_pFileInfo = malloc( k_cubDirWatchBufferSize ); m_pOverlapped = malloc( sizeof( DirWatcherOverlapped ) );
// post a watch
PostDirWatch(); #elif defined(OSX)
CFStringRef mypath = CFStringCreateWithCString( NULL, strPath.GetUTF8Path(), kCFStringEncodingMacRoman ); if ( !mypath ) { Assert( !"Failed to CFStringCreateWithCString watcher path" ); return; } CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL); FSEventStreamContext callbackInfo = {0, this, NULL, NULL, NULL}; CFAbsoluteTime latency = 1.0; // Latency in seconds
m_WatcherStream = (void *)FSEventStreamCreate(NULL, &fsevents_callback, &callbackInfo, pathsToWatch, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagNoDefer ); FSEventStreamScheduleWithRunLoop( (FSEventStreamRef)m_WatcherStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); CFRelease(pathsToWatch ); CFRelease( mypath ); FSEventStreamStart( (FSEventStreamRef)m_WatcherStream );
char szFullPath[MAX_PATH]; Q_MakeAbsolutePath( szFullPath, sizeof(szFullPath), pchDir ); m_BaseDir = szFullPath; struct timeval tv; gettimeofday( &tv, NULL ); TIMEVAL_TO_TIMESPEC( &tv, &m_modTime ); #else
Assert( !"Impl me" ); #endif
}
#ifdef WIN32
//-----------------------------------------------------------------------------
// Purpose: used by callback functions to push a file onto the list
//-----------------------------------------------------------------------------
void CDirWatcher::PostDirWatch() { memset( m_pOverlapped, 0, sizeof(DirWatcherOverlapped) ); DirWatcherOverlapped *pDirWatcherOverlapped = (DirWatcherOverlapped *)m_pOverlapped; pDirWatcherOverlapped->m_pDirWatcher = this;
DWORD dwBytes; ::ReadDirectoryChangesW( m_hFile, m_pFileInfo, k_cubDirWatchBufferSize, TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytes, (OVERLAPPED *)m_pOverlapped, &CDirWatcherFriend::DirWatchCallback ); } #endif
//-----------------------------------------------------------------------------
// Purpose: used by callback functions to push a file onto the list
//-----------------------------------------------------------------------------
void CDirWatcher::AddFileToChangeList( const char *pchFile ) { // make sure it isn't already in the list
FOR_EACH_LL( m_listChangedFiles, i ) { if ( !Q_stricmp( m_listChangedFiles[i], pchFile ) ) return; }
m_listChangedFiles.AddToTail( pchFile ); }
//-----------------------------------------------------------------------------
// Purpose: retrieve any changes
//-----------------------------------------------------------------------------
bool CDirWatcher::GetChangedFile( CUtlString *psFile ) { #ifdef WIN32
// this will trigger any pending directory reads
// this does get hit other places in the code; so the callback can happen at any time
::SleepEx( 0, TRUE ); #endif
if ( !m_listChangedFiles.Count() ) return false;
*psFile = m_listChangedFiles[m_listChangedFiles.Head()]; m_listChangedFiles.Remove( m_listChangedFiles.Head() ); return true; }
#ifdef DBGFLAG_VALIDATE
void CDirWatcher::Validate( CValidator &validator, const char *pchName ) { VALIDATE_SCOPE();
validator.ClaimMemory( m_pOverlapped ); validator.ClaimMemory( m_pFileInfo ); ValidateObj( m_listChangedFiles ); FOR_EACH_LL( m_listChangedFiles, i ) { ValidateObj( m_listChangedFiles[i] ); } } #endif
#endif // _PS3 || _X360
//-----------------------------------------------------------------------------
// Purpose: utility function to create dirs & subdirs
//-----------------------------------------------------------------------------
bool CreateDirRecursive( const char *pchPathIn ) { CPathString strPath( pchPathIn );
// Cast away const, we're going to modify in place even though that's kind of evil
char *path = (char *)strPath.GetUTF8Path();
// Does it already exist?
if ( BFileExists( path ) ) return true;
// Walk backwards to first non-existing dir that we find
char *s = path + Q_strlen(path) - 1;
while ( s > path ) { if ( *s == CORRECT_PATH_SEPARATOR ) { *s = '\0'; bool bExists = BFileExists( path ); *s = CORRECT_PATH_SEPARATOR;
if ( bExists ) { ++s; break; } } --s; }
// and then move forwards from there
while ( *s ) { if ( *s == CORRECT_PATH_SEPARATOR ) { *s = '\0'; BCreateDirectory( path ); *s = CORRECT_PATH_SEPARATOR; } s++; }
if ( !BCreateDirectory( path ) ) { return false; } return true; }
//-----------------------------------------------------------------------------
// Purpose: Creates the directory, returning true if it is created, or if it already existed
//-----------------------------------------------------------------------------
bool BCreateDirectory( const char *path ) { CPathString pathStr( path ); #ifdef WIN32
if ( ::CreateDirectoryW( pathStr.GetWCharPathPrePended(), NULL ) ) return true;
if ( ::GetLastError() == ERROR_ALREADY_EXISTS ) return true;
return false; #else
int i = mkdir( pathStr.GetUTF8Path(), S_IRWXU | S_IRWXG | S_IRWXO ); if ( i == 0 ) return true; if ( errno == EEXIST ) return true;
return false; #endif
}
//-----------------------------------------------------------------------------
// Purpose: make a file writable
//-----------------------------------------------------------------------------
bool MakeFileWriteable( const char *pszFileNameIn ) { CPathString strPath( pszFileNameIn ); #if defined( WIN32_FILEIO )
DWORD dwFileAttributes = ::GetFileAttributesW( strPath.GetWCharPathPrePended() );
if (dwFileAttributes != INVALID_FILE_ATTRIBUTES) { // remove flags that make it read only, if necessary
if (dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY)) { dwFileAttributes &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY); ::SetFileAttributesW( strPath.GetWCharPathPrePended(), dwFileAttributes ); } return true; } return false; #else
statBig_t statBuf; if (statBig( strPath.GetUTF8Path(), &statBuf ) != 0) return false; if (statBuf.st_mode & _S_IWRITE) return true; int ret = chmod( strPath.GetUTF8Path(), statBuf.st_mode | _S_IWRITE ); return (ret == 0); #endif
}
//-----------------------------------------------------------------------------
// Purpose: deletes a file
//-----------------------------------------------------------------------------
bool UnlinkFile( const char *pchFileIn ) { CPathString strPath( pchFileIn ); #ifdef _WIN32
if (::DeleteFileW( strPath.GetWCharPathPrePended() )) return true;
return false; #else
return (0 == _unlink( strPath.GetUTF8Path() )); #endif
}
//-----------------------------------------------------------------------------
// Purpose: Checks if a file exists
// Input: pchFileName - file name to check existence of (UTF8 - unqualified, relative, or fully qualified)
// Output: true if successful (file did not exist, or it existed and was deleted);
// false if unsuccessful (file existed but could not be deleted)
//-----------------------------------------------------------------------------
bool BFileExists( const char *pchFileNameIn ) { CPathString strPath( pchFileNameIn );
#if defined( WIN32_FILEIO )
// Checking file attributes is fastest way to determine existence
return (INVALID_FILE_ATTRIBUTES != ::GetFileAttributesW( strPath.GetWCharPathPrePended() )); #else
statBig_t buf; return (0 == statBig( strPath.GetUTF8Path(), &buf )); #endif
}
//-----------------------------------------------------------------------------
// Purpose: Deletes a file if it exists. If the file is read-only, will attempt
// to change file attributes and delete it.
// Input: pchFileName - file name to delete (unqualified, relative, or fully qualified)
// Output: true if successful (file did not exist, or it existed and was deleted);
// false if unsuccessful (file existed but could not be deleted)
//-----------------------------------------------------------------------------
bool BDeleteFileIfExists( const char * pchFileName ) { // vast majority don't need to be touched/tested to delete them, so don't
// take the penalty in the common case.
if (UnlinkFile( pchFileName )) return true;
if (BFileExists( pchFileName )) { MakeFileWriteable( pchFileName ); return UnlinkFile( pchFileName ); } else { return true; // doesn't exist
} }
//-----------------------------------------------------------------------------
// Purpose: Removes an empty directory that works on multiple platforms.
//-----------------------------------------------------------------------------
bool BRemoveDirectory( const char *pchPathIn ) { MakeFileWriteable( pchPathIn );
CPathString strPath( pchPathIn ); #if defined( WIN32_FILEIO )
if (::RemoveDirectoryW( strPath.GetWCharPathPrePended() )) return true; return false; #else
return _rmdir( pchPathIn ) == 0; #endif
}
//-----------------------------------------------------------------------------
// Purpose: Removes a directory and all subdirectories and all files in those directories
//-----------------------------------------------------------------------------
bool BRemoveDirectoryRecursive( const char *pchPathIn ) { CDirIterator dirIter( pchPathIn, "*" );
while (dirIter.BNextFile()) { uint32 unLenPath = Q_strlen( pchPathIn ) + Q_strlen( dirIter.CurrentFileName() ) + 2; char *pchPath = new char[unLenPath]; Q_snprintf( pchPath, unLenPath, "%s%c%s", pchPathIn, CORRECT_PATH_SEPARATOR, dirIter.CurrentFileName() );
if (dirIter.BCurrentIsDir()) { BRemoveDirectoryRecursive( pchPath ); } else { // Shouldn't have files in the root dir, delete them if found
BDeleteFileIfExists( pchPath ); } delete[] pchPath; }
return BRemoveDirectory( pchPathIn ); }
#ifdef POSIX
// findfirst/findnext implementation from filesystem/linux_support.[h|cpp]
// modified a bit for PS3
#if !defined(_PS3)
static char selectBuf[PATH_MAX];
#if defined(OSX) && !defined(__MAC_10_8)
static int FileSelect( direntBig_t *ent ) #elif defined(LINUX) || defined(OSX)
static int FileSelect( const direntBig_t *ent ) #else
#error
#endif
{ const char *mask = selectBuf; const char *name = ent->d_name;
return FileSelect( name, mask ); }
#endif // !_PS3
static int FileSelect( const char *name, const char *mask ) { //printf("Test:%s %s\n",mask,name);
if (!strcmp( name, "." ) || !strcmp( name, ".." )) return 0;
if (!strcmp( mask, "*.*" ) || !strcmp( mask, "*" )) 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
}
#if !defined(_PS3)
int FillDataStruct( _finddata_t *dat ) { statBig_t fileStat;
if (dat->curName >= dat->numNames) return -1;
Q_strncpy( dat->name, dat->namelist[dat->curName]->d_name, sizeof(dat->name) ); char szFullPath[MAX_PATH]; Q_snprintf( szFullPath, sizeof(szFullPath), "%s%c%s", dat->dirBase, CORRECT_PATH_SEPARATOR, dat->name ); if (statBig( szFullPath, &fileStat ) == 0) { dat->attrib = fileStat.st_mode; dat->size = fileStat.st_size; dat->time_write = fileStat.st_mtime; dat->time_create = fileStat.st_ctime; } else { dat->attrib = 0; dat->size = 0; dat->time_write = 0; dat->time_create = 0; } free( dat->namelist[dat->curName] ); dat->namelist[dat->curName] = NULL; dat->curName++; return 1; }
int _findfirst( const char *fileName, _finddata_t *dat ) { char nameStore[PATH_MAX]; char *dir = NULL; int n, iret = -1;
Q_strncpy( nameStore, fileName, sizeof(nameStore) );
if (strrchr( nameStore, '/' )) { dir = nameStore; while (strrchr( dir, '/' )) { statBig_t dirChk;
// zero this with the dir name
dir = strrchr( nameStore, '/' ); *dir = '\0'; if (dir == nameStore) { dir = "/"; } else { dir = nameStore; }
if (statBig( dir, &dirChk ) == 0 && S_ISDIR( dirChk.st_mode )) { break; } } } else { // couldn't find a dir separator...
return -1; }
if (strlen( dir ) > 0) { if (strlen( dir ) == 1) Q_strncpy( selectBuf, fileName + 1, sizeof(selectBuf) ); else Q_strncpy( selectBuf, fileName + strlen( dir ) + 1, sizeof(selectBuf) );
n = scandirBig( dir, &dat->namelist, FileSelect, alphasortBig ); if (n < 0) { // silently return, nothing interesting
} else { dat->curName = 0; dat->numNames = n; // n is the number of matches
Q_strncpy( dat->dirBase, dir, sizeof(dat->dirBase) ); iret = FillDataStruct( dat ); if (iret < 0) { free( dat->namelist ); dat->namelist = NULL; dat->curName = 0; dat->numNames = 0; } } }
// printf("Returning: %i \n",iret);
return iret; }
int _findnext( int64 handle, _finddata_t *dat ) { if (dat->curName >= dat->numNames) { free( dat->namelist ); dat->namelist = NULL; dat->curName = 0; dat->numNames = 0; return -1; // no matches left
}
FillDataStruct( dat ); return 0; }
bool _findclose( int64 handle ) { return true; }
#endif // !_PS3
#endif
|