| // Copyright 2019 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "sandboxed_api/util/fileops.h" |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| |
| #include <cstdio> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "sandboxed_api/testing.h" |
| #include "sandboxed_api/util/file_helpers.h" |
| #include "sandboxed_api/util/status_matchers.h" |
| |
| using sapi::IsOk; |
| using testing::Eq; |
| using testing::IsEmpty; |
| using testing::IsFalse; |
| using testing::IsTrue; |
| using testing::Ne; |
| using testing::SizeIs; |
| using testing::StrEq; |
| |
| namespace sapi::file_util { |
| |
| // Forward declare functions that are only used in fileops.cc. |
| namespace fileops { |
| bool GetCWD(std::string* result); |
| bool RemoveLastPathComponent(const std::string& file, std::string* output); |
| } // namespace fileops |
| |
| namespace { |
| |
| class FileOpsTest : public testing::Test { |
| protected: |
| static void SetUpTestSuite() { |
| ASSERT_THAT(chdir(GetTestTempPath().c_str()), Eq(0)); |
| } |
| }; |
| |
| TEST_F(FileOpsTest, GetCWDTest) { |
| std::string result; |
| ASSERT_THAT(fileops::GetCWD(&result), IsTrue()); |
| EXPECT_THAT(result, StrEq(GetTestTempPath())); |
| } |
| |
| TEST_F(FileOpsTest, MakeAbsoluteTest) { |
| const auto tmp_dir = GetTestTempPath(); |
| ASSERT_THAT(chdir(tmp_dir.c_str()), Eq(0)); |
| EXPECT_THAT(fileops::MakeAbsolute("", ""), StrEq("")); |
| EXPECT_THAT(fileops::MakeAbsolute(".", ""), StrEq(tmp_dir)); |
| EXPECT_THAT(fileops::MakeAbsolute(".", tmp_dir), StrEq(tmp_dir)); |
| EXPECT_THAT(fileops::MakeAbsolute(".", "/"), StrEq("/")); |
| EXPECT_THAT(fileops::MakeAbsolute("/", tmp_dir), StrEq("/")); |
| EXPECT_THAT(fileops::MakeAbsolute("/", "/"), StrEq("/")); |
| EXPECT_THAT(fileops::MakeAbsolute("/", ""), StrEq("/")); |
| EXPECT_THAT(fileops::MakeAbsolute("/foo/bar", ""), StrEq("/foo/bar")); |
| EXPECT_THAT(fileops::MakeAbsolute("foo/bar", ""), |
| StrEq(absl::StrCat(tmp_dir, "/foo/bar"))); |
| EXPECT_THAT(fileops::MakeAbsolute("foo/bar", tmp_dir), |
| StrEq(absl::StrCat(tmp_dir, "/foo/bar"))); |
| EXPECT_THAT(fileops::MakeAbsolute("foo/bar", tmp_dir + "/"), |
| StrEq(absl::StrCat(tmp_dir, "/foo/bar"))); |
| } |
| |
| TEST_F(FileOpsTest, ExistsTest) { |
| ASSERT_THAT(file::SetContents("exists_test", "", file::Defaults()), IsOk()); |
| EXPECT_THAT(fileops::Exists("exists_test", false), IsTrue()); |
| EXPECT_THAT(fileops::Exists("exists_test", true), IsTrue()); |
| |
| ASSERT_THAT(symlink("exists_test", "exists_test_link"), Eq(0)); |
| EXPECT_THAT(fileops::Exists("exists_test_link", false), IsTrue()); |
| EXPECT_THAT(fileops::Exists("exists_test_link", true), IsTrue()); |
| |
| ASSERT_THAT(unlink("exists_test"), Eq(0)); |
| EXPECT_THAT(fileops::Exists("exists_test_link", false), IsTrue()); |
| EXPECT_THAT(fileops::Exists("exists_test_link", true), IsFalse()); |
| |
| ASSERT_THAT(unlink("exists_test_link"), Eq(0)); |
| EXPECT_THAT(fileops::Exists("exists_test_link", false), IsFalse()); |
| EXPECT_THAT(fileops::Exists("exists_test_link", true), IsFalse()); |
| } |
| |
| TEST_F(FileOpsTest, ReadLinkTest) { |
| EXPECT_THAT(fileops::ReadLink("readlink_not_there"), StrEq("")); |
| EXPECT_THAT(errno, Eq(ENOENT)); |
| |
| ASSERT_THAT(file::SetContents("readlink_file", "", file::Defaults()), IsOk()); |
| EXPECT_THAT(fileops::ReadLink("readlink_file"), StrEq("")); |
| unlink("readlink_file"); |
| |
| ASSERT_THAT(symlink("..", "readlink_dotdot"), Eq(0)); |
| EXPECT_THAT(fileops::ReadLink("readlink_dotdot"), StrEq("..")); |
| unlink("readlink_dotdot"); |
| |
| ASSERT_THAT(symlink("../", "readlink_dotdotslash"), 0); |
| EXPECT_THAT(fileops::ReadLink("readlink_dotdotslash"), "../"); |
| unlink("readlink_dotdotslash"); |
| |
| ASSERT_THAT(symlink("/", "readlink_slash"), 0); |
| EXPECT_THAT(fileops::ReadLink("readlink_slash"), "/"); |
| unlink("readlink_slash"); |
| |
| const std::string very_long_name(PATH_MAX - 1, 'f'); |
| ASSERT_THAT(symlink(very_long_name.c_str(), "readlink_long"), Eq(0)); |
| EXPECT_THAT(fileops::ReadLink("readlink_long"), StrEq(very_long_name)); |
| unlink("readlink_long"); |
| } |
| |
| TEST_F(FileOpsTest, ListDirectoryEntriesFailTest) { |
| std::vector<std::string> files; |
| std::string error; |
| |
| EXPECT_THAT(fileops::ListDirectoryEntries("new_dir", &files, &error), |
| IsFalse()); |
| EXPECT_THAT(files, IsEmpty()); |
| EXPECT_THAT(error, StrEq("opendir(new_dir): No such file or directory")); |
| } |
| |
| TEST_F(FileOpsTest, ListDirectoryEntriesEmptyTest) { |
| std::vector<std::string> files; |
| std::string error; |
| |
| ASSERT_THAT(mkdir("new_dir", 0700), Eq(0)); |
| |
| EXPECT_THAT(fileops::ListDirectoryEntries("new_dir", &files, &error), |
| IsTrue()); |
| EXPECT_THAT(files, IsEmpty()); |
| |
| rmdir("new_dir"); |
| } |
| |
| TEST_F(FileOpsTest, ListDirectoryEntriesOneFileTest) { |
| ASSERT_THAT(mkdir("new_dir", 0700), Eq(0)); |
| ASSERT_THAT(file::SetContents("new_dir/first", "", file::Defaults()), IsOk()); |
| |
| std::vector<std::string> files; |
| std::string error; |
| EXPECT_THAT(fileops::ListDirectoryEntries("new_dir", &files, &error), |
| IsTrue()); |
| |
| unlink("new_dir/first"); |
| rmdir("new_dir"); |
| |
| ASSERT_THAT(files, SizeIs(1)); |
| EXPECT_THAT(files[0], "first"); |
| } |
| |
| TEST_F(FileOpsTest, ListDirectoryEntriesTest) { |
| ASSERT_THAT(mkdir("new_dir", 0700), Eq(0)); |
| constexpr int kNumFiles = 10; |
| for (int i = 0; i < kNumFiles; ++i) { |
| ASSERT_THAT(file::SetContents(absl::StrCat("new_dir/file", i), "", |
| file::Defaults()), |
| IsOk()); |
| } |
| |
| std::vector<std::string> files; |
| std::string error; |
| EXPECT_THAT(fileops::ListDirectoryEntries("new_dir", &files, &error), |
| IsTrue()); |
| |
| fileops::DeleteRecursively("new_dir"); |
| |
| ASSERT_THAT(files, SizeIs(kNumFiles)); |
| std::sort(files.begin(), files.end()); |
| for (int i = 0; i < kNumFiles; ++i) { |
| EXPECT_THAT(files[i], StrEq(absl::StrCat("file", i))); |
| } |
| } |
| |
| TEST_F(FileOpsTest, RemoveLastPathComponentTest) { |
| std::string result; |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/", &result), IsFalse()); |
| EXPECT_THAT(result, StrEq("/")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("///", &result), IsFalse()); |
| EXPECT_THAT(result, StrEq("/")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home", &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("/")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home/", &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("/")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home///", &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("/")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home///", &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("/")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("///home///", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home/someone", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/home")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home///someone", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/home")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home///someone/", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/home")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home///someone//", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/home")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home/someone/file", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/home/someone")); |
| |
| EXPECT_THAT( |
| fileops::RemoveLastPathComponent("/home/someone////file", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/home/someone")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home///someone/file", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/home///someone")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/home/someone/file", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("/home/someone")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("no_root", &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("no_root/", &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("no_root///", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("/file", &result), IsTrue()); |
| EXPECT_THAT(result, "/"); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("no_root/file", &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq("no_root")); |
| |
| result = "no_root"; |
| EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("")); |
| |
| result = "no_root/"; |
| EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("")); |
| |
| result = "no_root///"; |
| EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("")); |
| |
| result = "/file"; |
| EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("/")); |
| |
| result = "no_root/file"; |
| EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); |
| EXPECT_THAT(result, StrEq("no_root")); |
| |
| EXPECT_THAT(fileops::RemoveLastPathComponent("", &result), IsFalse()); |
| EXPECT_THAT(result, StrEq("")); |
| } |
| |
| TEST_F(FileOpsTest, TestBasename) { |
| EXPECT_THAT(fileops::Basename(""), StrEq("")); |
| EXPECT_THAT(fileops::Basename("/"), StrEq("")); |
| EXPECT_THAT(fileops::Basename("//"), StrEq("")); |
| EXPECT_THAT(fileops::Basename("/hello/"), StrEq("")); |
| EXPECT_THAT(fileops::Basename("//hello"), StrEq("hello")); |
| EXPECT_THAT(fileops::Basename("/hello/world"), StrEq("world")); |
| EXPECT_THAT(fileops::Basename("/hello, world"), StrEq("hello, world")); |
| } |
| |
| TEST_F(FileOpsTest, TestStripBasename) { |
| EXPECT_THAT(fileops::StripBasename(""), StrEq("")); |
| EXPECT_THAT(fileops::StripBasename("/"), StrEq("/")); |
| EXPECT_THAT(fileops::StripBasename("//"), StrEq("/")); |
| EXPECT_THAT(fileops::StripBasename("/hello"), StrEq("/")); |
| EXPECT_THAT(fileops::StripBasename("//hello"), StrEq("/")); |
| EXPECT_THAT(fileops::StripBasename("/hello/"), StrEq("/hello")); |
| EXPECT_THAT(fileops::StripBasename("/hello//"), StrEq("/hello/")); |
| EXPECT_THAT(fileops::StripBasename("/hello/world"), StrEq("/hello")); |
| EXPECT_THAT(fileops::StripBasename("/hello, world"), StrEq("/")); |
| } |
| |
| void SetupDirectory() { |
| ASSERT_THAT(mkdir("foo", 0755), Eq(0)); |
| ASSERT_THAT(mkdir("foo/bar", 0755), Eq(0)); |
| ASSERT_THAT(mkdir("foo/baz", 0755), Eq(0)); |
| ASSERT_THAT(file::SetContents("foo/quux", "", file::Defaults()), IsOk()); |
| ASSERT_THAT(chmod("foo/quux", 0644), Eq(0)); |
| |
| ASSERT_THAT(file::SetContents("foo/bar/foo", "", file::Defaults()), IsOk()); |
| ASSERT_THAT(chmod("foo/bar/foo", 0644), Eq(0)); |
| ASSERT_THAT(file::SetContents("foo/bar/bar", "", file::Defaults()), IsOk()); |
| ASSERT_THAT(chmod("foo/bar/bar", 0644), Eq(0)); |
| |
| ASSERT_THAT(mkdir("foo/bar/baz", 0755), Eq(0)); |
| ASSERT_THAT(file::SetContents("foo/bar/baz/foo", "", file::Defaults()), |
| IsOk()); |
| ASSERT_THAT(chmod("foo/bar/baz/foo", 0644), Eq(0)); |
| } |
| |
| TEST_F(FileOpsTest, DeleteRecursivelyTest) { |
| EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); |
| EXPECT_THAT(fileops::DeleteRecursively("/not_there"), IsTrue()); |
| |
| // Can't stat file |
| SetupDirectory(); |
| ASSERT_THAT(chmod("foo/bar/baz", 0000), Eq(0)); |
| EXPECT_THAT(fileops::DeleteRecursively("foo/bar/baz/quux"), IsFalse()); |
| EXPECT_THAT(errno, Eq(EACCES)); |
| ASSERT_THAT(chmod("foo/bar/baz", 0755), Eq(0)); |
| |
| EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); |
| struct stat64 st; |
| EXPECT_THAT(lstat64("foo", &st), Ne(0)); |
| |
| // Can't list subdirectory |
| SetupDirectory(); |
| ASSERT_THAT(chmod("foo/bar/baz", 0000), Eq(0)); |
| EXPECT_THAT(fileops::DeleteRecursively("foo"), IsFalse()); |
| EXPECT_THAT(errno, Eq(EACCES)); |
| ASSERT_THAT(chmod("foo/bar/baz", 0755), Eq(0)); |
| |
| EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); |
| |
| // Can't delete file |
| SetupDirectory(); |
| ASSERT_THAT(chmod("foo/bar/baz", 0500), Eq(0)); |
| EXPECT_THAT(fileops::DeleteRecursively("foo"), IsFalse()); |
| EXPECT_THAT(errno, Eq(EACCES)); |
| ASSERT_THAT(chmod("foo/bar/baz", 0755), Eq(0)); |
| |
| EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); |
| |
| // Can't delete directory |
| SetupDirectory(); |
| ASSERT_THAT(fileops::DeleteRecursively("foo/bar/baz/foo"), IsTrue()); |
| ASSERT_THAT(chmod("foo/bar", 0500), Eq(0)); |
| EXPECT_THAT(fileops::DeleteRecursively("foo/bar/baz"), IsFalse()); |
| EXPECT_THAT(errno, Eq(EACCES)); |
| ASSERT_THAT(chmod("foo/bar", 0755), Eq(0)); |
| |
| EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); |
| } |
| |
| TEST_F(FileOpsTest, ReadLinkAbsoluteTest) { |
| const auto tmp_dir = GetTestTempPath(); |
| ASSERT_THAT(chdir(tmp_dir.c_str()), Eq(0)); |
| |
| EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); |
| ASSERT_THAT(symlink("rel/path", "foo"), Eq(0)); |
| |
| const std::string expected_path = absl::StrCat(tmp_dir, "/rel/path"); |
| const std::string expected_path2 = absl::StrCat(tmp_dir, "/./rel/path"); |
| std::string result; |
| EXPECT_THAT(fileops::ReadLinkAbsolute("foo", &result), IsTrue()); |
| EXPECT_THAT(result, StrEq(expected_path)); |
| EXPECT_THAT(fileops::ReadLinkAbsolute("./foo", &result), IsTrue()); |
| EXPECT_THAT(result, StrEq(expected_path2)); |
| EXPECT_THAT(fileops::ReadLinkAbsolute(absl::StrCat(tmp_dir, "/foo"), &result), |
| IsTrue()); |
| EXPECT_THAT(result, StrEq(expected_path)); |
| |
| result.clear(); |
| EXPECT_THAT(fileops::ReadLinkAbsolute("/not_there", &result), IsFalse()); |
| EXPECT_THAT(result, IsEmpty()); |
| } |
| |
| TEST_F(FileOpsTest, CopyFileTest) { |
| const auto tmp_dir = GetTestTempPath(); |
| // Non-existent source |
| EXPECT_THAT( |
| fileops::CopyFile("/not/there", absl::StrCat(tmp_dir, "/out"), 0777), |
| IsFalse()); |
| |
| // Unwritable target |
| EXPECT_THAT(fileops::CopyFile("/proc/self/exe", tmp_dir, 0777), IsFalse()); |
| |
| EXPECT_THAT(file::SetContents(absl::StrCat(tmp_dir, "/test"), "test\n", |
| file::Defaults()), |
| IsOk()); |
| EXPECT_THAT(fileops::CopyFile(absl::StrCat(tmp_dir, "/test"), |
| absl::StrCat(tmp_dir, "/test2"), 0666), |
| IsTrue()); |
| |
| std::string text; |
| EXPECT_THAT(file::GetContents(absl::StrCat(tmp_dir, "/test2"), &text, |
| file::Defaults()), |
| IsOk()); |
| |
| EXPECT_THAT(text, StrEq("test\n")); |
| |
| unlink((absl::StrCat(tmp_dir, "/test")).c_str()); |
| unlink((absl::StrCat(tmp_dir, "/test2")).c_str()); |
| } |
| |
| } // namespace |
| } // namespace sapi::file_util |