filesys.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  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 <stdio.h>
  20. #include <string.h>
  21. #include <errno.h>
  22. #include <fstream>
  23. #include "log.h"
  24. #include "config.h"
  25. namespace fs
  26. {
  27. #ifdef _WIN32 // WINDOWS
  28. #define _WIN32_WINNT 0x0501
  29. #include <windows.h>
  30. #include <malloc.h>
  31. #include <tchar.h>
  32. #include <wchar.h>
  33. #define BUFSIZE MAX_PATH
  34. std::vector<DirListNode> GetDirListing(std::string pathstring)
  35. {
  36. std::vector<DirListNode> listing;
  37. WIN32_FIND_DATA FindFileData;
  38. HANDLE hFind = INVALID_HANDLE_VALUE;
  39. DWORD dwError;
  40. LPTSTR DirSpec;
  41. INT retval;
  42. DirSpec = (LPTSTR) malloc (BUFSIZE);
  43. if( DirSpec == NULL )
  44. {
  45. errorstream<<"GetDirListing: Insufficient memory available"<<std::endl;
  46. retval = 1;
  47. goto Cleanup;
  48. }
  49. // Check that the input is not larger than allowed.
  50. if (pathstring.size() > (BUFSIZE - 2))
  51. {
  52. errorstream<<"GetDirListing: Input directory is too large."<<std::endl;
  53. retval = 3;
  54. goto Cleanup;
  55. }
  56. //_tprintf (TEXT("Target directory is %s.\n"), pathstring.c_str());
  57. sprintf(DirSpec, "%s", (pathstring + "\\*").c_str());
  58. // Find the first file in the directory.
  59. hFind = FindFirstFile(DirSpec, &FindFileData);
  60. if (hFind == INVALID_HANDLE_VALUE)
  61. {
  62. retval = (-1);
  63. goto Cleanup;
  64. }
  65. else
  66. {
  67. // NOTE:
  68. // Be very sure to not include '..' in the results, it will
  69. // result in an epic failure when deleting stuff.
  70. DirListNode node;
  71. node.name = FindFileData.cFileName;
  72. node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
  73. if(node.name != "." && node.name != "..")
  74. listing.push_back(node);
  75. // List all the other files in the directory.
  76. while (FindNextFile(hFind, &FindFileData) != 0)
  77. {
  78. DirListNode node;
  79. node.name = FindFileData.cFileName;
  80. node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
  81. if(node.name != "." && node.name != "..")
  82. listing.push_back(node);
  83. }
  84. dwError = GetLastError();
  85. FindClose(hFind);
  86. if (dwError != ERROR_NO_MORE_FILES)
  87. {
  88. errorstream<<"GetDirListing: FindNextFile error. Error is "
  89. <<dwError<<std::endl;
  90. retval = (-1);
  91. goto Cleanup;
  92. }
  93. }
  94. retval = 0;
  95. Cleanup:
  96. free(DirSpec);
  97. if(retval != 0) listing.clear();
  98. //for(unsigned int i=0; i<listing.size(); i++){
  99. // infostream<<listing[i].name<<(listing[i].dir?" (dir)":" (file)")<<std::endl;
  100. //}
  101. return listing;
  102. }
  103. bool CreateDir(std::string path)
  104. {
  105. bool r = CreateDirectory(path.c_str(), NULL);
  106. if(r == true)
  107. return true;
  108. if(GetLastError() == ERROR_ALREADY_EXISTS)
  109. return true;
  110. return false;
  111. }
  112. bool PathExists(std::string path)
  113. {
  114. return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES);
  115. }
  116. bool IsDir(std::string path)
  117. {
  118. DWORD attr = GetFileAttributes(path.c_str());
  119. return (attr != INVALID_FILE_ATTRIBUTES &&
  120. (attr & FILE_ATTRIBUTE_DIRECTORY));
  121. }
  122. bool IsDirDelimiter(char c)
  123. {
  124. return c == '/' || c == '\\';
  125. }
  126. bool RecursiveDelete(std::string path)
  127. {
  128. infostream<<"Recursively deleting \""<<path<<"\""<<std::endl;
  129. DWORD attr = GetFileAttributes(path.c_str());
  130. bool is_directory = (attr != INVALID_FILE_ATTRIBUTES &&
  131. (attr & FILE_ATTRIBUTE_DIRECTORY));
  132. if(!is_directory)
  133. {
  134. infostream<<"RecursiveDelete: Deleting file "<<path<<std::endl;
  135. //bool did = DeleteFile(path.c_str());
  136. bool did = true;
  137. if(!did){
  138. errorstream<<"RecursiveDelete: Failed to delete file "
  139. <<path<<std::endl;
  140. return false;
  141. }
  142. }
  143. else
  144. {
  145. infostream<<"RecursiveDelete: Deleting content of directory "
  146. <<path<<std::endl;
  147. std::vector<DirListNode> content = GetDirListing(path);
  148. for(int i=0; i<content.size(); i++){
  149. const DirListNode &n = content[i];
  150. std::string fullpath = path + DIR_DELIM + n.name;
  151. bool did = RecursiveDelete(fullpath);
  152. if(!did){
  153. errorstream<<"RecursiveDelete: Failed to recurse to "
  154. <<fullpath<<std::endl;
  155. return false;
  156. }
  157. }
  158. infostream<<"RecursiveDelete: Deleting directory "<<path<<std::endl;
  159. //bool did = RemoveDirectory(path.c_str();
  160. bool did = true;
  161. if(!did){
  162. errorstream<<"Failed to recursively delete directory "
  163. <<path<<std::endl;
  164. return false;
  165. }
  166. }
  167. return true;
  168. }
  169. bool DeleteSingleFileOrEmptyDirectory(std::string path)
  170. {
  171. DWORD attr = GetFileAttributes(path.c_str());
  172. bool is_directory = (attr != INVALID_FILE_ATTRIBUTES &&
  173. (attr & FILE_ATTRIBUTE_DIRECTORY));
  174. if(!is_directory)
  175. {
  176. bool did = DeleteFile(path.c_str());
  177. return did;
  178. }
  179. else
  180. {
  181. bool did = RemoveDirectory(path.c_str());
  182. return did;
  183. }
  184. }
  185. std::string TempPath()
  186. {
  187. DWORD bufsize = GetTempPath(0, "");
  188. if(bufsize == 0){
  189. errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
  190. return "";
  191. }
  192. std::vector<char> buf(bufsize);
  193. DWORD len = GetTempPath(bufsize, &buf[0]);
  194. if(len == 0 || len > bufsize){
  195. errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
  196. return "";
  197. }
  198. return std::string(buf.begin(), buf.begin() + len);
  199. }
  200. #else // POSIX
  201. #include <sys/types.h>
  202. #include <dirent.h>
  203. #include <sys/stat.h>
  204. #include <sys/wait.h>
  205. #include <unistd.h>
  206. std::vector<DirListNode> GetDirListing(std::string pathstring)
  207. {
  208. std::vector<DirListNode> listing;
  209. DIR *dp;
  210. struct dirent *dirp;
  211. if((dp = opendir(pathstring.c_str())) == NULL) {
  212. //infostream<<"Error("<<errno<<") opening "<<pathstring<<std::endl;
  213. return listing;
  214. }
  215. while ((dirp = readdir(dp)) != NULL) {
  216. // NOTE:
  217. // Be very sure to not include '..' in the results, it will
  218. // result in an epic failure when deleting stuff.
  219. if(dirp->d_name[0]!='.'){
  220. DirListNode node;
  221. node.name = dirp->d_name;
  222. if(node.name == "." || node.name == "..")
  223. continue;
  224. int isdir = -1; // -1 means unknown
  225. /*
  226. POSIX doesn't define d_type member of struct dirent and
  227. certain filesystems on glibc/Linux will only return
  228. DT_UNKNOWN for the d_type member.
  229. Also we don't know whether symlinks are directories or not.
  230. */
  231. #ifdef _DIRENT_HAVE_D_TYPE
  232. if(dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK)
  233. isdir = (dirp->d_type == DT_DIR);
  234. #endif /* _DIRENT_HAVE_D_TYPE */
  235. /*
  236. Was d_type DT_UNKNOWN, DT_LNK or nonexistent?
  237. If so, try stat().
  238. */
  239. if(isdir == -1)
  240. {
  241. struct stat statbuf;
  242. if (stat((pathstring + "/" + node.name).c_str(), &statbuf))
  243. continue;
  244. isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
  245. }
  246. node.dir = isdir;
  247. listing.push_back(node);
  248. }
  249. }
  250. closedir(dp);
  251. return listing;
  252. }
  253. bool CreateDir(std::string path)
  254. {
  255. int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
  256. if(r == 0)
  257. {
  258. return true;
  259. }
  260. else
  261. {
  262. // If already exists, return true
  263. if(errno == EEXIST)
  264. return true;
  265. return false;
  266. }
  267. }
  268. bool PathExists(std::string path)
  269. {
  270. struct stat st;
  271. return (stat(path.c_str(),&st) == 0);
  272. }
  273. bool IsDir(std::string path)
  274. {
  275. struct stat statbuf;
  276. if(stat(path.c_str(), &statbuf))
  277. return false; // Actually error; but certainly not a directory
  278. return ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
  279. }
  280. bool IsDirDelimiter(char c)
  281. {
  282. return c == '/';
  283. }
  284. bool RecursiveDelete(std::string path)
  285. {
  286. /*
  287. Execute the 'rm' command directly, by fork() and execve()
  288. */
  289. infostream<<"Removing \""<<path<<"\""<<std::endl;
  290. //return false;
  291. pid_t child_pid = fork();
  292. if(child_pid == 0)
  293. {
  294. // Child
  295. char argv_data[3][10000];
  296. strcpy(argv_data[0], "/bin/rm");
  297. strcpy(argv_data[1], "-rf");
  298. strncpy(argv_data[2], path.c_str(), 10000);
  299. char *argv[4];
  300. argv[0] = argv_data[0];
  301. argv[1] = argv_data[1];
  302. argv[2] = argv_data[2];
  303. argv[3] = NULL;
  304. verbosestream<<"Executing '"<<argv[0]<<"' '"<<argv[1]<<"' '"
  305. <<argv[2]<<"'"<<std::endl;
  306. execv(argv[0], argv);
  307. // Execv shouldn't return. Failed.
  308. _exit(1);
  309. }
  310. else
  311. {
  312. // Parent
  313. int child_status;
  314. pid_t tpid;
  315. do{
  316. tpid = wait(&child_status);
  317. //if(tpid != child_pid) process_terminated(tpid);
  318. }while(tpid != child_pid);
  319. return (child_status == 0);
  320. }
  321. }
  322. bool DeleteSingleFileOrEmptyDirectory(std::string path)
  323. {
  324. if(IsDir(path)){
  325. bool did = (rmdir(path.c_str()) == 0);
  326. if(!did)
  327. errorstream<<"rmdir errno: "<<errno<<": "<<strerror(errno)
  328. <<std::endl;
  329. return did;
  330. } else {
  331. bool did = (unlink(path.c_str()) == 0);
  332. if(!did)
  333. errorstream<<"unlink errno: "<<errno<<": "<<strerror(errno)
  334. <<std::endl;
  335. return did;
  336. }
  337. }
  338. std::string TempPath()
  339. {
  340. /*
  341. Should the environment variables TMPDIR, TMP and TEMP
  342. and the macro P_tmpdir (if defined by stdio.h) be checked
  343. before falling back on /tmp?
  344. Probably not, because this function is intended to be
  345. compatible with lua's os.tmpname which under the default
  346. configuration hardcodes mkstemp("/tmp/lua_XXXXXX").
  347. */
  348. #ifdef __ANDROID__
  349. return DIR_DELIM "sdcard" DIR_DELIM PROJECT_NAME DIR_DELIM "tmp";
  350. #else
  351. return DIR_DELIM "tmp";
  352. #endif
  353. }
  354. #endif
  355. void GetRecursiveSubPaths(std::string path, std::vector<std::string> &dst)
  356. {
  357. std::vector<DirListNode> content = GetDirListing(path);
  358. for(unsigned int i=0; i<content.size(); i++){
  359. const DirListNode &n = content[i];
  360. std::string fullpath = path + DIR_DELIM + n.name;
  361. dst.push_back(fullpath);
  362. GetRecursiveSubPaths(fullpath, dst);
  363. }
  364. }
  365. bool DeletePaths(const std::vector<std::string> &paths)
  366. {
  367. bool success = true;
  368. // Go backwards to succesfully delete the output of GetRecursiveSubPaths
  369. for(int i=paths.size()-1; i>=0; i--){
  370. const std::string &path = paths[i];
  371. bool did = DeleteSingleFileOrEmptyDirectory(path);
  372. if(!did){
  373. errorstream<<"Failed to delete "<<path<<std::endl;
  374. success = false;
  375. }
  376. }
  377. return success;
  378. }
  379. bool RecursiveDeleteContent(std::string path)
  380. {
  381. infostream<<"Removing content of \""<<path<<"\""<<std::endl;
  382. std::vector<DirListNode> list = GetDirListing(path);
  383. for(unsigned int i=0; i<list.size(); i++)
  384. {
  385. if(trim(list[i].name) == "." || trim(list[i].name) == "..")
  386. continue;
  387. std::string childpath = path + DIR_DELIM + list[i].name;
  388. bool r = RecursiveDelete(childpath);
  389. if(r == false)
  390. {
  391. errorstream<<"Removing \""<<childpath<<"\" failed"<<std::endl;
  392. return false;
  393. }
  394. }
  395. return true;
  396. }
  397. bool CreateAllDirs(std::string path)
  398. {
  399. std::vector<std::string> tocreate;
  400. std::string basepath = path;
  401. while(!PathExists(basepath))
  402. {
  403. tocreate.push_back(basepath);
  404. basepath = RemoveLastPathComponent(basepath);
  405. if(basepath.empty())
  406. break;
  407. }
  408. for(int i=tocreate.size()-1;i>=0;i--)
  409. if(!CreateDir(tocreate[i]))
  410. return false;
  411. return true;
  412. }
  413. bool CopyFileContents(std::string source, std::string target)
  414. {
  415. FILE *sourcefile = fopen(source.c_str(), "rb");
  416. if(sourcefile == NULL){
  417. errorstream<<source<<": can't open for reading: "
  418. <<strerror(errno)<<std::endl;
  419. return false;
  420. }
  421. FILE *targetfile = fopen(target.c_str(), "wb");
  422. if(targetfile == NULL){
  423. errorstream<<target<<": can't open for writing: "
  424. <<strerror(errno)<<std::endl;
  425. fclose(sourcefile);
  426. return false;
  427. }
  428. size_t total = 0;
  429. bool retval = true;
  430. bool done = false;
  431. char readbuffer[BUFSIZ];
  432. while(!done){
  433. size_t readbytes = fread(readbuffer, 1,
  434. sizeof(readbuffer), sourcefile);
  435. total += readbytes;
  436. if(ferror(sourcefile)){
  437. errorstream<<source<<": IO error: "
  438. <<strerror(errno)<<std::endl;
  439. retval = false;
  440. done = true;
  441. }
  442. if(readbytes > 0){
  443. fwrite(readbuffer, 1, readbytes, targetfile);
  444. }
  445. if(feof(sourcefile) || ferror(sourcefile)){
  446. // flush destination file to catch write errors
  447. // (e.g. disk full)
  448. fflush(targetfile);
  449. done = true;
  450. }
  451. if(ferror(targetfile)){
  452. errorstream<<target<<": IO error: "
  453. <<strerror(errno)<<std::endl;
  454. retval = false;
  455. done = true;
  456. }
  457. }
  458. infostream<<"copied "<<total<<" bytes from "
  459. <<source<<" to "<<target<<std::endl;
  460. fclose(sourcefile);
  461. fclose(targetfile);
  462. return retval;
  463. }
  464. bool CopyDir(std::string source, std::string target)
  465. {
  466. if(PathExists(source)){
  467. if(!PathExists(target)){
  468. fs::CreateAllDirs(target);
  469. }
  470. bool retval = true;
  471. std::vector<DirListNode> content = fs::GetDirListing(source);
  472. for(unsigned int i=0; i < content.size(); i++){
  473. std::string sourcechild = source + DIR_DELIM + content[i].name;
  474. std::string targetchild = target + DIR_DELIM + content[i].name;
  475. if(content[i].dir){
  476. if(!fs::CopyDir(sourcechild, targetchild)){
  477. retval = false;
  478. }
  479. }
  480. else {
  481. if(!fs::CopyFileContents(sourcechild, targetchild)){
  482. retval = false;
  483. }
  484. }
  485. }
  486. return retval;
  487. }
  488. else {
  489. return false;
  490. }
  491. }
  492. bool PathStartsWith(std::string path, std::string prefix)
  493. {
  494. size_t pathsize = path.size();
  495. size_t pathpos = 0;
  496. size_t prefixsize = prefix.size();
  497. size_t prefixpos = 0;
  498. for(;;){
  499. bool delim1 = pathpos == pathsize
  500. || IsDirDelimiter(path[pathpos]);
  501. bool delim2 = prefixpos == prefixsize
  502. || IsDirDelimiter(prefix[prefixpos]);
  503. if(delim1 != delim2)
  504. return false;
  505. if(delim1){
  506. while(pathpos < pathsize &&
  507. IsDirDelimiter(path[pathpos]))
  508. ++pathpos;
  509. while(prefixpos < prefixsize &&
  510. IsDirDelimiter(prefix[prefixpos]))
  511. ++prefixpos;
  512. if(prefixpos == prefixsize)
  513. return true;
  514. if(pathpos == pathsize)
  515. return false;
  516. }
  517. else{
  518. size_t len = 0;
  519. do{
  520. char pathchar = path[pathpos+len];
  521. char prefixchar = prefix[prefixpos+len];
  522. if(FILESYS_CASE_INSENSITIVE){
  523. pathchar = tolower(pathchar);
  524. prefixchar = tolower(prefixchar);
  525. }
  526. if(pathchar != prefixchar)
  527. return false;
  528. ++len;
  529. } while(pathpos+len < pathsize
  530. && !IsDirDelimiter(path[pathpos+len])
  531. && prefixpos+len < prefixsize
  532. && !IsDirDelimiter(
  533. prefix[prefixpos+len]));
  534. pathpos += len;
  535. prefixpos += len;
  536. }
  537. }
  538. }
  539. std::string RemoveLastPathComponent(std::string path,
  540. std::string *removed, int count)
  541. {
  542. if(removed)
  543. *removed = "";
  544. size_t remaining = path.size();
  545. for(int i = 0; i < count; ++i){
  546. // strip a dir delimiter
  547. while(remaining != 0 && IsDirDelimiter(path[remaining-1]))
  548. remaining--;
  549. // strip a path component
  550. size_t component_end = remaining;
  551. while(remaining != 0 && !IsDirDelimiter(path[remaining-1]))
  552. remaining--;
  553. size_t component_start = remaining;
  554. // strip a dir delimiter
  555. while(remaining != 0 && IsDirDelimiter(path[remaining-1]))
  556. remaining--;
  557. if(removed){
  558. std::string component = path.substr(component_start,
  559. component_end - component_start);
  560. if(i)
  561. *removed = component + DIR_DELIM + *removed;
  562. else
  563. *removed = component;
  564. }
  565. }
  566. return path.substr(0, remaining);
  567. }
  568. std::string RemoveRelativePathComponents(std::string path)
  569. {
  570. size_t pos = path.size();
  571. size_t dotdot_count = 0;
  572. while(pos != 0){
  573. size_t component_with_delim_end = pos;
  574. // skip a dir delimiter
  575. while(pos != 0 && IsDirDelimiter(path[pos-1]))
  576. pos--;
  577. // strip a path component
  578. size_t component_end = pos;
  579. while(pos != 0 && !IsDirDelimiter(path[pos-1]))
  580. pos--;
  581. size_t component_start = pos;
  582. std::string component = path.substr(component_start,
  583. component_end - component_start);
  584. bool remove_this_component = false;
  585. if(component == "."){
  586. remove_this_component = true;
  587. }
  588. else if(component == ".."){
  589. remove_this_component = true;
  590. dotdot_count += 1;
  591. }
  592. else if(dotdot_count != 0){
  593. remove_this_component = true;
  594. dotdot_count -= 1;
  595. }
  596. if(remove_this_component){
  597. while(pos != 0 && IsDirDelimiter(path[pos-1]))
  598. pos--;
  599. path = path.substr(0, pos) + DIR_DELIM +
  600. path.substr(component_with_delim_end,
  601. std::string::npos);
  602. pos++;
  603. }
  604. }
  605. if(dotdot_count > 0)
  606. return "";
  607. // remove trailing dir delimiters
  608. pos = path.size();
  609. while(pos != 0 && IsDirDelimiter(path[pos-1]))
  610. pos--;
  611. return path.substr(0, pos);
  612. }
  613. bool safeWriteToFile(const std::string &path, const std::string &content)
  614. {
  615. std::string tmp_file = path + ".~mt";
  616. // Write to a tmp file
  617. std::ofstream os(tmp_file.c_str(), std::ios::binary);
  618. if (!os.good())
  619. return false;
  620. os << content;
  621. os.flush();
  622. os.close();
  623. if (os.fail()) {
  624. remove(tmp_file.c_str());
  625. return false;
  626. }
  627. // Copy file
  628. remove(path.c_str());
  629. if(rename(tmp_file.c_str(), path.c_str())) {
  630. remove(tmp_file.c_str());
  631. return false;
  632. } else {
  633. return true;
  634. }
  635. }
  636. } // namespace fs