path.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. #include "dirut.h"
  2. #include "path.h"
  3. #include "pathsplit.h"
  4. #include <util/generic/yexception.h>
  5. #include <util/string/cast.h>
  6. #include <util/string/escape.h>
  7. #include <util/system/compiler.h>
  8. #include <util/system/file.h>
  9. #include <util/system/fs.h>
  10. struct TFsPath::TSplit: public TAtomicRefCount<TSplit>, public TPathSplit {
  11. inline TSplit(const TStringBuf path)
  12. : TPathSplit(path)
  13. {
  14. }
  15. inline TSplit(const TString& path, const TSimpleIntrusivePtr<TSplit>& thatSplit, const TString::char_type* thatPathData) {
  16. for (const auto& thatPart : *thatSplit) {
  17. emplace_back(path.data() + (thatPart.data() - thatPathData), thatPart.size());
  18. }
  19. if (!thatSplit->Drive.empty()) {
  20. Drive = TStringBuf(path.data() + (thatSplit->Drive.data() - thatPathData), thatSplit->Drive.size());
  21. }
  22. IsAbsolute = thatSplit->IsAbsolute;
  23. }
  24. };
  25. void TFsPath::CheckDefined() const {
  26. if (!IsDefined()) {
  27. ythrow TIoException() << TStringBuf("must be defined");
  28. }
  29. }
  30. bool TFsPath::IsSubpathOf(const TFsPath& that) const {
  31. const TSplit& split = GetSplit();
  32. const TSplit& rsplit = that.GetSplit();
  33. if (rsplit.IsAbsolute != split.IsAbsolute) {
  34. return false;
  35. }
  36. if (rsplit.Drive != split.Drive) {
  37. return false;
  38. }
  39. if (rsplit.size() >= split.size()) {
  40. return false;
  41. }
  42. return std::equal(rsplit.begin(), rsplit.end(), split.begin());
  43. }
  44. bool TFsPath::IsNonStrictSubpathOf(const TFsPath& that) const {
  45. const TSplit& split = GetSplit();
  46. const TSplit& rsplit = that.GetSplit();
  47. if (rsplit.IsAbsolute != split.IsAbsolute) {
  48. return false;
  49. }
  50. if (rsplit.Drive != split.Drive) {
  51. return false;
  52. }
  53. if (rsplit.size() > split.size()) {
  54. return false;
  55. }
  56. return std::equal(rsplit.begin(), rsplit.end(), split.begin());
  57. }
  58. TFsPath TFsPath::RelativeTo(const TFsPath& root) const {
  59. TSplit split = GetSplit();
  60. const TSplit& rsplit = root.GetSplit();
  61. if (split.Reconstruct() == rsplit.Reconstruct()) {
  62. return TFsPath();
  63. }
  64. if (!this->IsSubpathOf(root)) {
  65. ythrow TIoException() << "path " << *this << " is not subpath of " << root;
  66. }
  67. split.erase(split.begin(), split.begin() + rsplit.size());
  68. split.IsAbsolute = false;
  69. return TFsPath(split.Reconstruct());
  70. }
  71. TFsPath TFsPath::RelativePath(const TFsPath& root) const {
  72. TSplit split = GetSplit();
  73. const TSplit& rsplit = root.GetSplit();
  74. size_t cnt = 0;
  75. while (split.size() > cnt && rsplit.size() > cnt && split[cnt] == rsplit[cnt]) {
  76. ++cnt;
  77. }
  78. bool absboth = split.IsAbsolute && rsplit.IsAbsolute;
  79. if (cnt == 0 && !absboth) {
  80. ythrow TIoException() << "No common parts in " << *this << " and " << root;
  81. }
  82. TString r;
  83. for (size_t i = 0; i < rsplit.size() - cnt; i++) {
  84. r += i == 0 ? ".." : "/..";
  85. }
  86. for (size_t i = cnt; i < split.size(); i++) {
  87. r += (i == 0 || i == cnt && rsplit.size() - cnt == 0 ? "" : "/");
  88. r += split[i];
  89. }
  90. return r.size() ? TFsPath(r) : TFsPath();
  91. }
  92. TFsPath TFsPath::Parent() const {
  93. if (!IsDefined()) {
  94. return TFsPath();
  95. }
  96. TSplit split = GetSplit();
  97. if (split.size()) {
  98. split.pop_back();
  99. }
  100. if (!split.size() && !split.IsAbsolute) {
  101. return TFsPath(".");
  102. }
  103. return TFsPath(split.Reconstruct());
  104. }
  105. TFsPath& TFsPath::operator/=(const TFsPath& that) {
  106. if (!IsDefined()) {
  107. *this = that;
  108. } else if (that.IsDefined() && that.GetPath() != ".") {
  109. if (!that.IsRelative()) {
  110. ythrow TIoException() << "path should be relative: " << that.GetPath();
  111. }
  112. TSplit split = GetSplit();
  113. const TSplit& rsplit = that.GetSplit();
  114. split.insert(split.end(), rsplit.begin(), rsplit.end());
  115. *this = TFsPath(split.Reconstruct());
  116. }
  117. return *this;
  118. }
  119. TFsPath& TFsPath::Fix() {
  120. // just normalize via reconstruction
  121. TFsPath(GetSplit().Reconstruct()).Swap(*this);
  122. return *this;
  123. }
  124. TString TFsPath::GetName() const {
  125. if (!IsDefined()) {
  126. return TString();
  127. }
  128. const TSplit& split = GetSplit();
  129. if (split.size() > 0) {
  130. if (split.back() != "..") {
  131. return TString(split.back());
  132. } else {
  133. // cannot just drop last component, because path itself may be a symlink
  134. return RealPath().GetName();
  135. }
  136. } else {
  137. if (split.IsAbsolute) {
  138. return split.Reconstruct();
  139. } else {
  140. return Cwd().GetName();
  141. }
  142. }
  143. }
  144. TString TFsPath::GetExtension() const {
  145. return TString(GetSplit().Extension());
  146. }
  147. bool TFsPath::IsAbsolute() const {
  148. return GetSplit().IsAbsolute;
  149. }
  150. bool TFsPath::IsRelative() const {
  151. return !IsAbsolute();
  152. }
  153. void TFsPath::InitSplit() const {
  154. Split_ = new TSplit(Path_);
  155. }
  156. TFsPath::TSplit& TFsPath::GetSplit() const {
  157. // XXX: race condition here
  158. if (!Split_) {
  159. InitSplit();
  160. }
  161. return *Split_;
  162. }
  163. static Y_FORCE_INLINE void VerifyPath(const TStringBuf path) {
  164. Y_ABORT_UNLESS(!path.Contains('\0'), "wrong format of TFsPath: %s", EscapeC(path).c_str());
  165. }
  166. TFsPath::TFsPath() {
  167. }
  168. TFsPath::TFsPath(const TString& path)
  169. : Path_(path)
  170. {
  171. VerifyPath(Path_);
  172. }
  173. TFsPath::TFsPath(const TStringBuf path)
  174. : Path_(ToString(path))
  175. {
  176. VerifyPath(Path_);
  177. }
  178. TFsPath::TFsPath(const char* path)
  179. : Path_(path)
  180. {
  181. }
  182. TFsPath::TFsPath(const TFsPath& that) {
  183. *this = that;
  184. }
  185. TFsPath::TFsPath(TFsPath&& that) {
  186. *this = std::move(that);
  187. }
  188. TFsPath& TFsPath::operator=(const TFsPath& that) {
  189. Path_ = that.Path_;
  190. if (that.Split_) {
  191. Split_ = new TSplit(Path_, that.Split_, that.Path_.begin());
  192. } else {
  193. Split_ = nullptr;
  194. }
  195. return *this;
  196. }
  197. TFsPath& TFsPath::operator=(TFsPath&& that) {
  198. #ifdef TSTRING_IS_STD_STRING
  199. const auto thatPathData = that.Path_.data();
  200. Path_ = std::move(that.Path_);
  201. if (that.Split_) {
  202. if (Path_.data() == thatPathData) { // Path_ moved, can move Split_
  203. Split_ = std::move(that.Split_);
  204. } else { // Path_ copied, rebuild Split_ using that.Split_
  205. Split_ = new TSplit(Path_, that.Split_, that.Path_.data());
  206. }
  207. } else {
  208. Split_ = nullptr;
  209. }
  210. #else
  211. Path_ = std::move(that.Path_);
  212. Split_ = std::move(that.Split_);
  213. #endif
  214. return *this;
  215. }
  216. TFsPath TFsPath::Child(const TString& name) const {
  217. if (!name) {
  218. ythrow TIoException() << "child name must not be empty";
  219. }
  220. return *this / name;
  221. }
  222. struct TClosedir {
  223. static void Destroy(DIR* dir) {
  224. if (dir) {
  225. if (0 != closedir(dir)) {
  226. ythrow TIoSystemError() << "failed to closedir";
  227. }
  228. }
  229. }
  230. };
  231. void TFsPath::ListNames(TVector<TString>& children) const {
  232. CheckDefined();
  233. THolder<DIR, TClosedir> dir(opendir(this->c_str()));
  234. if (!dir) {
  235. ythrow TIoSystemError() << "failed to opendir " << Path_;
  236. }
  237. for (;;) {
  238. struct dirent de;
  239. struct dirent* ok;
  240. // TODO(yazevnul|IGNIETFERRO-1070): remove these macroses by replacing `readdir_r` with proper
  241. // alternative
  242. Y_PRAGMA_DIAGNOSTIC_PUSH
  243. Y_PRAGMA_NO_DEPRECATED
  244. int r = readdir_r(dir.Get(), &de, &ok);
  245. Y_PRAGMA_DIAGNOSTIC_POP
  246. if (r != 0) {
  247. ythrow TIoSystemError() << "failed to readdir " << Path_;
  248. }
  249. if (ok == nullptr) {
  250. return;
  251. }
  252. TString name(de.d_name);
  253. if (name == "." || name == "..") {
  254. continue;
  255. }
  256. children.push_back(name);
  257. }
  258. }
  259. bool TFsPath::Contains(const TString& component) const {
  260. if (!IsDefined()) {
  261. return false;
  262. }
  263. TFsPath path = *this;
  264. while (path.Parent() != path) {
  265. if (path.GetName() == component) {
  266. return true;
  267. }
  268. path = path.Parent();
  269. }
  270. return false;
  271. }
  272. void TFsPath::List(TVector<TFsPath>& files) const {
  273. TVector<TString> names;
  274. ListNames(names);
  275. for (auto& name : names) {
  276. files.push_back(Child(name));
  277. }
  278. }
  279. void TFsPath::RenameTo(const TString& newPath) const {
  280. CheckDefined();
  281. if (!newPath) {
  282. ythrow TIoException() << "bad new file name";
  283. }
  284. if (!NFs::Rename(Path_, newPath)) {
  285. ythrow TIoSystemError() << "failed to rename " << Path_ << " to " << newPath;
  286. }
  287. }
  288. void TFsPath::RenameTo(const char* newPath) const {
  289. RenameTo(TString(newPath));
  290. }
  291. void TFsPath::RenameTo(const TFsPath& newPath) const {
  292. RenameTo(newPath.GetPath());
  293. }
  294. void TFsPath::Touch() const {
  295. CheckDefined();
  296. if (!TFile(*this, OpenAlways).IsOpen()) {
  297. ythrow TIoException() << "failed to touch " << *this;
  298. }
  299. }
  300. // XXX: move implementation to util/somewhere.
  301. TFsPath TFsPath::RealPath() const {
  302. CheckDefined();
  303. return ::RealPath(*this);
  304. }
  305. TFsPath TFsPath::RealLocation() const {
  306. CheckDefined();
  307. return ::RealLocation(*this);
  308. }
  309. TFsPath TFsPath::ReadLink() const {
  310. CheckDefined();
  311. if (!IsSymlink()) {
  312. ythrow TIoException() << "not a symlink " << *this;
  313. }
  314. return NFs::ReadLink(*this);
  315. }
  316. bool TFsPath::Exists() const {
  317. return IsDefined() && NFs::Exists(*this);
  318. }
  319. void TFsPath::CheckExists() const {
  320. if (!Exists()) {
  321. ythrow TIoException() << "path does not exist " << Path_;
  322. }
  323. }
  324. bool TFsPath::IsDirectory() const {
  325. return IsDefined() && TFileStat(GetPath().data()).IsDir();
  326. }
  327. bool TFsPath::IsFile() const {
  328. return IsDefined() && TFileStat(GetPath().data()).IsFile();
  329. }
  330. bool TFsPath::IsSymlink() const {
  331. return IsDefined() && TFileStat(GetPath().data(), true).IsSymlink();
  332. }
  333. void TFsPath::DeleteIfExists() const {
  334. if (!IsDefined()) {
  335. return;
  336. }
  337. ::unlink(this->c_str());
  338. ::rmdir(this->c_str());
  339. if (Exists()) {
  340. ythrow TIoException() << "failed to delete " << Path_;
  341. }
  342. }
  343. void TFsPath::MkDir(const int mode) const {
  344. CheckDefined();
  345. if (!Exists()) {
  346. int r = Mkdir(this->c_str(), mode);
  347. if (r != 0) {
  348. // TODO (stanly) will still fail on Windows because
  349. // LastSystemError() returns windows specific ERROR_ALREADY_EXISTS
  350. // instead of EEXIST.
  351. if (LastSystemError() != EEXIST) {
  352. ythrow TIoSystemError() << "could not create directory " << Path_;
  353. }
  354. }
  355. }
  356. }
  357. void TFsPath::MkDirs(const int mode) const {
  358. CheckDefined();
  359. if (!Exists()) {
  360. Parent().MkDirs(mode);
  361. MkDir(mode);
  362. }
  363. }
  364. void TFsPath::ForceDelete() const {
  365. if (!IsDefined()) {
  366. return;
  367. }
  368. TFileStat stat(GetPath().c_str(), true);
  369. if (stat.IsNull()) {
  370. const int err = LastSystemError();
  371. #ifdef _win_
  372. if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
  373. #else
  374. if (err == ENOENT) {
  375. #endif
  376. return;
  377. } else {
  378. ythrow TIoException() << "failed to stat " << Path_;
  379. }
  380. }
  381. bool succ;
  382. if (stat.IsDir()) {
  383. TVector<TFsPath> children;
  384. List(children);
  385. for (auto& i : children) {
  386. i.ForceDelete();
  387. }
  388. succ = ::rmdir(this->c_str()) == 0;
  389. } else {
  390. succ = ::unlink(this->c_str()) == 0;
  391. }
  392. if (!succ && LastSystemError()) {
  393. ythrow TIoException() << "failed to delete " << Path_;
  394. }
  395. }
  396. void TFsPath::CopyTo(const TString& newPath, bool force) const {
  397. if (IsDirectory()) {
  398. if (force) {
  399. TFsPath(newPath).MkDirs();
  400. } else if (!TFsPath(newPath).IsDirectory()) {
  401. ythrow TIoException() << "Target path is not a directory " << newPath;
  402. }
  403. TVector<TFsPath> children;
  404. List(children);
  405. for (auto&& i : children) {
  406. i.CopyTo(newPath + "/" + i.GetName(), force);
  407. }
  408. } else {
  409. if (force) {
  410. TFsPath(newPath).Parent().MkDirs();
  411. } else {
  412. if (!TFsPath(newPath).Parent().IsDirectory()) {
  413. ythrow TIoException() << "Parent (" << TFsPath(newPath).Parent() << ") of a target path is not a directory " << newPath;
  414. }
  415. if (TFsPath(newPath).Exists()) {
  416. ythrow TIoException() << "Path already exists " << newPath;
  417. }
  418. }
  419. NFs::Copy(Path_, newPath);
  420. }
  421. }
  422. void TFsPath::ForceRenameTo(const TString& newPath) const {
  423. try {
  424. RenameTo(newPath);
  425. } catch (const TIoSystemError& /* error */) {
  426. CopyTo(newPath, true);
  427. ForceDelete();
  428. }
  429. }
  430. TFsPath TFsPath::Cwd() {
  431. return TFsPath(::NFs::CurrentWorkingDirectory());
  432. }
  433. const TPathSplit& TFsPath::PathSplit() const {
  434. return GetSplit();
  435. }
  436. template <>
  437. void Out<TFsPath>(IOutputStream& os, const TFsPath& f) {
  438. os << f.GetPath();
  439. }
  440. template <>
  441. TFsPath FromStringImpl<TFsPath>(const char* s, size_t len) {
  442. return TFsPath{TStringBuf{s, len}};
  443. }
  444. template <>
  445. bool TryFromStringImpl(const char* s, size_t len, TFsPath& result) {
  446. try {
  447. result = TStringBuf{s, len};
  448. return true;
  449. } catch (std::exception&) {
  450. return false;
  451. }
  452. }