filesys.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. /*
  2. Minetest
  3. Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include "filesys.h"
  17. #include "util/string.h"
  18. #include <iostream>
  19. #include <cstdio>
  20. #include <cstdlib>
  21. #include <cstring>
  22. #include <cerrno>
  23. #include <fstream>
  24. #include "log.h"
  25. #include "config.h"
  26. #include "porting.h"
  27. #ifndef SERVER
  28. #include "irr_ptr.h"
  29. #include <IFileArchive.h>
  30. #include <IFileSystem.h>
  31. #endif
  32. #ifdef __linux__
  33. #include <fcntl.h>
  34. #include <sys/ioctl.h>
  35. #ifndef FICLONE
  36. #define FICLONE _IOW(0x94, 9, int)
  37. #endif
  38. #endif
  39. namespace fs
  40. {
  41. #ifdef _WIN32
  42. /***********
  43. * Windows *
  44. ***********/
  45. #include <windows.h>
  46. #include <shlwapi.h>
  47. #include <io.h>
  48. #include <direct.h>
  49. std::vector<DirListNode> GetDirListing(const std::string &pathstring)
  50. {
  51. std::vector<DirListNode> listing;
  52. WIN32_FIND_DATA FindFileData;
  53. HANDLE hFind = INVALID_HANDLE_VALUE;
  54. DWORD dwError;
  55. std::string dirSpec = pathstring + "\\*";
  56. // Find the first file in the directory.
  57. hFind = FindFirstFile(dirSpec.c_str(), &FindFileData);
  58. if (hFind == INVALID_HANDLE_VALUE) {
  59. dwError = GetLastError();
  60. if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) {
  61. errorstream << "GetDirListing: FindFirstFile error."
  62. << " Error is " << dwError << std::endl;
  63. }
  64. } else {
  65. // NOTE:
  66. // Be very sure to not include '..' in the results, it will
  67. // result in an epic failure when deleting stuff.
  68. DirListNode node;
  69. node.name = FindFileData.cFileName;
  70. node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
  71. if (node.name != "." && node.name != "..")
  72. listing.push_back(node);
  73. // List all the other files in the directory.
  74. while (FindNextFile(hFind, &FindFileData) != 0) {
  75. DirListNode node;
  76. node.name = FindFileData.cFileName;
  77. node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
  78. if(node.name != "." && node.name != "..")
  79. listing.push_back(node);
  80. }
  81. dwError = GetLastError();
  82. FindClose(hFind);
  83. if (dwError != ERROR_NO_MORE_FILES) {
  84. errorstream << "GetDirListing: FindNextFile error."
  85. << " Error is " << dwError << std::endl;
  86. listing.clear();
  87. return listing;
  88. }
  89. }
  90. return listing;
  91. }
  92. bool CreateDir(const std::string &path)
  93. {
  94. bool r = CreateDirectory(path.c_str(), NULL);
  95. if(r == true)
  96. return true;
  97. if(GetLastError() == ERROR_ALREADY_EXISTS)
  98. return true;
  99. return false;
  100. }
  101. bool PathExists(const std::string &path)
  102. {
  103. return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES);
  104. }
  105. bool IsPathAbsolute(const std::string &path)
  106. {
  107. return !PathIsRelative(path.c_str());
  108. }
  109. bool IsDir(const std::string &path)
  110. {
  111. DWORD attr = GetFileAttributes(path.c_str());
  112. return (attr != INVALID_FILE_ATTRIBUTES &&
  113. (attr & FILE_ATTRIBUTE_DIRECTORY));
  114. }
  115. bool IsExecutable(const std::string &path)
  116. {
  117. DWORD type;
  118. return GetBinaryType(path.c_str(), &type) != 0;
  119. }
  120. bool IsDirDelimiter(char c)
  121. {
  122. return c == '/' || c == '\\';
  123. }
  124. bool RecursiveDelete(const std::string &path)
  125. {
  126. infostream << "Recursively deleting \"" << path << "\"" << std::endl;
  127. if (!IsDir(path)) {
  128. infostream << "RecursiveDelete: Deleting file " << path << std::endl;
  129. if (!DeleteFile(path.c_str())) {
  130. errorstream << "RecursiveDelete: Failed to delete file "
  131. << path << std::endl;
  132. return false;
  133. }
  134. return true;
  135. }
  136. infostream << "RecursiveDelete: Deleting content of directory "
  137. << path << std::endl;
  138. std::vector<DirListNode> content = GetDirListing(path);
  139. for (const DirListNode &n: content) {
  140. std::string fullpath = path + DIR_DELIM + n.name;
  141. if (!RecursiveDelete(fullpath)) {
  142. errorstream << "RecursiveDelete: Failed to recurse to "
  143. << fullpath << std::endl;
  144. return false;
  145. }
  146. }
  147. infostream << "RecursiveDelete: Deleting directory " << path << std::endl;
  148. if (!RemoveDirectory(path.c_str())) {
  149. errorstream << "Failed to recursively delete directory "
  150. << path << std::endl;
  151. return false;
  152. }
  153. return true;
  154. }
  155. bool DeleteSingleFileOrEmptyDirectory(const std::string &path)
  156. {
  157. DWORD attr = GetFileAttributes(path.c_str());
  158. bool is_directory = (attr != INVALID_FILE_ATTRIBUTES &&
  159. (attr & FILE_ATTRIBUTE_DIRECTORY));
  160. if(!is_directory)
  161. {
  162. bool did = DeleteFile(path.c_str());
  163. return did;
  164. }
  165. else
  166. {
  167. bool did = RemoveDirectory(path.c_str());
  168. return did;
  169. }
  170. }
  171. std::string TempPath()
  172. {
  173. DWORD bufsize = GetTempPath(0, NULL);
  174. if(bufsize == 0){
  175. errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
  176. return "";
  177. }
  178. std::string buf;
  179. buf.resize(bufsize);
  180. DWORD len = GetTempPath(bufsize, &buf[0]);
  181. if(len == 0 || len > bufsize){
  182. errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
  183. return "";
  184. }
  185. buf.resize(len);
  186. return buf;
  187. }
  188. std::string CreateTempFile()
  189. {
  190. std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
  191. _mktemp_s(&path[0], path.size() + 1); // modifies path
  192. HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr,
  193. CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
  194. if (file == INVALID_HANDLE_VALUE)
  195. return "";
  196. CloseHandle(file);
  197. return path;
  198. }
  199. std::string CreateTempDir()
  200. {
  201. std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
  202. _mktemp_s(&path[0], path.size() + 1); // modifies path
  203. // will error if it already exists
  204. if (!CreateDirectory(path.c_str(), nullptr))
  205. return "";
  206. return path;
  207. }
  208. bool CopyFileContents(const std::string &source, const std::string &target)
  209. {
  210. BOOL ok = CopyFileEx(source.c_str(), target.c_str(), nullptr, nullptr,
  211. nullptr, COPY_FILE_ALLOW_DECRYPTED_DESTINATION);
  212. if (!ok) {
  213. errorstream << "copying " << source << " to " << target
  214. << " failed: " << GetLastError() << std::endl;
  215. return false;
  216. }
  217. // docs: "File attributes for the existing file are copied to the new file."
  218. // This is not our intention so get rid of unwanted attributes:
  219. DWORD attr = GetFileAttributes(target.c_str());
  220. if (attr == INVALID_FILE_ATTRIBUTES) {
  221. errorstream << target << ": file disappeared after copy" << std::endl;
  222. return false;
  223. }
  224. attr &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN);
  225. SetFileAttributes(target.c_str(), attr);
  226. tracestream << "copied " << source << " to " << target
  227. << " using CopyFileEx" << std::endl;
  228. return true;
  229. }
  230. #else
  231. /*********
  232. * POSIX *
  233. *********/
  234. #include <sys/types.h>
  235. #include <dirent.h>
  236. #include <sys/stat.h>
  237. #include <sys/wait.h>
  238. #include <unistd.h>
  239. std::vector<DirListNode> GetDirListing(const std::string &pathstring)
  240. {
  241. std::vector<DirListNode> listing;
  242. DIR *dp;
  243. struct dirent *dirp;
  244. if((dp = opendir(pathstring.c_str())) == NULL) {
  245. //infostream<<"Error("<<errno<<") opening "<<pathstring<<std::endl;
  246. return listing;
  247. }
  248. while ((dirp = readdir(dp)) != NULL) {
  249. // NOTE:
  250. // Be very sure to not include '..' in the results, it will
  251. // result in an epic failure when deleting stuff.
  252. if(strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
  253. continue;
  254. DirListNode node;
  255. node.name = dirp->d_name;
  256. int isdir = -1; // -1 means unknown
  257. /*
  258. POSIX doesn't define d_type member of struct dirent and
  259. certain filesystems on glibc/Linux will only return
  260. DT_UNKNOWN for the d_type member.
  261. Also we don't know whether symlinks are directories or not.
  262. */
  263. #ifdef _DIRENT_HAVE_D_TYPE
  264. if(dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK)
  265. isdir = (dirp->d_type == DT_DIR);
  266. #endif /* _DIRENT_HAVE_D_TYPE */
  267. /*
  268. Was d_type DT_UNKNOWN, DT_LNK or nonexistent?
  269. If so, try stat().
  270. */
  271. if(isdir == -1) {
  272. struct stat statbuf{};
  273. if (stat((pathstring + "/" + node.name).c_str(), &statbuf))
  274. continue;
  275. isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
  276. }
  277. node.dir = isdir;
  278. listing.push_back(node);
  279. }
  280. closedir(dp);
  281. return listing;
  282. }
  283. bool CreateDir(const std::string &path)
  284. {
  285. int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
  286. if (r == 0) {
  287. return true;
  288. }
  289. // If already exists, return true
  290. if (errno == EEXIST)
  291. return true;
  292. return false;
  293. }
  294. bool PathExists(const std::string &path)
  295. {
  296. struct stat st{};
  297. return (stat(path.c_str(),&st) == 0);
  298. }
  299. bool IsPathAbsolute(const std::string &path)
  300. {
  301. return path[0] == '/';
  302. }
  303. bool IsDir(const std::string &path)
  304. {
  305. struct stat statbuf{};
  306. if(stat(path.c_str(), &statbuf))
  307. return false; // Actually error; but certainly not a directory
  308. return ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
  309. }
  310. bool IsExecutable(const std::string &path)
  311. {
  312. return access(path.c_str(), X_OK) == 0;
  313. }
  314. bool IsDirDelimiter(char c)
  315. {
  316. return c == '/';
  317. }
  318. bool RecursiveDelete(const std::string &path)
  319. {
  320. /*
  321. Execute the 'rm' command directly, by fork() and execve()
  322. */
  323. infostream<<"Removing \""<<path<<"\""<<std::endl;
  324. pid_t child_pid = fork();
  325. if(child_pid == 0)
  326. {
  327. // Child
  328. const char *argv[4] = {
  329. #ifdef __ANDROID__
  330. "/system/bin/rm",
  331. #else
  332. "/bin/rm",
  333. #endif
  334. "-rf",
  335. path.c_str(),
  336. NULL
  337. };
  338. verbosestream<<"Executing '"<<argv[0]<<"' '"<<argv[1]<<"' '"
  339. <<argv[2]<<"'"<<std::endl;
  340. execv(argv[0], const_cast<char**>(argv));
  341. // Execv shouldn't return. Failed.
  342. _exit(1);
  343. }
  344. else
  345. {
  346. // Parent
  347. int child_status;
  348. pid_t tpid;
  349. do{
  350. tpid = wait(&child_status);
  351. }while(tpid != child_pid);
  352. return (child_status == 0);
  353. }
  354. }
  355. bool DeleteSingleFileOrEmptyDirectory(const std::string &path)
  356. {
  357. if (IsDir(path)) {
  358. bool did = (rmdir(path.c_str()) == 0);
  359. if (!did)
  360. errorstream << "rmdir errno: " << errno << ": " << strerror(errno)
  361. << std::endl;
  362. return did;
  363. }
  364. bool did = (unlink(path.c_str()) == 0);
  365. if (!did)
  366. errorstream << "unlink errno: " << errno << ": " << strerror(errno)
  367. << std::endl;
  368. return did;
  369. }
  370. std::string TempPath()
  371. {
  372. /*
  373. Should the environment variables TMPDIR, TMP and TEMP
  374. and the macro P_tmpdir (if defined by stdio.h) be checked
  375. before falling back on /tmp?
  376. Probably not, because this function is intended to be
  377. compatible with lua's os.tmpname which under the default
  378. configuration hardcodes mkstemp("/tmp/lua_XXXXXX").
  379. */
  380. #ifdef __ANDROID__
  381. return porting::path_cache;
  382. #else
  383. return DIR_DELIM "tmp";
  384. #endif
  385. }
  386. std::string CreateTempFile()
  387. {
  388. std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
  389. int fd = mkstemp(&path[0]); // modifies path
  390. if (fd == -1)
  391. return "";
  392. close(fd);
  393. return path;
  394. }
  395. std::string CreateTempDir()
  396. {
  397. std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
  398. auto r = mkdtemp(&path[0]); // modifies path
  399. if (!r)
  400. return "";
  401. return path;
  402. }
  403. namespace {
  404. struct FileDeleter {
  405. void operator()(FILE *stream) {
  406. fclose(stream);
  407. }
  408. };
  409. typedef std::unique_ptr<FILE, FileDeleter> FileUniquePtr;
  410. }
  411. bool CopyFileContents(const std::string &source, const std::string &target)
  412. {
  413. FileUniquePtr sourcefile, targetfile;
  414. #ifdef __linux__
  415. // Try to clone using Copy-on-Write (CoW). This is instant but supported
  416. // only by some filesystems.
  417. int srcfd, tgtfd;
  418. srcfd = open(source.c_str(), O_RDONLY);
  419. if (srcfd == -1) {
  420. errorstream << source << ": can't open for reading: "
  421. << strerror(errno) << std::endl;
  422. return false;
  423. }
  424. tgtfd = open(target.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
  425. if (tgtfd == -1) {
  426. errorstream << target << ": can't open for writing: "
  427. << strerror(errno) << std::endl;
  428. close(srcfd);
  429. return false;
  430. }
  431. if (ioctl(tgtfd, FICLONE, srcfd) == 0) {
  432. tracestream << "copied " << source << " to " << target
  433. << " using FICLONE" << std::endl;
  434. close(srcfd);
  435. close(tgtfd);
  436. return true;
  437. }
  438. // fallback to normal copy, but no need to reopen the files
  439. sourcefile.reset(fdopen(srcfd, "rb"));
  440. targetfile.reset(fdopen(tgtfd, "wb"));
  441. goto fallback;
  442. #endif
  443. sourcefile.reset(fopen(source.c_str(), "rb"));
  444. targetfile.reset(fopen(target.c_str(), "wb"));
  445. fallback:
  446. if (!sourcefile) {
  447. errorstream << source << ": can't open for reading: "
  448. << strerror(errno) << std::endl;
  449. return false;
  450. }
  451. if (!targetfile) {
  452. errorstream << target << ": can't open for writing: "
  453. << strerror(errno) << std::endl;
  454. return false;
  455. }
  456. size_t total = 0;
  457. bool done = false;
  458. char readbuffer[BUFSIZ];
  459. while (!done) {
  460. size_t readbytes = fread(readbuffer, 1,
  461. sizeof(readbuffer), sourcefile.get());
  462. total += readbytes;
  463. if (ferror(sourcefile.get())) {
  464. errorstream << source << ": IO error: "
  465. << strerror(errno) << std::endl;
  466. return false;
  467. }
  468. if (readbytes > 0)
  469. fwrite(readbuffer, 1, readbytes, targetfile.get());
  470. if (feof(sourcefile.get())) {
  471. // flush destination file to catch write errors (e.g. disk full)
  472. fflush(targetfile.get());
  473. done = true;
  474. }
  475. if (ferror(targetfile.get())) {
  476. errorstream << target << ": IO error: "
  477. << strerror(errno) << std::endl;
  478. return false;
  479. }
  480. }
  481. tracestream << "copied " << total << " bytes from "
  482. << source << " to " << target << std::endl;
  483. return true;
  484. }
  485. #endif
  486. /****************************
  487. * portable implementations *
  488. ****************************/
  489. void GetRecursiveDirs(std::vector<std::string> &dirs, const std::string &dir)
  490. {
  491. static const std::set<char> chars_to_ignore = { '_', '.' };
  492. if (dir.empty() || !IsDir(dir))
  493. return;
  494. dirs.push_back(dir);
  495. fs::GetRecursiveSubPaths(dir, dirs, false, chars_to_ignore);
  496. }
  497. std::vector<std::string> GetRecursiveDirs(const std::string &dir)
  498. {
  499. std::vector<std::string> result;
  500. GetRecursiveDirs(result, dir);
  501. return result;
  502. }
  503. void GetRecursiveSubPaths(const std::string &path,
  504. std::vector<std::string> &dst,
  505. bool list_files,
  506. const std::set<char> &ignore)
  507. {
  508. std::vector<DirListNode> content = GetDirListing(path);
  509. for (const auto &n : content) {
  510. std::string fullpath = path + DIR_DELIM + n.name;
  511. if (ignore.count(n.name[0]))
  512. continue;
  513. if (list_files || n.dir)
  514. dst.push_back(fullpath);
  515. if (n.dir)
  516. GetRecursiveSubPaths(fullpath, dst, list_files, ignore);
  517. }
  518. }
  519. bool RecursiveDeleteContent(const std::string &path)
  520. {
  521. infostream<<"Removing content of \""<<path<<"\""<<std::endl;
  522. std::vector<DirListNode> list = GetDirListing(path);
  523. for (const DirListNode &dln : list) {
  524. if(trim(dln.name) == "." || trim(dln.name) == "..")
  525. continue;
  526. std::string childpath = path + DIR_DELIM + dln.name;
  527. bool r = RecursiveDelete(childpath);
  528. if(!r) {
  529. errorstream << "Removing \"" << childpath << "\" failed" << std::endl;
  530. return false;
  531. }
  532. }
  533. return true;
  534. }
  535. bool CreateAllDirs(const std::string &path)
  536. {
  537. std::vector<std::string> tocreate;
  538. std::string basepath = path;
  539. while(!PathExists(basepath))
  540. {
  541. tocreate.push_back(basepath);
  542. basepath = RemoveLastPathComponent(basepath);
  543. if(basepath.empty())
  544. break;
  545. }
  546. for(int i=tocreate.size()-1;i>=0;i--)
  547. if(!CreateDir(tocreate[i]))
  548. return false;
  549. return true;
  550. }
  551. bool CopyDir(const std::string &source, const std::string &target)
  552. {
  553. if(PathExists(source)){
  554. if(!PathExists(target)){
  555. fs::CreateAllDirs(target);
  556. }
  557. bool retval = true;
  558. std::vector<DirListNode> content = fs::GetDirListing(source);
  559. for (const auto &dln : content) {
  560. std::string sourcechild = source + DIR_DELIM + dln.name;
  561. std::string targetchild = target + DIR_DELIM + dln.name;
  562. if(dln.dir){
  563. if(!fs::CopyDir(sourcechild, targetchild)){
  564. retval = false;
  565. }
  566. }
  567. else {
  568. if(!fs::CopyFileContents(sourcechild, targetchild)){
  569. retval = false;
  570. }
  571. }
  572. }
  573. return retval;
  574. }
  575. return false;
  576. }
  577. bool MoveDir(const std::string &source, const std::string &target)
  578. {
  579. infostream << "Moving \"" << source << "\" to \"" << target << "\"" << std::endl;
  580. // If target exists as empty folder delete, otherwise error
  581. if (fs::PathExists(target)) {
  582. if (rmdir(target.c_str()) != 0) {
  583. errorstream << "MoveDir: target \"" << target
  584. << "\" exists as file or non-empty folder" << std::endl;
  585. return false;
  586. }
  587. }
  588. // Try renaming first which is instant
  589. if (fs::Rename(source, target))
  590. return true;
  591. infostream << "MoveDir: rename not possible, will copy instead" << std::endl;
  592. bool retval = fs::CopyDir(source, target);
  593. if (retval)
  594. retval &= fs::RecursiveDelete(source);
  595. return retval;
  596. }
  597. bool PathStartsWith(const std::string &path, const std::string &prefix)
  598. {
  599. size_t pathsize = path.size();
  600. size_t pathpos = 0;
  601. size_t prefixsize = prefix.size();
  602. size_t prefixpos = 0;
  603. for(;;){
  604. bool delim1 = pathpos == pathsize
  605. || IsDirDelimiter(path[pathpos]);
  606. bool delim2 = prefixpos == prefixsize
  607. || IsDirDelimiter(prefix[prefixpos]);
  608. if(delim1 != delim2)
  609. return false;
  610. if(delim1){
  611. while(pathpos < pathsize &&
  612. IsDirDelimiter(path[pathpos]))
  613. ++pathpos;
  614. while(prefixpos < prefixsize &&
  615. IsDirDelimiter(prefix[prefixpos]))
  616. ++prefixpos;
  617. if(prefixpos == prefixsize)
  618. return true;
  619. if(pathpos == pathsize)
  620. return false;
  621. }
  622. else{
  623. size_t len = 0;
  624. do{
  625. char pathchar = path[pathpos+len];
  626. char prefixchar = prefix[prefixpos+len];
  627. if(FILESYS_CASE_INSENSITIVE){
  628. pathchar = my_tolower(pathchar);
  629. prefixchar = my_tolower(prefixchar);
  630. }
  631. if(pathchar != prefixchar)
  632. return false;
  633. ++len;
  634. } while(pathpos+len < pathsize
  635. && !IsDirDelimiter(path[pathpos+len])
  636. && prefixpos+len < prefixsize
  637. && !IsDirDelimiter(
  638. prefix[prefixpos+len]));
  639. pathpos += len;
  640. prefixpos += len;
  641. }
  642. }
  643. }
  644. std::string RemoveLastPathComponent(const std::string &path,
  645. std::string *removed, int count)
  646. {
  647. if(removed)
  648. removed->clear();
  649. size_t remaining = path.size();
  650. for(int i = 0; i < count; ++i){
  651. // strip a dir delimiter
  652. while(remaining != 0 && IsDirDelimiter(path[remaining-1]))
  653. remaining--;
  654. // strip a path component
  655. size_t component_end = remaining;
  656. while(remaining != 0 && !IsDirDelimiter(path[remaining-1]))
  657. remaining--;
  658. size_t component_start = remaining;
  659. // strip a dir delimiter
  660. while(remaining != 0 && IsDirDelimiter(path[remaining-1]))
  661. remaining--;
  662. if(removed){
  663. std::string component = path.substr(component_start,
  664. component_end - component_start);
  665. if(i)
  666. *removed = component + DIR_DELIM + *removed;
  667. else
  668. *removed = component;
  669. }
  670. }
  671. return path.substr(0, remaining);
  672. }
  673. std::string RemoveRelativePathComponents(std::string path)
  674. {
  675. size_t pos = path.size();
  676. size_t dotdot_count = 0;
  677. while (pos != 0) {
  678. size_t component_with_delim_end = pos;
  679. // skip a dir delimiter
  680. while (pos != 0 && IsDirDelimiter(path[pos-1]))
  681. pos--;
  682. // strip a path component
  683. size_t component_end = pos;
  684. while (pos != 0 && !IsDirDelimiter(path[pos-1]))
  685. pos--;
  686. size_t component_start = pos;
  687. std::string component = path.substr(component_start,
  688. component_end - component_start);
  689. bool remove_this_component = false;
  690. if (component == ".") {
  691. remove_this_component = true;
  692. } else if (component == "..") {
  693. remove_this_component = true;
  694. dotdot_count += 1;
  695. } else if (dotdot_count != 0) {
  696. remove_this_component = true;
  697. dotdot_count -= 1;
  698. }
  699. if (remove_this_component) {
  700. while (pos != 0 && IsDirDelimiter(path[pos-1]))
  701. pos--;
  702. if (component_start == 0) {
  703. // We need to remove the delemiter too
  704. path = path.substr(component_with_delim_end, std::string::npos);
  705. } else {
  706. path = path.substr(0, pos) + DIR_DELIM +
  707. path.substr(component_with_delim_end, std::string::npos);
  708. }
  709. if (pos > 0)
  710. pos++;
  711. }
  712. }
  713. if (dotdot_count > 0)
  714. return "";
  715. // remove trailing dir delimiters
  716. pos = path.size();
  717. while (pos != 0 && IsDirDelimiter(path[pos-1]))
  718. pos--;
  719. return path.substr(0, pos);
  720. }
  721. std::string AbsolutePath(const std::string &path)
  722. {
  723. #ifdef _WIN32
  724. char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH);
  725. #else
  726. char *abs_path = realpath(path.c_str(), NULL);
  727. #endif
  728. if (!abs_path) return "";
  729. std::string abs_path_str(abs_path);
  730. free(abs_path);
  731. return abs_path_str;
  732. }
  733. const char *GetFilenameFromPath(const char *path)
  734. {
  735. const char *filename = strrchr(path, DIR_DELIM_CHAR);
  736. // Consistent with IsDirDelimiter this function handles '/' too
  737. if (DIR_DELIM_CHAR != '/') {
  738. const char *tmp = strrchr(path, '/');
  739. if (tmp && tmp > filename)
  740. filename = tmp;
  741. }
  742. return filename ? filename + 1 : path;
  743. }
  744. bool safeWriteToFile(const std::string &path, std::string_view content)
  745. {
  746. std::string tmp_file = path + ".~mt";
  747. // Write to a tmp file
  748. bool tmp_success = false;
  749. #ifdef _WIN32
  750. // We've observed behavior suggesting that the MSVC implementation of std::ofstream::flush doesn't
  751. // actually flush, so we use win32 APIs.
  752. HANDLE tmp_handle = CreateFile(
  753. tmp_file.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
  754. if (tmp_handle == INVALID_HANDLE_VALUE) {
  755. return false;
  756. }
  757. DWORD bytes_written;
  758. tmp_success = (WriteFile(tmp_handle, content.data(), content.size(), &bytes_written, nullptr) &&
  759. FlushFileBuffers(tmp_handle));
  760. CloseHandle(tmp_handle);
  761. #else
  762. std::ofstream os(tmp_file.c_str(), std::ios::binary);
  763. if (!os.good()) {
  764. return false;
  765. }
  766. os << content;
  767. os.flush();
  768. os.close();
  769. tmp_success = !os.fail();
  770. #endif
  771. if (!tmp_success) {
  772. remove(tmp_file.c_str());
  773. return false;
  774. }
  775. bool rename_success = false;
  776. // Move the finished temporary file over the real file
  777. #ifdef _WIN32
  778. // When creating the file, it can cause Windows Search indexer, virus scanners and other apps
  779. // to query the file. This can make the move file call below fail.
  780. // We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed
  781. for (int attempt = 0; attempt < 5; attempt++) {
  782. rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(),
  783. MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
  784. if (rename_success)
  785. break;
  786. sleep_ms(1);
  787. }
  788. #else
  789. // On POSIX compliant systems rename() is specified to be able to swap the
  790. // file in place of the destination file, making this a truly error-proof
  791. // transaction.
  792. rename_success = rename(tmp_file.c_str(), path.c_str()) == 0;
  793. #endif
  794. if (!rename_success) {
  795. warningstream << "Failed to write to file: " << path.c_str() << std::endl;
  796. // Remove the temporary file because moving it over the target file
  797. // failed.
  798. remove(tmp_file.c_str());
  799. return false;
  800. }
  801. return true;
  802. }
  803. #ifndef SERVER
  804. bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string &destination)
  805. {
  806. // Be careful here not to touch the global file hierarchy in Irrlicht
  807. // since this function needs to be thread-safe!
  808. io::IArchiveLoader *zip_loader = nullptr;
  809. for (u32 i = 0; i < fs->getArchiveLoaderCount(); i++) {
  810. if (fs->getArchiveLoader(i)->isALoadableFileFormat(io::EFAT_ZIP)) {
  811. zip_loader = fs->getArchiveLoader(i);
  812. break;
  813. }
  814. }
  815. if (!zip_loader) {
  816. warningstream << "fs::extractZipFile(): Irrlicht said it doesn't support ZIPs." << std::endl;
  817. return false;
  818. }
  819. irr_ptr<io::IFileArchive> opened_zip(zip_loader->createArchive(filename, false, false));
  820. const io::IFileList* files_in_zip = opened_zip->getFileList();
  821. for (u32 i = 0; i < files_in_zip->getFileCount(); i++) {
  822. std::string fullpath = destination + DIR_DELIM;
  823. fullpath += files_in_zip->getFullFileName(i).c_str();
  824. std::string fullpath_dir = fs::RemoveLastPathComponent(fullpath);
  825. if (files_in_zip->isDirectory(i))
  826. continue; // ignore, we create dirs as necessary
  827. if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir))
  828. return false;
  829. irr_ptr<io::IReadFile> toread(opened_zip->createAndOpenFile(i));
  830. std::ofstream os(fullpath.c_str(), std::ios::binary);
  831. if (!os.good())
  832. return false;
  833. char buffer[4096];
  834. long total_read = 0;
  835. while (total_read < toread->getSize()) {
  836. long bytes_read = toread->read(buffer, sizeof(buffer));
  837. bool error = true;
  838. if (bytes_read != 0) {
  839. os.write(buffer, bytes_read);
  840. error = os.fail();
  841. }
  842. if (error) {
  843. os.close();
  844. remove(fullpath.c_str());
  845. return false;
  846. }
  847. total_read += bytes_read;
  848. }
  849. }
  850. return true;
  851. }
  852. #endif
  853. bool ReadFile(const std::string &path, std::string &out)
  854. {
  855. std::ifstream is(path, std::ios::binary | std::ios::ate);
  856. if (!is.good()) {
  857. return false;
  858. }
  859. auto size = is.tellg();
  860. out.resize(size);
  861. is.seekg(0);
  862. is.read(&out[0], size);
  863. return !is.fail();
  864. }
  865. bool Rename(const std::string &from, const std::string &to)
  866. {
  867. return rename(from.c_str(), to.c_str()) == 0;
  868. }
  869. } // namespace fs