FileSystem.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. //
  2. // Urho3D Engine
  3. // Copyright (c) 2008-2012 Lasse Öörni
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. //
  23. #include "Precompiled.h"
  24. #include "ArrayPtr.h"
  25. #include "Context.h"
  26. #include "File.h"
  27. #include "FileSystem.h"
  28. #include "Log.h"
  29. #include <cstdio>
  30. #include <cstring>
  31. #ifdef WIN32
  32. #include <windows.h>
  33. #include <shellapi.h>
  34. #include <direct.h>
  35. #include <shlobj.h>
  36. #else
  37. #include <dirent.h>
  38. #include <errno.h>
  39. #include <unistd.h>
  40. #include <sys/stat.h>
  41. #include <sys/wait.h>
  42. #define MAX_PATH 256
  43. #endif
  44. #if defined(__APPLE__)
  45. #include <mach-o/dyld.h>
  46. #endif
  47. #ifdef ANDROID
  48. extern "C" const char* SDL_Android_GetFilesDir();
  49. #endif
  50. #ifdef IOS
  51. extern "C" const char* SDL_IOS_GetResourceDir();
  52. #endif
  53. #include "DebugNew.h"
  54. OBJECTTYPESTATIC(FileSystem);
  55. FileSystem::FileSystem(Context* context) :
  56. Object(context)
  57. {
  58. }
  59. FileSystem::~FileSystem()
  60. {
  61. }
  62. bool FileSystem::SetCurrentDir(const String& pathName)
  63. {
  64. if (!CheckAccess(pathName))
  65. {
  66. LOGERROR("Access denied to " + pathName);
  67. return false;
  68. }
  69. #ifdef WIN32
  70. if (SetCurrentDirectoryW(GetWideNativePath(pathName).CString()) == FALSE)
  71. {
  72. LOGERROR("Failed to change directory to " + pathName);
  73. return false;
  74. }
  75. #else
  76. if (chdir(GetNativePath(pathName).CString()) != 0)
  77. {
  78. LOGERROR("Failed to change directory to " + pathName);
  79. return false;
  80. }
  81. #endif
  82. return true;
  83. }
  84. bool FileSystem::CreateDir(const String& pathName)
  85. {
  86. if (!CheckAccess(pathName))
  87. {
  88. LOGERROR("Access denied to " + pathName);
  89. return false;
  90. }
  91. #ifdef WIN32
  92. bool success = (CreateDirectoryW(GetWideNativePath(RemoveTrailingSlash(pathName)).CString(), 0) == TRUE) ||
  93. (GetLastError() == ERROR_ALREADY_EXISTS);
  94. #else
  95. bool success = mkdir(GetNativePath(RemoveTrailingSlash(pathName)).CString(), S_IRWXU) == 0 || errno == EEXIST;
  96. #endif
  97. if (success)
  98. LOGDEBUG("Created directory " + pathName);
  99. else
  100. LOGERROR("Failed to create directory " + pathName);
  101. return success;
  102. }
  103. int FileSystem::SystemCommand(const String& commandLine)
  104. {
  105. if (allowedPaths_.Empty())
  106. return system(commandLine.CString());
  107. else
  108. {
  109. LOGERROR("Executing an external command is not allowed");
  110. return -1;
  111. }
  112. }
  113. int FileSystem::SystemRun(const String& fileName, const Vector<String>& arguments)
  114. {
  115. if (allowedPaths_.Empty())
  116. {
  117. String fixedFileName = GetNativePath(fileName);
  118. #ifdef WIN32
  119. // Add .exe extension if no extension defined
  120. if (GetExtension(fixedFileName).Empty())
  121. fixedFileName += ".exe";
  122. String commandLine = "\"" + fixedFileName + "\"";
  123. for (unsigned i = 0; i < arguments.Size(); ++i)
  124. commandLine += " " + arguments[i];
  125. STARTUPINFOW startupInfo;
  126. PROCESS_INFORMATION processInfo;
  127. memset(&startupInfo, 0, sizeof startupInfo);
  128. memset(&processInfo, 0, sizeof processInfo);
  129. WString commandLineW(commandLine);
  130. if (!CreateProcessW(NULL, (wchar_t*)commandLineW.CString(), 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &startupInfo, &processInfo))
  131. {
  132. LOGERROR("Failed to execute command " + commandLine);
  133. return -1;
  134. }
  135. WaitForSingleObject(processInfo.hProcess, INFINITE);
  136. DWORD exitCode;
  137. GetExitCodeProcess(processInfo.hProcess, &exitCode);
  138. CloseHandle(processInfo.hProcess);
  139. CloseHandle(processInfo.hThread);
  140. return exitCode;
  141. #else
  142. pid_t pid = fork();
  143. if (!pid)
  144. {
  145. PODVector<const char*> argPtrs;
  146. argPtrs.Push(fixedFileName.CString());
  147. for (unsigned i = 0; i < arguments.Size(); ++i)
  148. argPtrs.Push(arguments[i].CString());
  149. argPtrs.Push(0);
  150. execvp(argPtrs[0], (char**)&argPtrs[0]);
  151. return -1; // Return -1 if we could not spawn the process
  152. }
  153. else if (pid > 0)
  154. {
  155. int exitCode;
  156. wait(&exitCode);
  157. return exitCode ? 1 : 0;
  158. }
  159. else
  160. {
  161. LOGERROR("Failed to fork");
  162. return -1;
  163. }
  164. #endif
  165. }
  166. else
  167. {
  168. LOGERROR("Executing an external command is not allowed");
  169. return -1;
  170. }
  171. }
  172. bool FileSystem::SystemOpen(const String& fileName, const String& mode)
  173. {
  174. #ifdef WIN32
  175. if (allowedPaths_.Empty())
  176. {
  177. if (!FileExists(fileName) && !DirExists(fileName))
  178. {
  179. LOGERROR("File or directory " + fileName + " not found");
  180. return false;
  181. }
  182. bool success = (int)ShellExecuteW(0, !mode.Empty() ? WString(mode).CString() : 0,
  183. GetWideNativePath(fileName).CString(), 0, 0, SW_SHOW) > 32;
  184. if (!success)
  185. LOGERROR("Failed to open " + fileName + " externally");
  186. return success;
  187. }
  188. else
  189. {
  190. LOGERROR("Opening a file externally is not allowed");
  191. return false;
  192. }
  193. #else
  194. /// \todo Implement on Unix-like systems
  195. LOGERROR("SystemOpen not implemented");
  196. return false;
  197. #endif
  198. }
  199. bool FileSystem::Copy(const String& srcFileName, const String& destFileName)
  200. {
  201. if (!CheckAccess(GetPath(srcFileName)))
  202. {
  203. LOGERROR("Access denied to " + srcFileName);
  204. return false;
  205. }
  206. if (!CheckAccess(GetPath(destFileName)))
  207. {
  208. LOGERROR("Access denied to " + destFileName);
  209. return false;
  210. }
  211. SharedPtr<File> srcFile(new File(context_, srcFileName, FILE_READ));
  212. SharedPtr<File> destFile(new File(context_, destFileName, FILE_WRITE));
  213. if (!srcFile->IsOpen() || !destFile->IsOpen())
  214. return false;
  215. unsigned fileSize = srcFile->GetSize();
  216. SharedArrayPtr<unsigned char> buffer(new unsigned char[fileSize]);
  217. unsigned bytesRead = srcFile->Read(buffer.Get(), fileSize);
  218. unsigned bytesWritten = destFile->Write(buffer.Get(), fileSize);
  219. return bytesRead == fileSize && bytesWritten == fileSize;
  220. }
  221. bool FileSystem::Rename(const String& srcFileName, const String& destFileName)
  222. {
  223. if (!CheckAccess(GetPath(srcFileName)))
  224. {
  225. LOGERROR("Access denied to " + srcFileName);
  226. return false;
  227. }
  228. if (!CheckAccess(GetPath(destFileName)))
  229. {
  230. LOGERROR("Access denied to " + destFileName);
  231. return false;
  232. }
  233. #ifdef WIN32
  234. return MoveFileW(GetWideNativePath(srcFileName).CString(), GetWideNativePath(destFileName).CString()) != 0;
  235. #else
  236. return rename(GetNativePath(srcFileName).CString(), GetNativePath(destFileName).CString()) == 0;
  237. #endif
  238. }
  239. bool FileSystem::Delete(const String& fileName)
  240. {
  241. if (!CheckAccess(GetPath(fileName)))
  242. {
  243. LOGERROR("Access denied to " + fileName);
  244. return false;
  245. }
  246. #ifdef WIN32
  247. return DeleteFileW(GetWideNativePath(fileName).CString()) != 0;
  248. #else
  249. return remove(GetNativePath(fileName).CString()) == 0;
  250. #endif
  251. }
  252. String FileSystem::GetCurrentDir() const
  253. {
  254. #ifdef WIN32
  255. wchar_t path[MAX_PATH];
  256. path[0] = 0;
  257. GetCurrentDirectoryW(MAX_PATH, path);
  258. return AddTrailingSlash(String(path));
  259. #else
  260. char path[MAX_PATH];
  261. path[0] = 0;
  262. getcwd(path, MAX_PATH);
  263. return AddTrailingSlash(String(path));
  264. #endif
  265. }
  266. bool FileSystem::CheckAccess(const String& pathName) const
  267. {
  268. String fixedPath = AddTrailingSlash(pathName);
  269. // If no allowed directories defined, succeed always
  270. if (allowedPaths_.Empty())
  271. return true;
  272. // If there is any attempt to go to a parent directory, disallow
  273. if (fixedPath.Find("..") != String::NPOS)
  274. return false;
  275. // Check if the path is a partial match of any of the allowed directories
  276. for (HashSet<String>::ConstIterator i = allowedPaths_.Begin(); i != allowedPaths_.End(); ++i)
  277. {
  278. if (fixedPath.Find(*i) == 0)
  279. return true;
  280. }
  281. // Not found, so disallow
  282. return false;
  283. }
  284. unsigned FileSystem::GetLastModifiedTime(const String& fileName) const
  285. {
  286. if (fileName.Empty() || !CheckAccess(fileName))
  287. return 0;
  288. #ifdef WIN32
  289. WIN32_FILE_ATTRIBUTE_DATA fileAttrData;
  290. memset(&fileAttrData, 0, sizeof fileAttrData);
  291. if (GetFileAttributesExW(WString(fileName).CString(), GetFileExInfoStandard, &fileAttrData))
  292. {
  293. ULARGE_INTEGER ull;
  294. ull.LowPart = fileAttrData.ftLastWriteTime.dwLowDateTime;
  295. ull.HighPart = fileAttrData.ftLastWriteTime.dwHighDateTime;
  296. return (unsigned)(ull.QuadPart / 10000000ULL - 11644473600ULL);
  297. }
  298. else
  299. return 0;
  300. #else
  301. struct stat st;
  302. if (!stat(fileName.CString(), &st))
  303. return (unsigned)st.st_mtime;
  304. else
  305. return 0;
  306. #endif
  307. }
  308. bool FileSystem::FileExists(const String& fileName) const
  309. {
  310. if (!CheckAccess(GetPath(fileName)))
  311. return false;
  312. String fixedName = GetNativePath(RemoveTrailingSlash(fileName));
  313. #ifdef ANDROID
  314. if (fixedName.StartsWith("/apk/"))
  315. {
  316. SDL_RWops* rwOps = SDL_RWFromFile(fileName.Substring(5).CString(), "rb");
  317. if (rwOps)
  318. {
  319. SDL_RWclose(rwOps);
  320. return true;
  321. }
  322. else
  323. return false;
  324. }
  325. #endif
  326. #ifdef WIN32
  327. DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
  328. if (attributes == INVALID_FILE_ATTRIBUTES || attributes & FILE_ATTRIBUTE_DIRECTORY)
  329. return false;
  330. #else
  331. struct stat st;
  332. if (stat(fixedName.CString(), &st) || st.st_mode & S_IFDIR)
  333. return false;
  334. #endif
  335. return true;
  336. }
  337. bool FileSystem::DirExists(const String& pathName) const
  338. {
  339. if (!CheckAccess(pathName))
  340. return false;
  341. String fixedName = GetNativePath(RemoveTrailingSlash(pathName));
  342. #ifdef ANDROID
  343. /// \todo Actually check for existence, now true is always returned for directories within the APK
  344. if (fixedName.StartsWith("/apk/"))
  345. return true;
  346. #endif
  347. #ifdef WIN32
  348. DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
  349. if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY))
  350. return false;
  351. #else
  352. struct stat st;
  353. if (stat(fixedName.CString(), &st) || !(st.st_mode & S_IFDIR))
  354. return false;
  355. #endif
  356. return true;
  357. }
  358. void FileSystem::ScanDir(Vector<String>& result, const String& pathName, const String& filter, unsigned flags, bool recursive) const
  359. {
  360. result.Clear();
  361. if (CheckAccess(pathName))
  362. {
  363. String initialPath = AddTrailingSlash(pathName);
  364. ScanDirInternal(result, initialPath, initialPath, filter, flags, recursive);
  365. }
  366. }
  367. String FileSystem::GetProgramDir() const
  368. {
  369. #if defined(ANDROID)
  370. // This is an internal directory specifier pointing to the assets in the .apk
  371. // Files from this directory will be opened using special handling
  372. return "/apk/";
  373. #elif defined(IOS)
  374. return AddTrailingSlash(SDL_IOS_GetResourceDir());
  375. #elif defined(WIN32)
  376. wchar_t exeName[MAX_PATH];
  377. exeName[0] = 0;
  378. GetModuleFileNameW(0, exeName, MAX_PATH);
  379. return GetPath(String(exeName));
  380. #elif defined(__APPLE__)
  381. char exeName[MAX_PATH];
  382. memset(exeName, 0, MAX_PATH);
  383. unsigned size = MAX_PATH;
  384. _NSGetExecutablePath(exeName, &size);
  385. return GetPath(String(exeName));
  386. #elif defined(__linux__)
  387. char exeName[MAX_PATH];
  388. memset(exeName, 0, MAX_PATH);
  389. pid_t pid = getpid();
  390. String link = "/proc/" + String(pid) + "/exe";
  391. readlink(link.CString(), exeName, MAX_PATH);
  392. return GetPath(String(exeName));
  393. #else
  394. return String();
  395. #endif
  396. }
  397. String FileSystem::GetUserDocumentsDir() const
  398. {
  399. #if defined(ANDROID)
  400. return AddTrailingSlash(SDL_Android_GetFilesDir());
  401. #elif defined(IOS)
  402. return AddTrailingSlash(SDL_IOS_GetResourceDir());
  403. #elif defined(WIN32)
  404. wchar_t pathName[MAX_PATH];
  405. pathName[0] = 0;
  406. SHGetSpecialFolderPathW(0, pathName, CSIDL_PERSONAL, 0);
  407. return AddTrailingSlash(String(pathName));
  408. #else
  409. char pathName[MAX_PATH];
  410. pathName[0] = 0;
  411. strcpy(pathName, getenv("HOME"));
  412. return AddTrailingSlash(String(pathName));
  413. #endif
  414. }
  415. void FileSystem::RegisterPath(const String& pathName)
  416. {
  417. if (pathName.Empty())
  418. return;
  419. allowedPaths_.Insert(AddTrailingSlash(pathName));
  420. }
  421. void FileSystem::ScanDirInternal(Vector<String>& result, String path, const String& startPath,
  422. const String& filter, unsigned flags, bool recursive) const
  423. {
  424. path = AddTrailingSlash(path);
  425. String deltaPath;
  426. if (path.Length() > startPath.Length())
  427. deltaPath = path.Substring(startPath.Length());
  428. #ifdef WIN32
  429. String pathAndFilter = GetNativePath(path + filter);
  430. WIN32_FIND_DATAW info;
  431. HANDLE handle = FindFirstFileW(WString(pathAndFilter).CString(), &info);
  432. if (handle != INVALID_HANDLE_VALUE)
  433. {
  434. do
  435. {
  436. String fileName(info.cFileName);
  437. if (!fileName.Empty())
  438. {
  439. if (info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(flags & SCAN_HIDDEN))
  440. continue;
  441. if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  442. {
  443. if (flags & SCAN_DIRS)
  444. result.Push(deltaPath + fileName);
  445. if (recursive && fileName != "." && fileName != "..")
  446. ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
  447. }
  448. else if (flags & SCAN_FILES)
  449. result.Push(deltaPath + fileName);
  450. }
  451. }
  452. while (FindNextFileW(handle, &info));
  453. FindClose(handle);
  454. }
  455. #else
  456. String filterExtension = filter.Substring(filter.Find('.'));
  457. if (filterExtension.Find('*') != String::NPOS)
  458. filterExtension.Clear();
  459. DIR *dir;
  460. struct dirent *de;
  461. struct stat st;
  462. dir = opendir(GetNativePath(path).CString());
  463. if (dir)
  464. {
  465. while ((de = readdir(dir)))
  466. {
  467. /// \todo Filename may be unnormalized Unicode on Mac OS X. Re-normalize as necessary
  468. String fileName(de->d_name);
  469. String pathAndName = path + fileName;
  470. if (!stat(pathAndName.CString(), &st))
  471. {
  472. if (st.st_mode & S_IFDIR)
  473. {
  474. if (flags & SCAN_DIRS)
  475. result.Push(deltaPath + fileName);
  476. if (recursive && fileName != "." && fileName != "..")
  477. ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
  478. }
  479. else if (flags & SCAN_FILES)
  480. {
  481. if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
  482. result.Push(deltaPath + fileName);
  483. }
  484. }
  485. }
  486. closedir(dir);
  487. }
  488. #endif
  489. }
  490. void SplitPath(const String& fullPath, String& pathName, String& fileName, String& extension)
  491. {
  492. String fullPathCopy = GetInternalPath(fullPath);
  493. unsigned extPos = fullPathCopy.FindLast('.');
  494. unsigned pathPos = fullPathCopy.FindLast('/');
  495. if (extPos != String::NPOS && (pathPos == String::NPOS || extPos > pathPos))
  496. {
  497. extension = fullPathCopy.Substring(extPos).ToLower();
  498. fullPathCopy = fullPathCopy.Substring(0, extPos);
  499. }
  500. else
  501. extension.Clear();
  502. pathPos = fullPathCopy.FindLast('/');
  503. if (pathPos != String::NPOS)
  504. {
  505. fileName = fullPathCopy.Substring(pathPos + 1);
  506. pathName = fullPathCopy.Substring(0, pathPos + 1);
  507. }
  508. else
  509. {
  510. fileName = fullPathCopy;
  511. pathName.Clear();
  512. }
  513. }
  514. String GetPath(const String& fullPath)
  515. {
  516. String path, file, extension;
  517. SplitPath(fullPath, path, file, extension);
  518. return path;
  519. }
  520. String GetFileName(const String& fullPath)
  521. {
  522. String path, file, extension;
  523. SplitPath(fullPath, path, file, extension);
  524. return file;
  525. }
  526. String GetExtension(const String& fullPath)
  527. {
  528. String path, file, extension;
  529. SplitPath(fullPath, path, file, extension);
  530. return extension;
  531. }
  532. String GetFileNameAndExtension(const String& fileName)
  533. {
  534. String path, file, extension;
  535. SplitPath(fileName, path, file, extension);
  536. return file + extension;
  537. }
  538. String AddTrailingSlash(const String& pathName)
  539. {
  540. String ret = pathName;
  541. ret.Replace('\\', '/');
  542. if (!ret.Empty() && ret.Back() != '/')
  543. ret += '/';
  544. return ret;
  545. }
  546. String RemoveTrailingSlash(const String& pathName)
  547. {
  548. String ret = pathName;
  549. ret.Replace('\\', '/');
  550. if (!ret.Empty() && ret.Back() == '/')
  551. ret.Resize(ret.Length() - 1);
  552. return ret;
  553. }
  554. String GetParentPath(const String& path)
  555. {
  556. unsigned pos = RemoveTrailingSlash(path).FindLast('/');
  557. if (pos != String::NPOS)
  558. return path.Substring(0, pos + 1);
  559. else
  560. return String();
  561. }
  562. String GetInternalPath(const String& pathName)
  563. {
  564. return pathName.Replaced('\\', '/');
  565. }
  566. String GetNativePath(const String& pathName)
  567. {
  568. #ifdef WIN32
  569. return pathName.Replaced('/', '\\');
  570. #else
  571. return pathName;
  572. #endif
  573. }
  574. WString GetWideNativePath(const String& pathName)
  575. {
  576. #ifdef WIN32
  577. return WString(pathName.Replaced('/', '\\'));
  578. #else
  579. return WString(pathName);
  580. #endif
  581. }