aboutsummaryrefslogtreecommitdiffstats
path: root/util/folder/path.cpp
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /util/folder/path.cpp
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'util/folder/path.cpp')
-rw-r--r--util/folder/path.cpp487
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;
+ }
+}