Team Fortress 2 Source Code as on 22/4/2020
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.

496 lines
14 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: A collection of utility classes to simplify file I/O, and
  4. // as much as possible contain portability problems. Here avoiding
  5. // including windows.h.
  6. //
  7. //=============================================================================
  8. #if defined(_WIN32)
  9. #undef _WIN32_WINNT
  10. #define _WIN32_WINNT 0x0502 // ReadDirectoryChangesW
  11. #endif
  12. #if defined(OSX)
  13. #include <CoreServices/CoreServices.h>
  14. #include <sys/types.h>
  15. #include <dirent.h>
  16. #include <sys/time.h>
  17. #endif
  18. #define ASYNC_FILEIO
  19. #if defined( LINUX )
  20. // Linux hasn't got a good AIO library that we have found yet, so lets punt for now
  21. #undef ASYNC_FILEIO
  22. #endif
  23. #if defined(_WIN32)
  24. //#include <direct.h>
  25. #include <io.h>
  26. // unset to force to use stdio implementation
  27. #define WIN32_FILEIO
  28. #if defined(ASYNC_FILEIO)
  29. #if defined(_WIN32) && !defined(WIN32_FILEIO)
  30. #error "trying to use async io without win32 filesystem API usage, that isn't doable"
  31. #endif
  32. #endif
  33. #else /* not defined (_WIN32) */
  34. #include <utime.h>
  35. #include <dirent.h>
  36. #include <unistd.h> // for unlink
  37. #include <limits.h> // defines PATH_MAX
  38. #include <alloca.h> // 'cause we like smashing the stack
  39. #if defined( _PS3 )
  40. #include <fcntl.h>
  41. #else
  42. #include <sys/fcntl.h>
  43. #include <sys/statvfs.h>
  44. #endif
  45. #include <sched.h>
  46. #define int64 int64_t
  47. #define _A_SUBDIR S_IFDIR
  48. // FUTURE map _A_HIDDEN via checking filename against .*
  49. #define _A_HIDDEN 0
  50. // FUTURE check 'read only' by checking mode against S_IRUSR
  51. #define _A_RDONLY 0
  52. // no files under posix are 'system' or 'archive'
  53. #define _A_SYSTEM 0
  54. #define _A_ARCH 0
  55. #endif
  56. #include "tier1/fileio.h"
  57. #include "tier1/utlbuffer.h"
  58. #include "tier1/strtools.h"
  59. #include <errno.h>
  60. #if defined( WIN32_FILEIO )
  61. #include "winlite.h"
  62. #endif
  63. #if defined( ASYNC_FILEIO )
  64. #ifdef _WIN32
  65. #include "winlite.h"
  66. #elif defined(_PS3)
  67. // bugbug ps3 - see some aio files under libfs.. skipping for the moment
  68. #elif defined(POSIX)
  69. #include <aio.h>
  70. #else
  71. #error "aio please"
  72. #endif
  73. #endif
  74. //-----------------------------------------------------------------------------
  75. // Purpose: Constructor from UTF8
  76. //-----------------------------------------------------------------------------
  77. CPathString::CPathString( const char *pchUTF8Path )
  78. {
  79. // Need to first turn into an absolute path, so \\?\ pre-pended paths will be ok
  80. m_pchUTF8Path = new char[ MAX_UNICODE_PATH_IN_UTF8 ];
  81. m_pwchWideCharPathPrepended = NULL;
  82. // First, convert to absolute path, which also does Q_FixSlashes for us.
  83. Q_MakeAbsolutePath( m_pchUTF8Path, MAX_UNICODE_PATH * 4, pchUTF8Path );
  84. // Second, fix any double slashes
  85. V_FixDoubleSlashes( m_pchUTF8Path );
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Purpose: Destructor
  89. //-----------------------------------------------------------------------------
  90. CPathString::~CPathString()
  91. {
  92. if ( m_pwchWideCharPathPrepended )
  93. {
  94. delete[] m_pwchWideCharPathPrepended;
  95. m_pwchWideCharPathPrepended = NULL;
  96. }
  97. if ( m_pchUTF8Path )
  98. {
  99. delete[] m_pchUTF8Path;
  100. m_pchUTF8Path = NULL;
  101. }
  102. }
  103. //-----------------------------------------------------------------------------
  104. // Purpose: Access UTF8 path
  105. //-----------------------------------------------------------------------------
  106. const char * CPathString::GetUTF8Path()
  107. {
  108. return m_pchUTF8Path;
  109. }
  110. //-----------------------------------------------------------------------------
  111. // Purpose: Gets wchar_t based path, with \\?\ pre-pended (allowing long paths
  112. // on Win32, should only be used with unicode extended path aware filesystem calls)
  113. //-----------------------------------------------------------------------------
  114. const wchar_t *CPathString::GetWCharPathPrePended()
  115. {
  116. PopulateWCharPath();
  117. return m_pwchWideCharPathPrepended;
  118. }
  119. //-----------------------------------------------------------------------------
  120. // Purpose: Builds wchar path string
  121. //-----------------------------------------------------------------------------
  122. void CPathString::PopulateWCharPath()
  123. {
  124. if ( m_pwchWideCharPathPrepended )
  125. return;
  126. // Check if the UTF8 path starts with \\, which on Win32 means it's a UNC path, and then needs a different prefix
  127. if ( m_pchUTF8Path[0] == '\\' && m_pchUTF8Path[1] == '\\' )
  128. {
  129. m_pwchWideCharPathPrepended = new wchar_t[MAX_UNICODE_PATH+8];
  130. Q_memcpy( m_pwchWideCharPathPrepended, L"\\\\?\\UNC\\", 8*sizeof(wchar_t) );
  131. #ifdef DBGFLAG_ASSERT
  132. int cchResult =
  133. #endif
  134. Q_UTF8ToUnicode( m_pchUTF8Path+2, m_pwchWideCharPathPrepended+8, MAX_UNICODE_PATH*sizeof(wchar_t) );
  135. Assert( cchResult );
  136. // Be sure we NULL terminate within our allocated region incase Q_UTF8ToUnicode failed, though we're already in bad shape then.
  137. m_pwchWideCharPathPrepended[MAX_UNICODE_PATH+7] = 0;
  138. }
  139. else
  140. {
  141. m_pwchWideCharPathPrepended = new wchar_t[MAX_UNICODE_PATH+4];
  142. Q_memcpy( m_pwchWideCharPathPrepended, L"\\\\?\\", 4*sizeof(wchar_t) );
  143. #ifdef DBGFLAG_ASSERT
  144. int cchResult =
  145. #endif
  146. Q_UTF8ToUnicode( m_pchUTF8Path, m_pwchWideCharPathPrepended+4, MAX_UNICODE_PATH*sizeof(wchar_t) );
  147. Assert( cchResult );
  148. // Be sure we NULL terminate within our allocated region incase Q_UTF8ToUnicode failed, though we're already in bad shape then.
  149. m_pwchWideCharPathPrepended[MAX_UNICODE_PATH+3] = 0;
  150. }
  151. }
  152. #ifdef WIN32
  153. struct DirWatcherOverlapped : public OVERLAPPED
  154. {
  155. CDirWatcher *m_pDirWatcher;
  156. };
  157. #endif
  158. #if !defined(_PS3) && !defined(_X360)
  159. // a buffer full of file names
  160. static const int k_cubDirWatchBufferSize = 8 * 1024;
  161. //-----------------------------------------------------------------------------
  162. // Purpose: directory watching
  163. //-----------------------------------------------------------------------------
  164. CDirWatcher::CDirWatcher()
  165. {
  166. m_hFile = NULL;
  167. m_pOverlapped = NULL;
  168. m_pFileInfo = NULL;
  169. #ifdef OSX
  170. m_WatcherStream = 0;
  171. #endif
  172. }
  173. //-----------------------------------------------------------------------------
  174. // Purpose: directory watching
  175. //-----------------------------------------------------------------------------
  176. CDirWatcher::~CDirWatcher()
  177. {
  178. #ifdef WIN32
  179. if ( m_pOverlapped )
  180. {
  181. // mark the overlapped structure as gone
  182. DirWatcherOverlapped *pDirWatcherOverlapped = (DirWatcherOverlapped *)m_pOverlapped;
  183. pDirWatcherOverlapped->m_pDirWatcher = NULL;
  184. }
  185. if ( m_hFile )
  186. {
  187. // make sure we flush any pending I/O's on the handle
  188. ::CancelIo( m_hFile );
  189. ::SleepEx( 0, TRUE );
  190. // close the handle
  191. ::CloseHandle( m_hFile );
  192. }
  193. #elif defined(OSX)
  194. if ( m_WatcherStream )
  195. {
  196. FSEventStreamStop( (FSEventStreamRef)m_WatcherStream );
  197. FSEventStreamInvalidate( (FSEventStreamRef)m_WatcherStream );
  198. FSEventStreamRelease( (FSEventStreamRef)m_WatcherStream );
  199. m_WatcherStream = 0;
  200. }
  201. #endif
  202. if ( m_pFileInfo )
  203. {
  204. free( m_pFileInfo );
  205. }
  206. if ( m_pOverlapped )
  207. {
  208. free( m_pOverlapped );
  209. }
  210. }
  211. #ifdef WIN32
  212. //-----------------------------------------------------------------------------
  213. // Purpose: callback watch
  214. // gets called on the same thread whenever a SleepEx() occurs
  215. //-----------------------------------------------------------------------------
  216. class CDirWatcherFriend
  217. {
  218. public:
  219. static void WINAPI DirWatchCallback( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, OVERLAPPED *pOverlapped )
  220. {
  221. DirWatcherOverlapped *pDirWatcherOverlapped = (DirWatcherOverlapped *)pOverlapped;
  222. // see if we've been cancelled
  223. if ( !pDirWatcherOverlapped->m_pDirWatcher )
  224. return;
  225. // parse and pass back
  226. if ( dwNumberOfBytesTransfered > sizeof(FILE_NOTIFY_INFORMATION) )
  227. {
  228. FILE_NOTIFY_INFORMATION *pFileNotifyInformation = (FILE_NOTIFY_INFORMATION *)pDirWatcherOverlapped->m_pDirWatcher->m_pFileInfo;
  229. do
  230. {
  231. // null terminate the string and turn it to UTF-8
  232. int cNumWChars = pFileNotifyInformation->FileNameLength / sizeof(wchar_t);
  233. wchar_t *pwchT = new wchar_t[cNumWChars + 1];
  234. memcpy( pwchT, pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength );
  235. pwchT[cNumWChars] = 0;
  236. CStrAutoEncode strAutoEncode( pwchT );
  237. // add it to our list
  238. pDirWatcherOverlapped->m_pDirWatcher->AddFileToChangeList( strAutoEncode.ToString() );
  239. delete[] pwchT;
  240. if ( pFileNotifyInformation->NextEntryOffset == 0 )
  241. break;
  242. // move to the next file
  243. pFileNotifyInformation = (FILE_NOTIFY_INFORMATION *)(((byte*)pFileNotifyInformation) + pFileNotifyInformation->NextEntryOffset);
  244. } while ( 1 );
  245. }
  246. // watch again
  247. pDirWatcherOverlapped->m_pDirWatcher->PostDirWatch();
  248. }
  249. };
  250. #elif defined(OSX)
  251. void CheckDirectoryForChanges( const char *path_buff, CDirWatcher *pDirWatch, bool bRecurse )
  252. {
  253. DIR *dir = opendir(path_buff);
  254. char fullpath[MAX_PATH];
  255. struct dirent *dirent;
  256. struct timespec ts = { 0, 0 };
  257. bool bTimeSet = false;
  258. while ( (dirent = readdir(dir)) != NULL )
  259. {
  260. if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
  261. continue;
  262. snprintf( fullpath, PATH_MAX, "%s/%s", path_buff, dirent->d_name );
  263. struct stat st;
  264. if (lstat(fullpath, &st) != 0)
  265. continue;
  266. if ( S_ISDIR(st.st_mode) && bRecurse )
  267. {
  268. CheckDirectoryForChanges( fullpath, pDirWatch, bRecurse );
  269. }
  270. else if ( st.st_mtimespec.tv_sec > pDirWatch->m_modTime.tv_sec ||
  271. ( st.st_mtimespec.tv_sec == pDirWatch->m_modTime.tv_sec && st.st_mtimespec.tv_nsec > pDirWatch->m_modTime.tv_nsec ) )
  272. {
  273. ts = st.st_mtimespec;
  274. bTimeSet = true;
  275. // the win32 size only sends up the dir relative to the watching dir, so replicate that here
  276. pDirWatch->AddFileToChangeList( fullpath + pDirWatch->m_BaseDir.Length() + 1 );
  277. }
  278. }
  279. if ( bTimeSet )
  280. pDirWatch->m_modTime = ts;
  281. closedir(dir);
  282. }
  283. static void fsevents_callback( ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents,void *eventPaths,
  284. const FSEventStreamEventFlags eventMasks[], const FSEventStreamEventId eventIDs[] )
  285. {
  286. char path_buff[PATH_MAX];
  287. for (int i=0; i < numEvents; i++)
  288. {
  289. char **paths = (char **)eventPaths;
  290. strcpy(path_buff, paths[i]);
  291. int len = strlen(path_buff);
  292. if (path_buff[len-1] == '/')
  293. {
  294. // chop off a trailing slash
  295. path_buff[--len] = '\0';
  296. }
  297. bool bRecurse = false;
  298. if (eventMasks[i] & kFSEventStreamEventFlagMustScanSubDirs
  299. || eventMasks[i] & kFSEventStreamEventFlagUserDropped
  300. || eventMasks[i] & kFSEventStreamEventFlagKernelDropped)
  301. {
  302. bRecurse = true;
  303. }
  304. CDirWatcher *pDirWatch = (CDirWatcher *)clientCallBackInfo;
  305. // make sure its in our subdir
  306. if ( !V_strnicmp( path_buff, pDirWatch->m_BaseDir.String(), pDirWatch->m_BaseDir.Length() ) )
  307. CheckDirectoryForChanges( path_buff, pDirWatch, bRecurse );
  308. }
  309. }
  310. #endif
  311. //-----------------------------------------------------------------------------
  312. // Purpose: only one directory can be watched at a time
  313. //-----------------------------------------------------------------------------
  314. void CDirWatcher::SetDirToWatch( const char *pchDir )
  315. {
  316. if ( !pchDir || !*pchDir )
  317. return;
  318. CPathString strPath( pchDir );
  319. #ifdef WIN32
  320. // open the directory
  321. 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 );
  322. // create our buffers
  323. m_pFileInfo = malloc( k_cubDirWatchBufferSize );
  324. m_pOverlapped = malloc( sizeof( DirWatcherOverlapped ) );
  325. // post a watch
  326. PostDirWatch();
  327. #elif defined(OSX)
  328. CFStringRef mypath = CFStringCreateWithCString( NULL, strPath.GetUTF8Path(), kCFStringEncodingMacRoman );
  329. if ( !mypath )
  330. {
  331. Assert( !"Failed to CFStringCreateWithCString watcher path" );
  332. return;
  333. }
  334. CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL);
  335. FSEventStreamContext callbackInfo = {0, this, NULL, NULL, NULL};
  336. CFAbsoluteTime latency = 1.0; // Latency in seconds
  337. m_WatcherStream = (void *)FSEventStreamCreate(NULL,
  338. &fsevents_callback,
  339. &callbackInfo,
  340. pathsToWatch,
  341. kFSEventStreamEventIdSinceNow,
  342. latency,
  343. kFSEventStreamCreateFlagNoDefer
  344. );
  345. FSEventStreamScheduleWithRunLoop( (FSEventStreamRef)m_WatcherStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
  346. CFRelease(pathsToWatch );
  347. CFRelease( mypath );
  348. FSEventStreamStart( (FSEventStreamRef)m_WatcherStream );
  349. char szFullPath[MAX_PATH];
  350. Q_MakeAbsolutePath( szFullPath, sizeof(szFullPath), pchDir );
  351. m_BaseDir = szFullPath;
  352. struct timeval tv;
  353. gettimeofday( &tv, NULL );
  354. TIMEVAL_TO_TIMESPEC( &tv, &m_modTime );
  355. #else
  356. Assert( !"Impl me" );
  357. #endif
  358. }
  359. #ifdef WIN32
  360. //-----------------------------------------------------------------------------
  361. // Purpose: used by callback functions to push a file onto the list
  362. //-----------------------------------------------------------------------------
  363. void CDirWatcher::PostDirWatch()
  364. {
  365. memset( m_pOverlapped, 0, sizeof(DirWatcherOverlapped) );
  366. DirWatcherOverlapped *pDirWatcherOverlapped = (DirWatcherOverlapped *)m_pOverlapped;
  367. pDirWatcherOverlapped->m_pDirWatcher = this;
  368. DWORD dwBytes;
  369. ::ReadDirectoryChangesW( m_hFile, m_pFileInfo, k_cubDirWatchBufferSize, TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytes, (OVERLAPPED *)m_pOverlapped, &CDirWatcherFriend::DirWatchCallback );
  370. }
  371. #endif
  372. //-----------------------------------------------------------------------------
  373. // Purpose: used by callback functions to push a file onto the list
  374. //-----------------------------------------------------------------------------
  375. void CDirWatcher::AddFileToChangeList( const char *pchFile )
  376. {
  377. // make sure it isn't already in the list
  378. FOR_EACH_LL( m_listChangedFiles, i )
  379. {
  380. if ( !Q_stricmp( m_listChangedFiles[i], pchFile ) )
  381. return;
  382. }
  383. m_listChangedFiles.AddToTail( pchFile );
  384. }
  385. //-----------------------------------------------------------------------------
  386. // Purpose: retrieve any changes
  387. //-----------------------------------------------------------------------------
  388. bool CDirWatcher::GetChangedFile( CUtlString *psFile )
  389. {
  390. #ifdef WIN32
  391. // this will trigger any pending directory reads
  392. // this does get hit other places in the code; so the callback can happen at any time
  393. ::SleepEx( 0, TRUE );
  394. #endif
  395. if ( !m_listChangedFiles.Count() )
  396. return false;
  397. *psFile = m_listChangedFiles[m_listChangedFiles.Head()];
  398. m_listChangedFiles.Remove( m_listChangedFiles.Head() );
  399. return true;
  400. }
  401. #ifdef DBGFLAG_VALIDATE
  402. void CDirWatcher::Validate( CValidator &validator, const char *pchName )
  403. {
  404. VALIDATE_SCOPE();
  405. validator.ClaimMemory( m_pOverlapped );
  406. validator.ClaimMemory( m_pFileInfo );
  407. ValidateObj( m_listChangedFiles );
  408. FOR_EACH_LL( m_listChangedFiles, i )
  409. {
  410. ValidateObj( m_listChangedFiles[i] );
  411. }
  412. }
  413. #endif
  414. #endif // _PS3 || _X360