path.cpp 11 KB

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