lldiriterator.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. /**
  2. * @file lldiriterator.cpp
  3. * @brief Implementation of directory iterator class
  4. *
  5. * $LicenseInfo:firstyear=2010&license=viewergpl$
  6. *
  7. * Copyright (c) 2010, Linden Research, Inc. (c) 2021 Henri Beauchamp.
  8. *
  9. * Modifications by Henri Beauchamp:
  10. * - Allow a simple iterator without matching pattern.
  11. * - Allow iterating on entries that do *not* match the given pattern.
  12. * - Allow to return sundry information for each found entry.
  13. * - Added LLDirIterator::deleteFilesInDir().
  14. * - Added LLDirIterator::deleteRecursivelyInDir().
  15. * - Proper catching of throw()s and boost::filesystem errors.
  16. * - Got rid of boost::regex in favour of std::regex since we now use C++11.
  17. * - Added support for iterating on logical drives (when passed an empty
  18. * path), under Windows.
  19. *
  20. * Second Life Viewer Source Code
  21. * The source code in this file ("Source Code") is provided by Linden Lab
  22. * to you under the terms of the GNU General Public License, version 2.0
  23. * ("GPL"), unless you have obtained a separate licensing agreement
  24. * ("Other License"), formally executed by you and Linden Lab. Terms of
  25. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  26. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  27. *
  28. * There are special exceptions to the terms and conditions of the GPL as
  29. * it is applied to this Source Code. View the full text of the exception
  30. * in the file doc/FLOSS-exception.txt in this software distribution, or
  31. * online at
  32. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  33. *
  34. * By copying, modifying or distributing this software, you acknowledge
  35. * that you have read and understood your obligations described above,
  36. * and agree to abide by those obligations.
  37. *
  38. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  39. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  40. * COMPLETENESS OR PERFORMANCE.
  41. * $/LicenseInfo$
  42. */
  43. #include "linden_common.h"
  44. #include <regex>
  45. #include "boost/filesystem.hpp"
  46. #include "lldiriterator.h"
  47. #include "llstring.h"
  48. using namespace boost::filesystem;
  49. ///////////////////////////////////////////////////////////////////////////////
  50. // LLDirIterator::Impl class
  51. // Hides all the gory details and saves from including verbose header files by
  52. // code using LLDirIterator...
  53. ///////////////////////////////////////////////////////////////////////////////
  54. class LLDirIterator::Impl
  55. {
  56. protected:
  57. LOG_CLASS(LLDirIterator::Impl);
  58. public:
  59. LL_INLINE Impl(const directory_iterator& dir_iter, U32 requested_info)
  60. : mIter(dir_iter),
  61. mRequestedInfo(requested_info),
  62. mGotFilter(false),
  63. mIsFile(false),
  64. mIsDirectory(false),
  65. mIsLink(false),
  66. mIsHidden(false),
  67. #if LL_WINDOWS
  68. mIsDriveIterator(false),
  69. mNextDrive(0),
  70. #endif
  71. mSize(0),
  72. mTimeStamp(0)
  73. {
  74. }
  75. LL_INLINE void setFilter(const std::regex& regexp)
  76. {
  77. mGotFilter = true;
  78. mFilterExp.assign(regexp);
  79. }
  80. #if LL_WINDOWS
  81. LL_INLINE void setDriveIterator()
  82. {
  83. mIsDriveIterator = true;
  84. }
  85. #endif
  86. bool next(std::string& name, bool not_matching);
  87. LL_INLINE bool isFile() const
  88. {
  89. checkRequestedInfo(DI_ISFILE);
  90. return mIsFile;
  91. }
  92. LL_INLINE bool isDirectory() const
  93. {
  94. checkRequestedInfo(DI_ISDIR);
  95. return mIsDirectory;
  96. }
  97. LL_INLINE bool isLink() const
  98. {
  99. checkRequestedInfo(DI_ISLINK);
  100. return mIsLink;
  101. }
  102. LL_INLINE bool isHidden() const
  103. {
  104. checkRequestedInfo(DI_ISHIDDEN);
  105. return mIsHidden;
  106. }
  107. LL_INLINE size_t getSize() const
  108. {
  109. checkRequestedInfo(DI_SIZE);
  110. return mSize;
  111. }
  112. LL_INLINE time_t getTimeStamp() const
  113. {
  114. checkRequestedInfo(DI_TIMESTAMP);
  115. return mTimeStamp;
  116. }
  117. static std::string globPatternToRegex(const char* glob);
  118. private:
  119. LL_INLINE bool hasRequestedInfo(U32 info) const
  120. {
  121. return (mRequestedInfo & info) != 0;
  122. }
  123. // Do not let the compiler inline the llerrs (would waste CPU cache)
  124. LL_NO_INLINE void checkRequestedInfo(U32 info) const;
  125. void populateEntryInfo();
  126. private:
  127. directory_iterator mIter;
  128. std::regex mFilterExp;
  129. size_t mSize;
  130. time_t mTimeStamp;
  131. U32 mRequestedInfo;
  132. #if LL_WINDOWS
  133. U8 mNextDrive;
  134. bool mIsDriveIterator;
  135. #endif
  136. bool mGotFilter;
  137. bool mIsFile;
  138. bool mIsDirectory;
  139. bool mIsLink;
  140. bool mIsHidden;
  141. };
  142. //static
  143. std::string LLDirIterator::Impl::globPatternToRegex(const char* glob)
  144. {
  145. size_t glob_len = strlen(glob);
  146. std::string expr;
  147. expr.reserve(glob_len * 2);
  148. S32 braces = 0;
  149. bool escaped = false;
  150. bool square_brace_open = false;
  151. for (size_t i = 0; i < glob_len; ++i)
  152. {
  153. const char& c = glob[i];
  154. switch (c)
  155. {
  156. case '*':
  157. if (i == 0)
  158. {
  159. expr = "[^.].*";
  160. }
  161. else
  162. {
  163. expr += escaped ? "*" : ".*";
  164. }
  165. break;
  166. case '?':
  167. expr += escaped ? '?' : '.';
  168. break;
  169. case '{':
  170. ++braces;
  171. expr += '(';
  172. break;
  173. case '}':
  174. if (--braces < 0)
  175. {
  176. llerrs << "Closing brace without an equivalent opening brace in: "
  177. << glob << llendl;
  178. }
  179. expr += ')';
  180. break;
  181. case ',':
  182. expr += braces ? '|' : c;
  183. break;
  184. case '!':
  185. expr += square_brace_open ? '^' : c;
  186. break;
  187. case '.':
  188. case '^':
  189. case '(':
  190. case ')':
  191. case '+':
  192. case '|':
  193. case '$':
  194. expr += '\\';
  195. // And fall-through...
  196. default:
  197. expr += c;
  198. }
  199. escaped = c == '\\';
  200. square_brace_open = c == '[';
  201. }
  202. if (braces)
  203. {
  204. llerrs << "Unterminated brace expression in: " << glob << llendl;
  205. }
  206. return expr;
  207. }
  208. void LLDirIterator::Impl::checkRequestedInfo(U32 info) const
  209. {
  210. if (!(mRequestedInfo & info))
  211. {
  212. llerrs << "Bad info request: " << info << llendl;
  213. }
  214. }
  215. void LLDirIterator::Impl::populateEntryInfo()
  216. {
  217. // Do this first, since it does not risk throw()ing at our face...
  218. if (hasRequestedInfo(DI_ISHIDDEN))
  219. {
  220. #if LL_WINDOWS
  221. DWORD attr = GetFileAttributesA(mIter->path().string().c_str());
  222. mIsHidden = (attr & FILE_ATTRIBUTE_HIDDEN) != 0;
  223. #else
  224. // Note: gcc v12.1 detects "dangling pointer to an unnamed temporary
  225. // may be used" when trying to use a const std::string& name here... HB
  226. std::string name = mIter->path().filename().string();
  227. mIsHidden = !name.empty() && name[0] == '.';
  228. #endif
  229. if (mRequestedInfo == DI_ISHIDDEN)
  230. {
  231. return; // No need to enter the try {} catch {} ...
  232. }
  233. }
  234. try
  235. {
  236. bool want_size = hasRequestedInfo(DI_SIZE);
  237. if (want_size || hasRequestedInfo(DI_ISFILE))
  238. {
  239. mIsFile = is_regular_file(mIter->path());
  240. }
  241. if (hasRequestedInfo(DI_ISDIR))
  242. {
  243. mIsDirectory = is_directory(mIter->path());
  244. }
  245. if (hasRequestedInfo(DI_ISLINK))
  246. {
  247. mIsLink = is_symlink(mIter->path());
  248. }
  249. if (want_size)
  250. {
  251. mSize = mIsFile ? file_size(mIter->path()) : 0;
  252. }
  253. if (hasRequestedInfo(DI_TIMESTAMP))
  254. {
  255. mTimeStamp = last_write_time(mIter->path());
  256. }
  257. }
  258. catch (const filesystem_error& e)
  259. {
  260. llwarns << e.what() << llendl;
  261. }
  262. }
  263. bool LLDirIterator::Impl::next(std::string& name, bool not_matching)
  264. {
  265. #if LL_WINDOWS
  266. if (mIsDriveIterator)
  267. {
  268. // Note: we ignore name matching since we iterate on drives and not
  269. // on files.
  270. name.clear();
  271. mIsDirectory = false;
  272. if (mNextDrive >= 26)
  273. {
  274. return false;
  275. }
  276. DWORD drives_map = GetLogicalDrives();
  277. for (U8 i = mNextDrive; i < 26; ++i)
  278. {
  279. if (drives_map & (1L << i))
  280. {
  281. char volume = 'A' + (char)i;
  282. name = volume;
  283. name += ':';
  284. mIsDirectory = true;
  285. mNextDrive = i + 1;
  286. break;
  287. }
  288. }
  289. return mIsDirectory;
  290. }
  291. #endif
  292. bool found = false;
  293. directory_iterator end; // Default construction yields past-the-end
  294. if (mGotFilter) // Iterator with glob pattern matching
  295. {
  296. try
  297. {
  298. boost::system::error_code ec;
  299. std::smatch match;
  300. while (mIter != end)
  301. {
  302. name = mIter->path().filename().string();
  303. if (name == "." || name == "..")
  304. {
  305. continue;
  306. }
  307. found = std::regex_match(name, match, mFilterExp);
  308. if (not_matching)
  309. {
  310. // We want entries not matching the pattern (unless they
  311. // are symbolic links).
  312. found = !found || is_symlink(mIter->path(), ec);
  313. }
  314. if (found)
  315. {
  316. break;
  317. }
  318. ++mIter;
  319. }
  320. }
  321. catch (const std::regex_error& e)
  322. {
  323. llwarns << e.what() << llendl;
  324. }
  325. }
  326. // Simple iterator without matching glob pattern
  327. else
  328. {
  329. while (mIter != end)
  330. {
  331. name = mIter->path().filename().string();
  332. if (name != "." && name != "..")
  333. {
  334. found = true;
  335. break;
  336. }
  337. ++mIter;
  338. }
  339. }
  340. if (found)
  341. {
  342. if (mRequestedInfo)
  343. {
  344. populateEntryInfo();
  345. }
  346. }
  347. else
  348. {
  349. name.clear();
  350. if (mRequestedInfo)
  351. {
  352. mIsFile = mIsDirectory = mIsLink = mIsHidden = false;
  353. mTimeStamp = 0;
  354. }
  355. }
  356. // Now that we got all the needed info, we can increment the iterator, if
  357. // not already at end...
  358. if (mIter != end)
  359. {
  360. ++mIter;
  361. }
  362. return found;
  363. }
  364. ///////////////////////////////////////////////////////////////////////////////
  365. // LLDirIterator class proper
  366. ///////////////////////////////////////////////////////////////////////////////
  367. // Helper function
  368. static void append_separator_if_needed(std::string& dirname)
  369. {
  370. #if LL_WINDOWS
  371. if (dirname.back() != '\\')
  372. {
  373. dirname += '\\';
  374. }
  375. #else
  376. if (dirname.back() != '/')
  377. {
  378. dirname += '/';
  379. }
  380. #endif
  381. }
  382. // NOTE: I chose to initialize the iterator and regexp inside LLDirIterator()
  383. // instead of LLDirIterator::Impl::Impl(), because I want the warnings to be
  384. // identified as coming from the former in the log file; only llerrs (which
  385. // shall never be seen happening by the end users) might come from the
  386. // implementation sub-class. The mImpl pointer is also used as a "flag" to
  387. // denote a successful initialization (stays NULL when init fails). HB
  388. LLDirIterator::LLDirIterator(const std::string& dirname, const char* mask,
  389. U32 requested_info)
  390. : mImpl(NULL)
  391. {
  392. if (dirname.empty())
  393. {
  394. #if LL_WINDOWS
  395. // When iterating on an empty path under Windows, we actually want the
  396. // list of the existing logical drives... In this case we ignore the
  397. // matching pattern, if any.
  398. directory_iterator iter;
  399. mImpl = new Impl(iter, requested_info);
  400. mImpl->setDriveIterator();
  401. return;
  402. #else
  403. llwarns << "Invalid (empty) path." << llendl;
  404. return;
  405. #endif
  406. }
  407. #if LL_WINDOWS
  408. path dir_path(ll_convert_string_to_wide(dirname));
  409. #else
  410. path dir_path(dirname);
  411. #endif
  412. // Check if dir_path is a directory.
  413. boost::system::error_code ec;
  414. if (!is_directory(dir_path, ec) || ec.failed())
  415. {
  416. if (ec.failed())
  417. {
  418. llwarns << "Invalid path: " << dirname << " - Error: "
  419. << ec.message() << llendl;
  420. }
  421. else
  422. {
  423. llwarns << "Invalid path: " << dirname << llendl;
  424. }
  425. return;
  426. }
  427. mDirPath = dir_path.string();
  428. append_separator_if_needed(mDirPath);
  429. // Initialize the directory iterator for the given path.
  430. directory_iterator iter(dir_path, ec);
  431. if (ec.failed())
  432. {
  433. llwarns << "Directory: " << mDirPath
  434. << " - Error while creating iterator: " << ec.message()
  435. << llendl;
  436. return;
  437. }
  438. std::regex regexp;
  439. bool has_glob = mask && *mask;
  440. if (has_glob)
  441. {
  442. // Convert the glob mask into a regular expression string
  443. std::string expr = Impl::globPatternToRegex(mask);
  444. // Initialize regexp with the expression converted from the glob mask
  445. try
  446. {
  447. regexp.assign(expr);
  448. }
  449. catch (const std::regex_error& e)
  450. {
  451. llwarns << "\"" << expr << "\" is not a valid regular expression: "
  452. << e.what() << " - Search match global pattern was: "
  453. << mask << llendl;
  454. return;
  455. }
  456. }
  457. mImpl = new Impl(iter, requested_info);
  458. if (has_glob)
  459. {
  460. mImpl->setFilter(regexp);
  461. }
  462. }
  463. LLDirIterator::~LLDirIterator()
  464. {
  465. delete mImpl;
  466. }
  467. bool LLDirIterator::next(std::string& name, bool not_matching)
  468. {
  469. return mImpl && mImpl->next(name, not_matching);
  470. }
  471. bool LLDirIterator::isFile() const
  472. {
  473. return mImpl && mImpl->isFile();
  474. }
  475. bool LLDirIterator::isDirectory() const
  476. {
  477. return mImpl && mImpl->isDirectory();
  478. }
  479. bool LLDirIterator::isLink() const
  480. {
  481. return mImpl && mImpl->isLink();
  482. }
  483. bool LLDirIterator::isHidden() const
  484. {
  485. return mImpl && mImpl->isHidden();
  486. }
  487. size_t LLDirIterator::getSize() const
  488. {
  489. return mImpl ? mImpl->getSize() : 0;
  490. }
  491. time_t LLDirIterator::getTimeStamp() const
  492. {
  493. return mImpl ? mImpl->getTimeStamp() : 0;
  494. }
  495. //static
  496. U32 LLDirIterator::deleteFilesInDir(const std::string& dirname,
  497. const char* mask, bool not_matching)
  498. {
  499. if (not_matching && !(mask && *mask))
  500. {
  501. // Nothing to do !
  502. return 0;
  503. }
  504. LLDirIterator iter(dirname, mask, DI_ISDIR);
  505. if (!iter.isValid())
  506. {
  507. return 0;
  508. }
  509. U32 count = 0;
  510. boost::system::error_code ec;
  511. std::string filename;
  512. while (iter.next(filename, not_matching))
  513. {
  514. if (!iter.isDirectory()) // We remove files and links alike
  515. {
  516. ++count;
  517. remove(iter.getPath() + filename, ec);
  518. if (ec.failed())
  519. {
  520. --count;
  521. llwarns << "Failure to remove \"" << filename << "\". Reason: "
  522. << ec.message() << llendl;
  523. }
  524. }
  525. }
  526. return count;
  527. }
  528. //static
  529. U32 LLDirIterator::deleteRecursivelyInDir(const std::string& dirname,
  530. const char* mask, bool not_matching)
  531. {
  532. if (not_matching && !(mask && *mask))
  533. {
  534. // Nothing to do !
  535. return 0;
  536. }
  537. LLDirIterator iter(dirname, mask, DI_ISDIR);
  538. if (!iter.isValid())
  539. {
  540. return 0;
  541. }
  542. U32 count = 0;
  543. boost::system::error_code ec;
  544. std::string name, subdir;
  545. while (iter.next(name, not_matching))
  546. {
  547. if (iter.isDirectory())
  548. {
  549. subdir = dirname;
  550. append_separator_if_needed(subdir);
  551. subdir += name;
  552. count += deleteRecursivelyInDir(subdir, mask, not_matching);
  553. // That subdirectory is now empty and can be removed (excepted,
  554. // maybe, when not_matching is true and a corresponding file was
  555. // found in it, but then the directory removal will simply fail).
  556. LLFile::rmdir(subdir);
  557. }
  558. else // We remove files and links alike
  559. {
  560. ++count;
  561. remove(iter.getPath() + name, ec);
  562. if (ec.failed())
  563. {
  564. --count;
  565. llwarns << "Failure to remove \"" << name << "\". Reason: "
  566. << ec.message() << llendl;
  567. }
  568. }
  569. }
  570. return count;
  571. }