dirut.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. #include "dirut.h"
  2. #include "iterator.h"
  3. #include "filelist.h"
  4. #include "fts.h"
  5. #include "pathsplit.h"
  6. #include "path.h"
  7. #include <util/generic/yexception.h>
  8. #include <util/system/compiler.h>
  9. #include <util/system/fs.h>
  10. #include <util/system/maxlen.h>
  11. #include <util/system/yassert.h>
  12. void SlashFolderLocal(TString& folder) {
  13. if (!folder) {
  14. return;
  15. }
  16. #ifdef _win32_
  17. size_t pos;
  18. while ((pos = folder.find('/')) != TString::npos) {
  19. folder.replace(pos, 1, LOCSLASH_S);
  20. }
  21. #endif
  22. if (folder[folder.size() - 1] != LOCSLASH_C) {
  23. folder.append(LOCSLASH_S);
  24. }
  25. }
  26. #ifndef _win32_
  27. bool correctpath(TString& folder) {
  28. return resolvepath(folder, "/");
  29. }
  30. bool resolvepath(TString& folder, const TString& home) {
  31. Y_ASSERT(home && home.at(0) == '/');
  32. if (!folder) {
  33. return false;
  34. }
  35. // may be from windows
  36. char* ptr = folder.begin();
  37. while ((ptr = strchr(ptr, '\\')) != nullptr) {
  38. *ptr = '/';
  39. }
  40. if (folder.at(0) == '~') {
  41. if (folder.length() == 1 || folder.at(1) == '/') {
  42. folder = GetHomeDir() + (folder.data() + 1);
  43. } else {
  44. char* buf = (char*)alloca(folder.length() + 1);
  45. strcpy(buf, folder.data() + 1);
  46. char* p = strchr(buf, '/');
  47. if (p) {
  48. *p++ = 0;
  49. }
  50. passwd* pw = getpwnam(buf);
  51. if (pw) {
  52. folder = pw->pw_dir;
  53. folder += "/";
  54. if (p) {
  55. folder += p;
  56. }
  57. } else {
  58. return false; // unknown user
  59. }
  60. }
  61. }
  62. int len = folder.length() + home.length() + 1;
  63. char* path = (char*)alloca(len);
  64. if (folder.at(0) != '/') {
  65. strcpy(path, home.data());
  66. strcpy(strrchr(path, '/') + 1, folder.data()); // the last char must be '/' if it's a dir
  67. } else {
  68. strcpy(path, folder.data());
  69. }
  70. len = strlen(path) + 1;
  71. // grabbed from url.cpp
  72. char* newpath = (char*)alloca(len + 2);
  73. const char** pp = (const char**)alloca(len * sizeof(char*));
  74. int i = 0;
  75. for (char* s = path; s;) {
  76. pp[i++] = s;
  77. s = strchr(s, '/');
  78. if (s) {
  79. *s++ = 0;
  80. }
  81. }
  82. for (int j = 1; j < i;) {
  83. const char*& p = pp[j];
  84. if (strcmp(p, ".") == 0 || strcmp(p, "") == 0) {
  85. if (j == i - 1) {
  86. p = "";
  87. break;
  88. } else {
  89. memmove(pp + j, pp + j + 1, (i - j - 1) * sizeof(p));
  90. --i;
  91. }
  92. } else if (strcmp(p, "..") == 0) {
  93. if (j == i - 1) {
  94. if (j == 1) {
  95. p = "";
  96. } else {
  97. --i;
  98. pp[j - 1] = "";
  99. }
  100. break;
  101. } else {
  102. if (j == 1) {
  103. memmove(pp + j, pp + j + 1, (i - j - 1) * sizeof(p));
  104. --i;
  105. } else {
  106. memmove(pp + j - 1, pp + j + 1, (i - j - 1) * sizeof(p));
  107. i -= 2;
  108. --j;
  109. }
  110. }
  111. } else {
  112. ++j;
  113. }
  114. }
  115. char* s = newpath;
  116. for (int k = 0; k < i; k++) {
  117. s = strchr(strcpy(s, pp[k]), 0);
  118. *s++ = '/';
  119. }
  120. *(--s) = 0;
  121. folder = newpath;
  122. return true;
  123. }
  124. #else
  125. using dir_type = enum {
  126. dt_empty,
  127. dt_error,
  128. dt_up,
  129. dt_dir
  130. };
  131. // precondition: *ptr != '\\' || *ptr == 0 (cause dt_error)
  132. // postcondition: *ptr != '\\'
  133. template <typename T>
  134. static int next_dir(T*& ptr) {
  135. int has_blank = 0;
  136. int has_dot = 0;
  137. int has_letter = 0;
  138. int has_ctrl = 0;
  139. while (*ptr && *ptr != '\\') {
  140. int c = (unsigned char)*ptr++;
  141. switch (c) {
  142. case ' ':
  143. ++has_blank;
  144. break;
  145. case '.':
  146. ++has_dot;
  147. break;
  148. case '/':
  149. case ':':
  150. case '*':
  151. case '?':
  152. case '"':
  153. case '<':
  154. case '>':
  155. case '|':
  156. ++has_ctrl;
  157. break;
  158. default:
  159. if (c == 127 || c < ' ') {
  160. ++has_ctrl;
  161. } else {
  162. ++has_letter;
  163. }
  164. }
  165. }
  166. if (*ptr) {
  167. ++ptr;
  168. }
  169. if (has_ctrl) {
  170. return dt_error;
  171. }
  172. if (has_letter) {
  173. return dt_dir;
  174. }
  175. if (has_dot && has_blank) {
  176. return dt_error;
  177. }
  178. if (has_dot == 1) {
  179. return dt_empty;
  180. }
  181. if (has_dot == 2) {
  182. return dt_up;
  183. }
  184. return dt_error;
  185. }
  186. using disk_type = enum {
  187. dk_noflags = 0,
  188. dk_unc = 1,
  189. dk_hasdrive = 2,
  190. dk_fromroot = 4,
  191. dk_error = 8
  192. };
  193. // root slash (if any) - part of disk
  194. template <typename T>
  195. static int skip_disk(T*& ptr) {
  196. int result = dk_noflags;
  197. if (!*ptr) {
  198. return result;
  199. }
  200. if (ptr[0] == '\\' && ptr[1] == '\\') {
  201. result |= dk_unc | dk_fromroot;
  202. ptr += 2;
  203. if (next_dir(ptr) != dt_dir) {
  204. return dk_error; // has no host name
  205. }
  206. if (next_dir(ptr) != dt_dir) {
  207. return dk_error; // has no share name
  208. }
  209. } else {
  210. if (*ptr && *(ptr + 1) == ':') {
  211. result |= dk_hasdrive;
  212. ptr += 2;
  213. }
  214. if (*ptr == '\\' || *ptr == '/') {
  215. ++ptr;
  216. result |= dk_fromroot;
  217. }
  218. }
  219. return result;
  220. }
  221. int correctpath(char* cpath, const char* path) {
  222. if (!path || !*path) {
  223. *cpath = 0;
  224. return 1;
  225. }
  226. char* ptr = (char*)path;
  227. char* cptr = cpath;
  228. int counter = 0;
  229. while (*ptr) {
  230. char c = *ptr;
  231. if (c == '/') {
  232. c = '\\';
  233. }
  234. if (c == '\\') {
  235. ++counter;
  236. } else {
  237. counter = 0;
  238. }
  239. if (counter <= 1) {
  240. *cptr = c;
  241. ++cptr;
  242. }
  243. ++ptr;
  244. }
  245. *cptr = 0;
  246. // replace '/' by '\'
  247. int dk = skip_disk(cpath);
  248. if (dk == dk_error) {
  249. return 0;
  250. }
  251. char* ptr1 = ptr = cpath;
  252. int level = 0;
  253. while (*ptr) {
  254. switch (next_dir(ptr)) {
  255. case dt_dir:
  256. ++level;
  257. break;
  258. case dt_empty:
  259. memmove(ptr1, ptr, strlen(ptr) + 1);
  260. ptr = ptr1;
  261. break;
  262. case dt_up:
  263. --level;
  264. if (level >= 0) {
  265. *--ptr1 = 0;
  266. ptr1 = strrchr(cpath, '\\');
  267. if (!ptr1) {
  268. ptr1 = cpath;
  269. } else {
  270. ++ptr1;
  271. }
  272. memmove(ptr1, ptr, strlen(ptr) + 1);
  273. ptr = ptr1;
  274. break;
  275. } else if (level == -1 && (dk & dk_hasdrive)) {
  276. if (*ptr && *(ptr + 1) == ':' && *(cpath - 2) == ':') {
  277. memmove(cpath - 3, ptr, strlen(ptr) + 1);
  278. return 1;
  279. }
  280. }
  281. if (dk & dk_fromroot) {
  282. return 0;
  283. }
  284. break;
  285. case dt_error:
  286. default:
  287. return 0;
  288. }
  289. ptr1 = ptr;
  290. }
  291. if ((ptr > cpath || ptr == cpath && dk & dk_unc) && *(ptr - 1) == '\\') {
  292. *(ptr - 1) = 0;
  293. }
  294. return 1;
  295. }
  296. static inline int normchar(unsigned char c) {
  297. return (c < 'a' || c > 'z') ? c : c - 32;
  298. }
  299. static inline char* strslashcat(char* a, const char* b) {
  300. size_t len = strlen(a);
  301. if (len && a[len - 1] != '\\') {
  302. a[len++] = '\\';
  303. }
  304. strcpy(a + len, b);
  305. return a;
  306. }
  307. int resolvepath(char* apath, const char* rpath, const char* cpath) {
  308. const char* redisk = rpath;
  309. if (!rpath || !*rpath) {
  310. return 0;
  311. }
  312. int rdt = skip_disk(redisk);
  313. if (rdt == dk_error) {
  314. return 0;
  315. }
  316. if (rdt & dk_unc || rdt & dk_hasdrive && rdt & dk_fromroot) {
  317. return correctpath(apath, rpath);
  318. }
  319. const char* cedisk = cpath;
  320. if (!cpath || !*cpath) {
  321. return 0;
  322. }
  323. int cdt = skip_disk(cedisk);
  324. if (cdt == dk_error) {
  325. return 0;
  326. }
  327. char* tpath = (char*)alloca(strlen(rpath) + strlen(cpath) + 3);
  328. // rdt&dk_hasdrive && !rdt&dk_fromroot
  329. if (rdt & dk_hasdrive) {
  330. if (!(cdt & dk_fromroot)) {
  331. return 0;
  332. }
  333. if (cdt & dk_hasdrive && normchar(*rpath) != normchar(*cpath)) {
  334. return 0;
  335. }
  336. memcpy(tpath, rpath, 2);
  337. memcpy(tpath + 2, cedisk, strlen(cedisk) + 1);
  338. strslashcat(tpath, redisk);
  339. // !rdt&dk_hasdrive && rdt&dk_fromroot
  340. } else if (rdt & dk_fromroot) {
  341. if (!(cdt & dk_hasdrive) && !(cdt & dk_unc)) {
  342. return 0;
  343. }
  344. memcpy(tpath, cpath, cedisk - cpath);
  345. tpath[cedisk - cpath] = 0;
  346. strslashcat(tpath, redisk);
  347. // !rdt&dk_hasdrive && !rdt&dk_fromroot
  348. } else {
  349. if (!(cdt & dk_fromroot) || !(cdt & dk_hasdrive) && !(cdt & dk_unc)) {
  350. return 0;
  351. }
  352. strslashcat(strcpy(tpath, cpath), redisk);
  353. }
  354. return correctpath(apath, tpath);
  355. }
  356. bool correctpath(TString& filename) {
  357. char* ptr = (char*)alloca(filename.size() + 2);
  358. if (correctpath(ptr, filename.data())) {
  359. filename = ptr;
  360. return true;
  361. }
  362. return false;
  363. }
  364. bool resolvepath(TString& folder, const TString& home) {
  365. char* ptr = (char*)alloca(folder.size() + 3 + home.size());
  366. if (resolvepath(ptr, folder.data(), home.data())) {
  367. folder = ptr;
  368. return true;
  369. }
  370. return false;
  371. }
  372. #endif // !defined _win32_
  373. char GetDirectorySeparator() {
  374. return LOCSLASH_C;
  375. }
  376. const char* GetDirectorySeparatorS() {
  377. return LOCSLASH_S;
  378. }
  379. void RemoveDirWithContents(TString dirName) {
  380. SlashFolderLocal(dirName);
  381. TDirIterator dir(dirName, TDirIterator::TOptions(FTS_NOSTAT));
  382. for (auto it = dir.begin(); it != dir.end(); ++it) {
  383. switch (it->fts_info) {
  384. case FTS_F:
  385. case FTS_DEFAULT:
  386. case FTS_DP:
  387. case FTS_SL:
  388. case FTS_SLNONE:
  389. if (!NFs::Remove(it->fts_path)) {
  390. ythrow TSystemError() << "error while removing " << it->fts_path;
  391. }
  392. break;
  393. }
  394. }
  395. }
  396. int mkpath(char* path, int mode) {
  397. return NFs::MakeDirectoryRecursive(path, NFs::EFilePermission(mode)) ? 0 : -1;
  398. }
  399. // Implementation of realpath in FreeBSD (version 9.0 and less) and GetFullPathName in Windows
  400. // did not require last component of the file name to exist (other implementations will fail
  401. // if it does not). Use RealLocation if that behaviour is required.
  402. TString RealPath(const TString& path) {
  403. TTempBuf result;
  404. Y_ASSERT(result.Size() > MAX_PATH); // TMP_BUF_LEN > MAX_PATH
  405. #ifdef _win_
  406. if (GetFullPathName(path.data(), result.Size(), result.Data(), nullptr) == 0)
  407. #else
  408. if (realpath(path.data(), result.Data()) == nullptr)
  409. #endif
  410. ythrow TFileError() << "RealPath failed \"" << path << "\"";
  411. return result.Data();
  412. }
  413. TString RealLocation(const TString& path) {
  414. if (NFs::Exists(path)) {
  415. return RealPath(path);
  416. }
  417. TString dirpath = GetDirName(path);
  418. if (NFs::Exists(dirpath)) {
  419. return RealPath(dirpath) + GetDirectorySeparatorS() + GetFileNameComponent(path.data());
  420. }
  421. ythrow TFileError() << "RealLocation failed \"" << path << "\"";
  422. }
  423. int MakeTempDir(char path[/*FILENAME_MAX*/], const char* prefix) {
  424. int ret;
  425. TString sysTmp;
  426. #ifdef _win32_
  427. if (!prefix || *prefix == '/') {
  428. #else
  429. if (!prefix) {
  430. #endif
  431. sysTmp = GetSystemTempDir();
  432. prefix = sysTmp.data();
  433. }
  434. if ((ret = ResolvePath(prefix, nullptr, path, 1)) != 0) {
  435. return ret;
  436. }
  437. if (!TFileStat(path).IsDir()) {
  438. return ENOENT;
  439. }
  440. if ((strlcat(path, "tmpXXXXXX", FILENAME_MAX) > FILENAME_MAX - 100)) {
  441. return EINVAL;
  442. }
  443. if (!(mkdtemp(path))) {
  444. return errno ? errno : EINVAL;
  445. }
  446. strcat(path, LOCSLASH_S);
  447. return 0;
  448. }
  449. bool IsDir(const TString& path) {
  450. return TFileStat(path).IsDir();
  451. }
  452. TString GetHomeDir() {
  453. TString s(getenv("HOME"));
  454. if (!s) {
  455. #ifndef _win32_
  456. passwd* pw = nullptr;
  457. s = getenv("USER");
  458. if (s) {
  459. pw = getpwnam(s.data());
  460. } else {
  461. pw = getpwuid(getuid());
  462. }
  463. if (pw) {
  464. s = pw->pw_dir;
  465. } else
  466. #endif
  467. {
  468. char* cur_dir = getcwd(nullptr, 0);
  469. s = cur_dir;
  470. free(cur_dir);
  471. }
  472. }
  473. return s;
  474. }
  475. void MakeDirIfNotExist(const char* path, int mode) {
  476. if (!NFs::MakeDirectory(path, NFs::EFilePermission(mode)) && !NFs::Exists(path)) {
  477. ythrow TSystemError() << "failed to create directory " << path;
  478. }
  479. }
  480. void MakePathIfNotExist(const char* path, int mode) {
  481. NFs::MakeDirectoryRecursive(path, NFs::EFilePermission(mode));
  482. if (!NFs::Exists(path) || !TFileStat(path).IsDir()) {
  483. ythrow TSystemError() << "failed to create directory " << path;
  484. }
  485. }
  486. const char* GetFileNameComponent(const char* f) {
  487. const char* p = strrchr(f, LOCSLASH_C);
  488. #ifdef _win_
  489. // "/" is also valid char separator on Windows
  490. const char* p2 = strrchr(f, '/');
  491. if (p2 > p) {
  492. p = p2;
  493. }
  494. #endif
  495. if (p) {
  496. return p + 1;
  497. }
  498. return f;
  499. }
  500. TString GetSystemTempDir() {
  501. #ifdef _win_
  502. char buffer[1024];
  503. DWORD size = GetTempPath(1024, buffer);
  504. if (!size) {
  505. ythrow TSystemError() << "failed to get system temporary directory";
  506. }
  507. return TString(buffer, size);
  508. #else
  509. const char* var = "TMPDIR";
  510. const char* def = "/tmp";
  511. const char* r = getenv(var);
  512. const char* result = r ? r : def;
  513. return result[0] == '/' ? result : ResolveDir(result);
  514. #endif
  515. }
  516. TString ResolveDir(const char* path) {
  517. return ResolvePath(path, true);
  518. }
  519. bool SafeResolveDir(const char* path, TString& result) {
  520. try {
  521. result = ResolvePath(path, true);
  522. return true;
  523. } catch (...) {
  524. return false;
  525. }
  526. }
  527. TString GetDirName(const TString& path) {
  528. return TFsPath(path).Dirname();
  529. }
  530. #ifdef _win32_
  531. char* realpath(const char* pathname, char resolved_path[MAXPATHLEN]) {
  532. // partial implementation: no path existence check
  533. return _fullpath(resolved_path, pathname, MAXPATHLEN - 1);
  534. }
  535. #endif
  536. TString GetBaseName(const TString& path) {
  537. return TFsPath(path).Basename();
  538. }
  539. static bool IsAbsolutePath(const char* str) {
  540. return str && TPathSplitTraitsLocal::IsAbsolutePath(TStringBuf(str, NStringPrivate::GetStringLengthWithLimit(str, 3)));
  541. }
  542. int ResolvePath(const char* rel, const char* abs, char res[/*MAXPATHLEN*/], bool isdir) {
  543. char t[MAXPATHLEN * 2 + 3];
  544. size_t len;
  545. *res = 0;
  546. if (!rel || !*rel) {
  547. return EINVAL;
  548. }
  549. if (!IsAbsolutePath(rel) && IsAbsolutePath(abs)) {
  550. len = strlcpy(t, abs, sizeof(t));
  551. if (len >= sizeof(t) - 3) {
  552. return EINVAL;
  553. }
  554. if (t[len - 1] != LOCSLASH_C) {
  555. t[len++] = LOCSLASH_C;
  556. }
  557. len += strlcpy(t + len, rel, sizeof(t) - len);
  558. } else {
  559. len = strlcpy(t, rel, sizeof(t));
  560. }
  561. if (len >= sizeof(t) - 3) {
  562. return EINVAL;
  563. }
  564. if (isdir && t[len - 1] != LOCSLASH_C) {
  565. t[len++] = LOCSLASH_C;
  566. t[len] = 0;
  567. }
  568. if (!realpath(t, res)) {
  569. if (!isdir && realpath(GetDirName(t).data(), res)) {
  570. len = strlen(res);
  571. if (res[len - 1] != LOCSLASH_C) {
  572. res[len++] = LOCSLASH_C;
  573. res[len] = 0;
  574. }
  575. strcpy(res + len, GetBaseName(t).data());
  576. return 0;
  577. }
  578. return errno ? errno : ENOENT;
  579. }
  580. if (isdir) {
  581. len = strlen(res);
  582. if (res[len - 1] != LOCSLASH_C) {
  583. res[len++] = LOCSLASH_C;
  584. res[len] = 0;
  585. }
  586. }
  587. return 0;
  588. }
  589. TString ResolvePath(const char* rel, const char* abs, bool isdir) {
  590. char buf[PATH_MAX];
  591. if (ResolvePath(rel, abs, buf, isdir)) {
  592. ythrow yexception() << "cannot resolve path: \"" << rel << "\"";
  593. }
  594. return buf;
  595. }
  596. TString ResolvePath(const char* path, bool isDir) {
  597. return ResolvePath(path, nullptr, isDir);
  598. }
  599. TString StripFileComponent(const TString& fileName) {
  600. TString dir = IsDir(fileName) ? fileName : GetDirName(fileName);
  601. if (!dir.empty() && dir.back() != GetDirectorySeparator()) {
  602. dir.append(GetDirectorySeparator());
  603. }
  604. return dir;
  605. }