#include "dirut.h"
#include "path.h"
#include "pathsplit.h"
#include <util/generic/yexception.h>
#include <util/string/cast.h>
#include <util/string/escape.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)
{
}
inline TSplit(const TString& path, const TSimpleIntrusivePtr<TSplit>& thatSplit, const TString::char_type* thatPathData) {
for (const auto& thatPart : *thatSplit) {
emplace_back(path.data() + (thatPart.data() - thatPathData), thatPart.size());
}
if (!thatSplit->Drive.empty()) {
Drive = TStringBuf(path.data() + (thatSplit->Drive.data() - thatPathData), thatSplit->Drive.size());
}
IsAbsolute = thatSplit->IsAbsolute;
}
};
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_ABORT_UNLESS(!path.Contains('\0'), "wrong format of TFsPath: %s", EscapeC(path).c_str());
}
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(const TFsPath& that) {
*this = that;
}
TFsPath::TFsPath(TFsPath&& that) {
*this = std::move(that);
}
TFsPath& TFsPath::operator=(const TFsPath& that) {
Path_ = that.Path_;
if (that.Split_) {
Split_ = new TSplit(Path_, that.Split_, that.Path_.begin());
} else {
Split_ = nullptr;
}
return *this;
}
TFsPath& TFsPath::operator=(TFsPath&& that) {
#ifdef TSTRING_IS_STD_STRING
const auto thatPathData = that.Path_.data();
Path_ = std::move(that.Path_);
if (that.Split_) {
if (Path_.data() == thatPathData) { // Path_ moved, can move Split_
Split_ = std::move(that.Split_);
} else { // Path_ copied, rebuild Split_ using that.Split_
Split_ = new TSplit(Path_, that.Split_, that.Path_.data());
}
} else {
Split_ = nullptr;
}
#else
Path_ = std::move(that.Path_);
Split_ = std::move(that.Split_);
#endif
return *this;
}
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;
}
}