You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
497 lines
14 KiB
497 lines
14 KiB
5 years ago
|
//========= Copyright 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
|
||
|
|
||
|
#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 )
|
||
|
// 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
|
||
|
|
||
|
#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
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#include "tier1/fileio.h"
|
||
|
#include "tier1/utlbuffer.h"
|
||
|
#include "tier1/strtools.h"
|
||
|
#include <errno.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
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#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
|