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_ut.cpp | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'util/folder/path_ut.cpp')
-rw-r--r-- | util/folder/path_ut.cpp | 812 |
1 files changed, 812 insertions, 0 deletions
diff --git a/util/folder/path_ut.cpp b/util/folder/path_ut.cpp new file mode 100644 index 0000000000..e6a3451016 --- /dev/null +++ b/util/folder/path_ut.cpp @@ -0,0 +1,812 @@ +#include "path.h" +#include "pathsplit.h" +#include "dirut.h" +#include "tempdir.h" + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/generic/scope.h> +#include <util/system/platform.h> +#include <util/system/yassert.h> +#include <util/stream/output.h> +#include <util/stream/file.h> +#include <util/system/fs.h> + +#include <algorithm> + +#ifdef _win_ + #include <aclapi.h> +#endif + +namespace { + /// empty directory for test that needs filesystem + /// recreates directory in constructor and removes directory in destructor + class TTestDirectory { + private: + TFsPath Path_; + + public: + TTestDirectory(const TString& name); + ~TTestDirectory(); + + TFsPath GetFsPath() const { + return Path_; + } + + TFsPath Child(const TString& name) const { + return Path_.Child(name); + } + }; + + TTestDirectory::TTestDirectory(const TString& name) { + Y_VERIFY(name.length() > 0, "have to specify name"); + Y_VERIFY(name.find('.') == TString::npos, "must be simple name"); + Y_VERIFY(name.find('/') == TString::npos, "must be simple name"); + Y_VERIFY(name.find('\\') == TString::npos, "must be simple name"); + Path_ = TFsPath(name); + + Path_.ForceDelete(); + Path_.MkDir(); + } + + TTestDirectory::~TTestDirectory() { + Path_.ForceDelete(); + } +} + +Y_UNIT_TEST_SUITE(TFsPathTests) { + Y_UNIT_TEST(TestMkDirs) { + const TFsPath path = "a/b/c/d/e/f"; + path.ForceDelete(); + TFsPath current = path; + ui32 checksCounter = 0; + while (current != ".") { + UNIT_ASSERT(!path.Exists()); + ++checksCounter; + current = current.Parent(); + } + UNIT_ASSERT_VALUES_EQUAL(checksCounter, 6); + + path.MkDirs(); + UNIT_ASSERT(path.Exists()); + + current = path; + while (current != ".") { + UNIT_ASSERT(path.Exists()); + current = current.Parent(); + } + } + + Y_UNIT_TEST(MkDirFreak) { + TFsPath path; + UNIT_ASSERT_EXCEPTION(path.MkDir(), TIoException); + UNIT_ASSERT_EXCEPTION(path.MkDirs(), TIoException); + path = "."; + path.MkDir(); + path.MkDirs(); + } + + Y_UNIT_TEST(Parent) { +#ifdef _win_ + UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\etc/passwd").Parent(), TFsPath("\\etc")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\etc").Parent(), TFsPath("\\")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\").Parent(), TFsPath("\\")); + + UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc\\passwd").Parent(), TFsPath("etc")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc").Parent(), TFsPath(".")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath(".\\etc").Parent(), TFsPath(".")); + + UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\etc/passwd").Parent(), TFsPath("C:\\etc")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\etc").Parent(), TFsPath("C:\\")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\").Parent(), TFsPath("C:\\")); +#else + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/etc/passwd").Parent(), TFsPath("/etc")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/etc").Parent(), TFsPath("/")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/").Parent(), TFsPath("/")); + + UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc/passwd").Parent(), TFsPath("etc")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc").Parent(), TFsPath(".")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("./etc").Parent(), TFsPath(".")); +#endif + +#if 0 + UNIT_ASSERT_VALUES_EQUAL(TFsPath("./etc/passwd").Parent(), TFsPath("./etc")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("./").Parent(), TFsPath("..")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath(".").Parent(), TFsPath("..")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("..").Parent(), TFsPath("../..")); +#endif + } + + Y_UNIT_TEST(GetName) { + TTestDirectory d("GetName"); + UNIT_ASSERT_VALUES_EQUAL(TString("dfgh"), d.Child("dfgh").GetName()); + + // check does not fail + TFsPath(".").GetName(); + +#ifdef _unix_ + UNIT_ASSERT_VALUES_EQUAL(TString("/"), TFsPath("/").GetName()); +#endif + } + + Y_UNIT_TEST(GetExtension) { + TTestDirectory d("GetExtension"); + UNIT_ASSERT_VALUES_EQUAL("", d.Child("a").GetExtension()); + UNIT_ASSERT_VALUES_EQUAL("", d.Child(".a").GetExtension()); + UNIT_ASSERT_VALUES_EQUAL("", d.Child("zlib").GetExtension()); + UNIT_ASSERT_VALUES_EQUAL("zlib", d.Child("file.zlib").GetExtension()); + UNIT_ASSERT_VALUES_EQUAL("zlib", d.Child("file.ylib.zlib").GetExtension()); + } + + Y_UNIT_TEST(TestRename) { + TTestDirectory xx("TestRename"); + TFsPath f1 = xx.Child("f1"); + TFsPath f2 = xx.Child("f2"); + f1.Touch(); + f1.RenameTo(f2); + UNIT_ASSERT(!f1.Exists()); + UNIT_ASSERT(f2.Exists()); + } + + Y_UNIT_TEST(TestForceRename) { + TTestDirectory xx("TestForceRename"); + TFsPath fMain = xx.Child("main"); + + TFsPath f1 = fMain.Child("f1"); + f1.MkDirs(); + TFsPath f1Child = f1.Child("f1child"); + f1Child.Touch(); + + TFsPath f2 = fMain.Child("f2"); + f2.MkDirs(); + + fMain.ForceRenameTo("TestForceRename/main1"); + + UNIT_ASSERT(!xx.Child("main").Exists()); + UNIT_ASSERT(xx.Child("main1").Child("f1").Exists()); + UNIT_ASSERT(xx.Child("main1").Child("f2").Exists()); + UNIT_ASSERT(xx.Child("main1").Child("f1").Child("f1child").Exists()); + } + + Y_UNIT_TEST(TestRenameFail) { + UNIT_ASSERT_EXCEPTION(TFsPath("sfsfsfsdfsfsdfdf").RenameTo("sdfsdf"), TIoException); + } + +#ifndef _win_ + Y_UNIT_TEST(TestRealPath) { + UNIT_ASSERT(TFsPath(".").RealPath().IsDirectory()); + + TTestDirectory td("TestRealPath"); + TFsPath link = td.Child("link"); + TFsPath target1 = td.Child("target1"); + target1.Touch(); + TFsPath target2 = td.Child("target2"); + target2.Touch(); + UNIT_ASSERT(NFs::SymLink(target1.RealPath(), link.GetPath())); + UNIT_ASSERT_VALUES_EQUAL(link.RealPath(), target1.RealPath()); + UNIT_ASSERT(NFs::Remove(link.GetPath())); + UNIT_ASSERT(NFs::SymLink(target2.RealPath(), link.GetPath())); + UNIT_ASSERT_VALUES_EQUAL(link.RealPath(), target2.RealPath()); // must not cache old value + } +#endif + + Y_UNIT_TEST(TestSlashesAndBasename) { + TFsPath p("/db/BASE/primus121-025-1380131338//"); + UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338")); + TFsPath testP = p / "test"; +#ifdef _win_ + UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\test"); +#else + UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "/db/BASE/primus121-025-1380131338/test"); +#endif + } + + Y_UNIT_TEST(TestSlashesAndBasenameWin) { + TFsPath p("\\db\\BASE\\primus121-025-1380131338\\\\"); + TFsPath testP = p / "test"; +#ifdef _win_ + UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338")); + UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\test"); +#else + UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("\\db\\BASE\\primus121-025-1380131338\\\\")); + UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\\\/test"); +#endif + } + + Y_UNIT_TEST(TestSlashesAndBasenameWinDrive) { + TFsPath p("C:\\db\\BASE\\primus121-025-1380131338\\\\"); + TFsPath testP = p / "test"; +#ifdef _win_ + UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338")); + UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "C:\\db\\BASE\\primus121-025-1380131338\\test"); +#else + UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("C:\\db\\BASE\\primus121-025-1380131338\\\\")); + UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "C:\\db\\BASE\\primus121-025-1380131338\\\\/test"); +#endif + } + + Y_UNIT_TEST(TestList) { + TTestDirectory td("TestList-dir"); + + TFsPath dir = td.GetFsPath(); + dir.Child("a").Touch(); + dir.Child("b").MkDir(); + dir.Child("b").Child("b-1").Touch(); + dir.Child("c").MkDir(); + dir.Child("d").Touch(); + + TVector<TString> children; + dir.ListNames(children); + std::sort(children.begin(), children.end()); + + TVector<TString> expected; + expected.push_back("a"); + expected.push_back("b"); + expected.push_back("c"); + expected.push_back("d"); + + UNIT_ASSERT_VALUES_EQUAL(expected, children); + } + +#ifdef _unix_ + Y_UNIT_TEST(MkDirMode) { + TTestDirectory td("MkDirMode"); + TFsPath subDir = td.Child("subdir"); + const int mode = MODE0775; + subDir.MkDir(mode); + TFileStat stat; + UNIT_ASSERT(subDir.Stat(stat)); + // mkdir(2) places umask(2) on mode argument. + const int mask = Umask(0); + Umask(mask); + UNIT_ASSERT_VALUES_EQUAL(stat.Mode& MODE0777, mode & ~mask); + } +#endif + + Y_UNIT_TEST(Cwd) { + UNIT_ASSERT_VALUES_EQUAL(TFsPath::Cwd().RealPath(), TFsPath(".").RealPath()); + } + + Y_UNIT_TEST(TestSubpathOf) { + UNIT_ASSERT(TFsPath("/a/b/c/d").IsSubpathOf("/a/b")); + + UNIT_ASSERT(TFsPath("/a").IsSubpathOf("/")); + UNIT_ASSERT(!TFsPath("/").IsSubpathOf("/a")); + UNIT_ASSERT(!TFsPath("/a").IsSubpathOf("/a")); + + UNIT_ASSERT(TFsPath("/a/b").IsSubpathOf("/a")); + UNIT_ASSERT(TFsPath("a/b").IsSubpathOf("a")); + UNIT_ASSERT(!TFsPath("/a/b").IsSubpathOf("/b")); + UNIT_ASSERT(!TFsPath("a/b").IsSubpathOf("b")); + + // mixing absolute/relative + UNIT_ASSERT(!TFsPath("a").IsSubpathOf("/")); + UNIT_ASSERT(!TFsPath("a").IsSubpathOf("/a")); + UNIT_ASSERT(!TFsPath("/a").IsSubpathOf("a")); + UNIT_ASSERT(!TFsPath("a/b").IsSubpathOf("/a")); + UNIT_ASSERT(!TFsPath("/a/b").IsSubpathOf("a")); + +#ifdef _win_ + UNIT_ASSERT(TFsPath("x:/a/b").IsSubpathOf("x:/a")); + UNIT_ASSERT(!TFsPath("x:/a/b").IsSubpathOf("y:/a")); + UNIT_ASSERT(!TFsPath("x:/a/b").IsSubpathOf("a")); +#endif + } + + Y_UNIT_TEST(TestNonStrictSubpathOf) { + UNIT_ASSERT(TFsPath("/a/b/c/d").IsNonStrictSubpathOf("/a/b")); + + UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/")); + UNIT_ASSERT(!TFsPath("/").IsNonStrictSubpathOf("/a")); + + UNIT_ASSERT(TFsPath("/a/b").IsNonStrictSubpathOf("/a")); + UNIT_ASSERT(TFsPath("a/b").IsNonStrictSubpathOf("a")); + UNIT_ASSERT(!TFsPath("/a/b").IsNonStrictSubpathOf("/b")); + UNIT_ASSERT(!TFsPath("a/b").IsNonStrictSubpathOf("b")); + + // mixing absolute/relative + UNIT_ASSERT(!TFsPath("a").IsNonStrictSubpathOf("/")); + UNIT_ASSERT(!TFsPath("a").IsNonStrictSubpathOf("/a")); + UNIT_ASSERT(!TFsPath("/a").IsNonStrictSubpathOf("a")); + UNIT_ASSERT(!TFsPath("a/b").IsNonStrictSubpathOf("/a")); + UNIT_ASSERT(!TFsPath("/a/b").IsNonStrictSubpathOf("a")); + + // equal paths + UNIT_ASSERT(TFsPath("").IsNonStrictSubpathOf("")); + UNIT_ASSERT(TFsPath("/").IsNonStrictSubpathOf("/")); + UNIT_ASSERT(TFsPath("a").IsNonStrictSubpathOf("a")); + UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/a")); + UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/a/")); + UNIT_ASSERT(TFsPath("/a/").IsNonStrictSubpathOf("/a")); + UNIT_ASSERT(TFsPath("/a/").IsNonStrictSubpathOf("/a/")); + +#ifdef _win_ + UNIT_ASSERT(TFsPath("x:/a/b").IsNonStrictSubpathOf("x:/a")); + + UNIT_ASSERT(TFsPath("x:/a").IsNonStrictSubpathOf("x:/a")); + UNIT_ASSERT(TFsPath("x:/a/").IsNonStrictSubpathOf("x:/a")); + UNIT_ASSERT(TFsPath("x:/a").IsNonStrictSubpathOf("x:/a/")); + UNIT_ASSERT(TFsPath("x:/a/").IsNonStrictSubpathOf("x:/a/")); + + UNIT_ASSERT(!TFsPath("x:/").IsNonStrictSubpathOf("y:/")); + UNIT_ASSERT(!TFsPath("x:/a/b").IsNonStrictSubpathOf("y:/a")); + UNIT_ASSERT(!TFsPath("x:/a/b").IsNonStrictSubpathOf("a")); +#endif + } + + Y_UNIT_TEST(TestRelativePath) { + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b/c/d").RelativePath(TFsPath("/a/b")), TFsPath("c/d")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b/c/d").RelativePath(TFsPath("/a/b/e/f")), TFsPath("../../c/d")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/").RelativePath(TFsPath("/")), TFsPath()); + UNIT_ASSERT_VALUES_EQUAL(TFsPath(".").RelativePath(TFsPath(".")), TFsPath()); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/c").RelativePath(TFsPath("/a/b/../c")), TFsPath()); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/.././b").RelativePath(TFsPath("b/c")), TFsPath("..")); + + UNIT_ASSERT_EXCEPTION(TFsPath("a/b/c").RelativePath(TFsPath("d/e")), TIoException); + } + + Y_UNIT_TEST(TestUndefined) { + UNIT_ASSERT_VALUES_EQUAL(TFsPath(), TFsPath("")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath(), TFsPath().Fix()); + + UNIT_ASSERT_VALUES_EQUAL(TFsPath() / TFsPath(), TFsPath()); +#ifdef _win_ + UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b"), TFsPath() / TString("a\\b")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b"), "a\\b" / TFsPath()); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\a\\b"), TFsPath() / "\\a\\b"); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\a\\b"), "\\a\\b" / TFsPath()); +#else + UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b"), TFsPath() / TString("a/b")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b"), "a/b" / TFsPath()); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b"), TFsPath() / "/a/b"); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b"), "/a/b" / TFsPath()); +#endif + UNIT_ASSERT_VALUES_EQUAL(TFsPath("."), TFsPath() / "."); + UNIT_ASSERT_VALUES_EQUAL(TFsPath("."), "." / TFsPath()); + + UNIT_ASSERT(TFsPath().PathSplit().empty()); + UNIT_ASSERT(!TFsPath().PathSplit().IsAbsolute); + UNIT_ASSERT(TFsPath().IsRelative()); // undefined path is relative + + UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetPath(), ""); + UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetName(), ""); + UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetExtension(), ""); + + UNIT_ASSERT_VALUES_EQUAL(TFsPath().Parent(), TFsPath()); + UNIT_ASSERT_VALUES_EQUAL(TFsPath().Child("a"), TFsPath("a")); + UNIT_ASSERT_VALUES_EQUAL(TFsPath().Basename(), ""); + UNIT_ASSERT_VALUES_EQUAL(TFsPath().Dirname(), ""); + + UNIT_ASSERT(!TFsPath().IsSubpathOf("a/b")); + UNIT_ASSERT(TFsPath().IsContainerOf("a/b")); + UNIT_ASSERT(!TFsPath().IsContainerOf("/a/b")); +#ifdef _win_ + UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b").RelativeTo(TFsPath()), TFsPath("a\\b")); +#else + UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b").RelativeTo(TFsPath()), TFsPath("a/b")); +#endif + + UNIT_ASSERT(!TFsPath().Exists()); + UNIT_ASSERT(!TFsPath().IsFile()); + UNIT_ASSERT(!TFsPath().IsDirectory()); + TFileStat stat; + UNIT_ASSERT(!TFsPath().Stat(stat)); + } + + Y_UNIT_TEST(TestJoinFsPaths) { +#ifdef _win_ + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", "c\\d"), "a\\b\\c\\d"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", "..\\c"), "a\\b\\..\\c"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b\\..\\c", "d"), "a\\c\\d"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a", "b", "c", "d"), "a\\b\\c\\d"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b\\..\\c"), "a\\b\\..\\c"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", ""), "a\\b"); +#else + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", "c/d"), "a/b/c/d"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", "../c"), "a/b/../c"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b/../c", "d"), "a/c/d"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a", "b", "c", "d"), "a/b/c/d"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b/../c"), "a/b/../c"); + UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", ""), "a/b"); +#endif + } + + Y_UNIT_TEST(TestStringCast) { + TFsPath pathOne; + UNIT_ASSERT(TryFromString<TFsPath>("/a/b", pathOne)); + UNIT_ASSERT_VALUES_EQUAL(pathOne, TFsPath{"/a/b"}); + + TFsPath pathTwo; + UNIT_ASSERT_NO_EXCEPTION(TryFromString<TFsPath>("/a/b", pathTwo)); + + UNIT_ASSERT_VALUES_EQUAL(FromString<TFsPath>("/a/b"), TFsPath{"/a/b"}); + + TFsPath pathThree{"/a/b"}; + UNIT_ASSERT_VALUES_EQUAL(ToString(pathThree), "/a/b"); + } + +#ifdef _unix_ + Y_UNIT_TEST(TestRemoveSymlinkToDir) { + TTempDir tempDir; + TFsPath tempDirPath(tempDir()); + + const TString originDir = tempDirPath.Child("origin"); + MakePathIfNotExist(originDir.c_str()); + + const TString originFile = TFsPath(originDir).Child("data"); + { + TFixedBufferFileOutput out(originFile); + out << "data111!!!"; + } + + const TString link = tempDirPath.Child("origin_symlink"); + NFs::SymLink(originDir, link); + + TFsPath(link).ForceDelete(); + + UNIT_ASSERT(!NFs::Exists(link)); + UNIT_ASSERT(NFs::Exists(originFile)); + UNIT_ASSERT(NFs::Exists(originDir)); + } + + Y_UNIT_TEST(TestRemoveSymlinkToFile) { + TTempDir tempDir; + TFsPath tempDirPath(tempDir()); + + const TString originDir = tempDirPath.Child("origin"); + MakePathIfNotExist(originDir.c_str()); + + const TString originFile = TFsPath(originDir).Child("data"); + { + TFixedBufferFileOutput out(originFile); + out << "data111!!!"; + } + + const TString link = tempDirPath.Child("origin_symlink"); + NFs::SymLink(originFile, link); + + TFsPath(link).ForceDelete(); + + UNIT_ASSERT(!NFs::Exists(link)); + UNIT_ASSERT(NFs::Exists(originFile)); + UNIT_ASSERT(NFs::Exists(originDir)); + } + + Y_UNIT_TEST(TestRemoveDirWithSymlinkToDir) { + TTempDir tempDir; + TFsPath tempDirPath(tempDir()); + + const TString symlinkedDir = tempDirPath.Child("to_remove"); + MakePathIfNotExist(symlinkedDir.c_str()); + + const TString originDir = tempDirPath.Child("origin"); + MakePathIfNotExist(originDir.c_str()); + + const TString originFile = TFsPath(originDir).Child("data"); + { + TFixedBufferFileOutput out(originFile); + out << "data111!!!"; + } + + const TString symlinkedFile = TFsPath(symlinkedDir).Child("origin_symlink"); + NFs::SymLink(originDir, symlinkedFile); + + TFsPath(symlinkedDir).ForceDelete(); + + UNIT_ASSERT(!NFs::Exists(symlinkedFile)); + UNIT_ASSERT(!NFs::Exists(symlinkedDir)); + UNIT_ASSERT(NFs::Exists(originFile)); + UNIT_ASSERT(NFs::Exists(originDir)); + } + + Y_UNIT_TEST(TestRemoveDirWithSymlinkToFile) { + TTempDir tempDir; + TFsPath tempDirPath(tempDir()); + + const TString symlinkedDir = tempDirPath.Child("to_remove"); + MakePathIfNotExist(symlinkedDir.c_str()); + + const TString originDir = tempDirPath.Child("origin"); + MakePathIfNotExist(originDir.c_str()); + + const TString originFile = TFsPath(originDir).Child("data"); + { + TFixedBufferFileOutput out(originFile); + out << "data111!!!"; + } + + const TString symlinkedFile = TFsPath(symlinkedDir).Child("origin_symlink"); + NFs::SymLink(originFile, symlinkedFile); + + TFsPath(symlinkedDir).ForceDelete(); + + UNIT_ASSERT(!NFs::Exists(symlinkedFile)); + UNIT_ASSERT(!NFs::Exists(symlinkedDir)); + UNIT_ASSERT(NFs::Exists(originFile)); + UNIT_ASSERT(NFs::Exists(originDir)); + } +#endif + + Y_UNIT_TEST(TestForceDeleteNonexisting) { + TTempDir tempDir; + TFsPath nonexisting = TFsPath(tempDir()).Child("nonexisting"); + nonexisting.ForceDelete(); + } + + // Here we want to test that all possible errors during TFsPath::ForceDelete + // are properly handled. To do so we have to trigger fs operation errors in + // three points: + // 1. stat/GetFileInformationByHandle + // 2. opendir + // 3. unlink/rmdir + // + // On unix systems we can achieve this by simply setting access rights on + // entry being deleted and its parent. But on windows it is more complicated. + // Current Chmod implementation on windows is not enough as it sets only + // FILE_ATTRIBUTE_READONLY throught SetFileAttributes call. But doing so does + // not affect directory access rights on older versions of Windows and Wine + // that we use to run autocheck tests. + // + // To get required access rights we use DACL in SetSecurityInfo. This is wrapped + // in RAII class that drops requested permissions on file/dir and grantss them + // back in destructor. + // + // Another obstacle is FILE_LIST_DIRECTORY permission when running on Wine. + // Dropping this permission is necessary to provoke error + // in GetFileInformationByHandle. Wine allows dropping this permission, but I + // have not found a way to grant it back. So tests crash during cleanup sequence. + // To make it possible to run this tests natively we detect Wine with special + // registry key and skip these tests only there. + +#ifdef _win_ + struct TLocalFree { + static void Destroy(void* ptr) { + LocalFree((HLOCAL)ptr); + } + }; + + bool IsWine() { + HKEY subKey = nullptr; + LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Wine", 0, KEY_READ, &subKey); + if (result == ERROR_SUCCESS) { + return true; + } + result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Wine", 0, KEY_READ, &subKey); + if (result == ERROR_SUCCESS) { + return true; + } + + HMODULE hntdll = GetModuleHandle("ntdll.dll"); + if (!hntdll) { + return false; + } + + auto func = GetProcAddress(hntdll, "wine_get_version"); + return func != nullptr; + } + + class TWinFileDenyAccessScope { + public: + TWinFileDenyAccessScope(const TFsPath& name, DWORD permissions) + : Name_(name) + , Perms_(permissions) + { + DWORD res = 0; + PACL oldAcl = nullptr; + PSECURITY_DESCRIPTOR sd = nullptr; + + res = GetNamedSecurityInfoA((LPSTR)name.c_str(), + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + nullptr, + nullptr, + &oldAcl, + nullptr, + &sd); + SdHolder_.Reset(sd); + if (res != ERROR_SUCCESS) { + ythrow TSystemError(res) << "error in GetNamedSecurityInfoA"; + } + + Acl_ = SetAcl(oldAcl, DENY_ACCESS); + } + + ~TWinFileDenyAccessScope() { + try { + const TFsPath parent = Name_.Parent(); + Chmod(parent.c_str(), MODE0777); + Chmod(Name_.c_str(), MODE0777); + SetAcl((PACL)Acl_.Get(), GRANT_ACCESS); + } catch (const yexception& ex) { + Cerr << "~TWinFileDenyAccessScope failed: " << ex.AsStrBuf() << Endl; + } + } + + THolder<void, TLocalFree> SetAcl(PACL oldAcl, ACCESS_MODE accessMode) { + DWORD res = 0; + EXPLICIT_ACCESS ea; + PACL newAcl = nullptr; + THolder<void, TLocalFree> newAclHolder; + + memset(&ea, 0, sizeof(EXPLICIT_ACCESS)); + ea.grfAccessPermissions = Perms_; + ea.grfAccessMode = accessMode; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME; + ea.Trustee.ptstrName = (LPSTR) "CURRENT_USER"; + + res = SetEntriesInAcl(1, &ea, oldAcl, &newAcl); + newAclHolder.Reset(newAcl); + if (res != ERROR_SUCCESS) { + ythrow TSystemError(res) << "error in SetEntriesInAcl"; + } + + res = SetNamedSecurityInfoA((LPSTR)Name_.c_str(), + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + nullptr, + nullptr, + newAcl, + nullptr); + if (res != ERROR_SUCCESS) { + ythrow TSystemError(res) << "error in SetNamedSecurityInfoA"; + } + + return std::move(newAclHolder); + } + + private: + const TFsPath Name_; + const DWORD Perms_; + THolder<void, TLocalFree> SdHolder_; + THolder<void, TLocalFree> Acl_; + }; +#endif + + Y_UNIT_TEST(TestForceDeleteErrorUnlink) { + TTempDir tempDir; + + const TFsPath testDir = TFsPath(tempDir()).Child("dir"); + MakePathIfNotExist(testDir.c_str()); + + const TFsPath testFile = testDir.Child("file"); + { + TFixedBufferFileOutput out(testFile); + out << "data111!!!"; + } + +#ifdef _win_ + Chmod(testFile.c_str(), S_IRUSR); + Y_DEFER { + Chmod(testFile.c_str(), MODE0777); + }; +#else + Chmod(testDir.c_str(), S_IRUSR | S_IXUSR); + Y_DEFER { + Chmod(testDir.c_str(), MODE0777); + }; +#endif + + UNIT_ASSERT_EXCEPTION_CONTAINS(testFile.ForceDelete(), TIoException, "failed to delete"); + } + + Y_UNIT_TEST(TestForceDeleteErrorRmdir) { + TTempDir tempDir; + + const TFsPath testDir = TFsPath(tempDir()).Child("dir"); + const TFsPath testSubdir = testDir.Child("file"); + MakePathIfNotExist(testSubdir.c_str()); + +#ifdef _win_ + Chmod(testSubdir.c_str(), 0); + Y_DEFER { + Chmod(testSubdir.c_str(), MODE0777); + }; + TWinFileDenyAccessScope dirAcl(testDir, FILE_WRITE_DATA); +#else + Chmod(testDir.c_str(), S_IRUSR | S_IXUSR); + Y_DEFER { + Chmod(testDir.c_str(), MODE0777); + }; +#endif + + UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to delete"); + } + + Y_UNIT_TEST(TestForceDeleteErrorStatDir) { + TTempDir tempDir; + + const TFsPath testDir = TFsPath(tempDir()).Child("dir"); + const TFsPath testSubdir = testDir.Child("file"); + MakePathIfNotExist(testSubdir.c_str()); + +#ifdef _win_ + if (IsWine()) { + // FILE_LIST_DIRECTORY seem to be irreversible on wine + return; + } + TWinFileDenyAccessScope subdirAcl(testSubdir, FILE_READ_ATTRIBUTES); + TWinFileDenyAccessScope dirAcl(testDir, FILE_LIST_DIRECTORY); +#else + Chmod(testDir.c_str(), 0); + Y_DEFER { + Chmod(testDir.c_str(), MODE0777); + }; +#endif + + UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to stat"); + } + + Y_UNIT_TEST(TestForceDeleteErrorStatFile) { + TTempDir tempDir; + + const TFsPath testDir = TFsPath(tempDir()).Child("dir"); + MakePathIfNotExist(testDir.c_str()); + + const TFsPath testFile = testDir.Child("file"); + { + TFixedBufferFileOutput out(testFile); + out << "data111!!!"; + } + +#ifdef _win_ + if (IsWine()) { + // FILE_LIST_DIRECTORY seem to be irreversible on wine + return; + } + TWinFileDenyAccessScope fileAcl(testFile, FILE_READ_ATTRIBUTES); + TWinFileDenyAccessScope dirAcl(testDir, FILE_LIST_DIRECTORY); +#else + Chmod(testDir.c_str(), 0); + Y_DEFER { + Chmod(testDir.c_str(), MODE0777); + }; +#endif + + UNIT_ASSERT_EXCEPTION_CONTAINS(testFile.ForceDelete(), TIoException, "failed to stat"); + } + + Y_UNIT_TEST(TestForceDeleteErrorListDir) { + TTempDir tempDir; + + const TFsPath testDir = TFsPath(tempDir()).Child("dir"); + const TFsPath testSubdir = testDir.Child("file"); + MakePathIfNotExist(testSubdir.c_str()); + +#ifdef _win_ + if (IsWine()) { + // FILE_LIST_DIRECTORY seem to be irreversible on wine + return; + } + TWinFileDenyAccessScope subdirAcl(testSubdir, FILE_LIST_DIRECTORY); +#else + Chmod(testSubdir.c_str(), 0); + Y_DEFER { + Chmod(testSubdir.c_str(), MODE0777); + }; +#endif + + UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to opendir"); + } + +#ifdef _unix_ + Y_UNIT_TEST(TestForceDeleteErrorSymlink) { + TTempDir tempDir; + + const TFsPath testDir = TFsPath(tempDir()).Child("dir"); + MakePathIfNotExist(testDir.c_str()); + + const TFsPath testSymlink = testDir.Child("symlink"); + NFs::SymLink("something", testSymlink); + + Chmod(testSymlink.c_str(), S_IRUSR); + Chmod(testDir.c_str(), S_IRUSR | S_IXUSR); + Y_DEFER { + Chmod(testDir.c_str(), MODE0777); + Chmod(testSymlink.c_str(), MODE0777); + }; + + UNIT_ASSERT_EXCEPTION_CONTAINS(testSymlink.ForceDelete(), TIoException, "failed to delete"); + } +#endif +} |