diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /util/folder/path.cpp | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'util/folder/path.cpp')
-rw-r--r-- | util/folder/path.cpp | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/util/folder/path.cpp b/util/folder/path.cpp new file mode 100644 index 0000000000..bfe0c67d68 --- /dev/null +++ b/util/folder/path.cpp @@ -0,0 +1,487 @@ +#include "dirut.h" +#include "path.h" +#include "pathsplit.h" + +#include <util/generic/yexception.h> +#include <util/string/cast.h> +#include <util/system/compiler.h> +#include <util/system/file.h> +#include <util/system/fs.h> + +struct TFsPath::TSplit: public TAtomicRefCount<TSplit>, public TPathSplit { + inline TSplit(const TStringBuf path) + : TPathSplit(path) + { + } +}; + +void TFsPath::CheckDefined() const { + if (!IsDefined()) { + ythrow TIoException() << TStringBuf("must be defined"); + } +} + +bool TFsPath::IsSubpathOf(const TFsPath& that) const { + const TSplit& split = GetSplit(); + const TSplit& rsplit = that.GetSplit(); + + if (rsplit.IsAbsolute != split.IsAbsolute) { + return false; + } + + if (rsplit.Drive != split.Drive) { + return false; + } + + if (rsplit.size() >= split.size()) { + return false; + } + + return std::equal(rsplit.begin(), rsplit.end(), split.begin()); +} + +bool TFsPath::IsNonStrictSubpathOf(const TFsPath& that) const { + const TSplit& split = GetSplit(); + const TSplit& rsplit = that.GetSplit(); + + if (rsplit.IsAbsolute != split.IsAbsolute) { + return false; + } + + if (rsplit.Drive != split.Drive) { + return false; + } + + if (rsplit.size() > split.size()) { + return false; + } + + return std::equal(rsplit.begin(), rsplit.end(), split.begin()); +} + +TFsPath TFsPath::RelativeTo(const TFsPath& root) const { + TSplit split = GetSplit(); + const TSplit& rsplit = root.GetSplit(); + + if (split.Reconstruct() == rsplit.Reconstruct()) { + return TFsPath(); + } + + if (!this->IsSubpathOf(root)) { + ythrow TIoException() << "path " << *this << " is not subpath of " << root; + } + + split.erase(split.begin(), split.begin() + rsplit.size()); + split.IsAbsolute = false; + + return TFsPath(split.Reconstruct()); +} + +TFsPath TFsPath::RelativePath(const TFsPath& root) const { + TSplit split = GetSplit(); + const TSplit& rsplit = root.GetSplit(); + size_t cnt = 0; + + while (split.size() > cnt && rsplit.size() > cnt && split[cnt] == rsplit[cnt]) { + ++cnt; + } + bool absboth = split.IsAbsolute && rsplit.IsAbsolute; + if (cnt == 0 && !absboth) { + ythrow TIoException() << "No common parts in " << *this << " and " << root; + } + TString r; + for (size_t i = 0; i < rsplit.size() - cnt; i++) { + r += i == 0 ? ".." : "/.."; + } + for (size_t i = cnt; i < split.size(); i++) { + r += (i == 0 || i == cnt && rsplit.size() - cnt == 0 ? "" : "/"); + r += split[i]; + } + return r.size() ? TFsPath(r) : TFsPath(); +} + +TFsPath TFsPath::Parent() const { + if (!IsDefined()) { + return TFsPath(); + } + + TSplit split = GetSplit(); + if (split.size()) { + split.pop_back(); + } + if (!split.size() && !split.IsAbsolute) { + return TFsPath("."); + } + return TFsPath(split.Reconstruct()); +} + +TFsPath& TFsPath::operator/=(const TFsPath& that) { + if (!IsDefined()) { + *this = that; + + } else if (that.IsDefined() && that.GetPath() != ".") { + if (!that.IsRelative()) { + ythrow TIoException() << "path should be relative: " << that.GetPath(); + } + + TSplit split = GetSplit(); + const TSplit& rsplit = that.GetSplit(); + split.insert(split.end(), rsplit.begin(), rsplit.end()); + *this = TFsPath(split.Reconstruct()); + } + return *this; +} + +TFsPath& TFsPath::Fix() { + // just normalize via reconstruction + TFsPath(GetSplit().Reconstruct()).Swap(*this); + + return *this; +} + +TString TFsPath::GetName() const { + if (!IsDefined()) { + return TString(); + } + + const TSplit& split = GetSplit(); + + if (split.size() > 0) { + if (split.back() != "..") { + return TString(split.back()); + } else { + // cannot just drop last component, because path itself may be a symlink + return RealPath().GetName(); + } + } else { + if (split.IsAbsolute) { + return split.Reconstruct(); + } else { + return Cwd().GetName(); + } + } +} + +TString TFsPath::GetExtension() const { + return TString(GetSplit().Extension()); +} + +bool TFsPath::IsAbsolute() const { + return GetSplit().IsAbsolute; +} + +bool TFsPath::IsRelative() const { + return !IsAbsolute(); +} + +void TFsPath::InitSplit() const { + Split_ = new TSplit(Path_); +} + +TFsPath::TSplit& TFsPath::GetSplit() const { + // XXX: race condition here + if (!Split_) { + InitSplit(); + } + return *Split_; +} + +static Y_FORCE_INLINE void VerifyPath(const TStringBuf path) { + Y_VERIFY(!path.Contains('\0'), "wrong format of TFsPath"); +} + +TFsPath::TFsPath() { +} + +TFsPath::TFsPath(const TString& path) + : Path_(path) +{ + VerifyPath(Path_); +} + +TFsPath::TFsPath(const TStringBuf path) + : Path_(ToString(path)) +{ + VerifyPath(Path_); +} + +TFsPath::TFsPath(const char* path) + : Path_(path) +{ +} + +TFsPath TFsPath::Child(const TString& name) const { + if (!name) { + ythrow TIoException() << "child name must not be empty"; + } + + return *this / name; +} + +struct TClosedir { + static void Destroy(DIR* dir) { + if (dir) { + if (0 != closedir(dir)) { + ythrow TIoSystemError() << "failed to closedir"; + } + } + } +}; + +void TFsPath::ListNames(TVector<TString>& children) const { + CheckDefined(); + THolder<DIR, TClosedir> dir(opendir(this->c_str())); + if (!dir) { + ythrow TIoSystemError() << "failed to opendir " << Path_; + } + + for (;;) { + struct dirent de; + struct dirent* ok; + // TODO(yazevnul|IGNIETFERRO-1070): remove these macroses by replacing `readdir_r` with proper + // alternative + Y_PRAGMA_DIAGNOSTIC_PUSH + Y_PRAGMA_NO_DEPRECATED + int r = readdir_r(dir.Get(), &de, &ok); + Y_PRAGMA_DIAGNOSTIC_POP + if (r != 0) { + ythrow TIoSystemError() << "failed to readdir " << Path_; + } + if (ok == nullptr) { + return; + } + TString name(de.d_name); + if (name == "." || name == "..") { + continue; + } + children.push_back(name); + } +} + +bool TFsPath::Contains(const TString& component) const { + if (!IsDefined()) { + return false; + } + + TFsPath path = *this; + while (path.Parent() != path) { + if (path.GetName() == component) { + return true; + } + + path = path.Parent(); + } + + return false; +} + +void TFsPath::List(TVector<TFsPath>& files) const { + TVector<TString> names; + ListNames(names); + for (auto& name : names) { + files.push_back(Child(name)); + } +} + +void TFsPath::RenameTo(const TString& newPath) const { + CheckDefined(); + if (!newPath) { + ythrow TIoException() << "bad new file name"; + } + if (!NFs::Rename(Path_, newPath)) { + ythrow TIoSystemError() << "failed to rename " << Path_ << " to " << newPath; + } +} + +void TFsPath::RenameTo(const char* newPath) const { + RenameTo(TString(newPath)); +} + +void TFsPath::RenameTo(const TFsPath& newPath) const { + RenameTo(newPath.GetPath()); +} + +void TFsPath::Touch() const { + CheckDefined(); + if (!TFile(*this, OpenAlways).IsOpen()) { + ythrow TIoException() << "failed to touch " << *this; + } +} + +// XXX: move implementation to util/somewhere. +TFsPath TFsPath::RealPath() const { + CheckDefined(); + return ::RealPath(*this); +} + +TFsPath TFsPath::RealLocation() const { + CheckDefined(); + return ::RealLocation(*this); +} + +TFsPath TFsPath::ReadLink() const { + CheckDefined(); + + if (!IsSymlink()) { + ythrow TIoException() << "not a symlink " << *this; + } + + return NFs::ReadLink(*this); +} + +bool TFsPath::Exists() const { + return IsDefined() && NFs::Exists(*this); +} + +void TFsPath::CheckExists() const { + if (!Exists()) { + ythrow TIoException() << "path does not exist " << Path_; + } +} + +bool TFsPath::IsDirectory() const { + return IsDefined() && TFileStat(GetPath().data()).IsDir(); +} + +bool TFsPath::IsFile() const { + return IsDefined() && TFileStat(GetPath().data()).IsFile(); +} + +bool TFsPath::IsSymlink() const { + return IsDefined() && TFileStat(GetPath().data(), true).IsSymlink(); +} + +void TFsPath::DeleteIfExists() const { + if (!IsDefined()) { + return; + } + + ::unlink(this->c_str()); + ::rmdir(this->c_str()); + if (Exists()) { + ythrow TIoException() << "failed to delete " << Path_; + } +} + +void TFsPath::MkDir(const int mode) const { + CheckDefined(); + if (!Exists()) { + int r = Mkdir(this->c_str(), mode); + if (r != 0) { + // TODO (stanly) will still fail on Windows because + // LastSystemError() returns windows specific ERROR_ALREADY_EXISTS + // instead of EEXIST. + if (LastSystemError() != EEXIST) { + ythrow TIoSystemError() << "could not create directory " << Path_; + } + } + } +} + +void TFsPath::MkDirs(const int mode) const { + CheckDefined(); + if (!Exists()) { + Parent().MkDirs(mode); + MkDir(mode); + } +} + +void TFsPath::ForceDelete() const { + if (!IsDefined()) { + return; + } + + TFileStat stat(GetPath().c_str(), true); + if (stat.IsNull()) { + const int err = LastSystemError(); +#ifdef _win_ + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { +#else + if (err == ENOENT) { +#endif + return; + } else { + ythrow TIoException() << "failed to stat " << Path_; + } + } + + ClearLastSystemError(); + if (stat.IsDir()) { + TVector<TFsPath> children; + List(children); + for (auto& i : children) { + i.ForceDelete(); + } + ::rmdir(this->c_str()); + } else { + ::unlink(this->c_str()); + } + + if (LastSystemError()) { + ythrow TIoException() << "failed to delete " << Path_; + } +} + +void TFsPath::CopyTo(const TString& newPath, bool force) const { + if (IsDirectory()) { + if (force) { + TFsPath(newPath).MkDirs(); + } else if (!TFsPath(newPath).IsDirectory()) { + ythrow TIoException() << "Target path is not a directory " << newPath; + } + TVector<TFsPath> children; + List(children); + for (auto&& i : children) { + i.CopyTo(newPath + "/" + i.GetName(), force); + } + } else { + if (force) { + TFsPath(newPath).Parent().MkDirs(); + } else { + if (!TFsPath(newPath).Parent().IsDirectory()) { + ythrow TIoException() << "Parent (" << TFsPath(newPath).Parent() << ") of a target path is not a directory " << newPath; + } + if (TFsPath(newPath).Exists()) { + ythrow TIoException() << "Path already exists " << newPath; + } + } + NFs::Copy(Path_, newPath); + } +} + +void TFsPath::ForceRenameTo(const TString& newPath) const { + try { + RenameTo(newPath); + } catch (const TIoSystemError& /* error */) { + CopyTo(newPath, true); + ForceDelete(); + } +} + +TFsPath TFsPath::Cwd() { + return TFsPath(::NFs::CurrentWorkingDirectory()); +} + +const TPathSplit& TFsPath::PathSplit() const { + return GetSplit(); +} + +template <> +void Out<TFsPath>(IOutputStream& os, const TFsPath& f) { + os << f.GetPath(); +} + +template <> +TFsPath FromStringImpl<TFsPath>(const char* s, size_t len) { + return TFsPath{TStringBuf{s, len}}; +} + +template <> +bool TryFromStringImpl(const char* s, size_t len, TFsPath& result) { + try { + result = TStringBuf{s, len}; + return true; + } catch (std::exception&) { + return false; + } +} |