497 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			497 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //========= 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
 |