csgo-2018-source/tier1/fileio.cpp
2021-07-24 21:11:47 -07:00

1928 lines
53 KiB
C++

//========= 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