| // Copyright 2014 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "brillo/file_utils.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <string> |
| |
| #include <base/check.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/rand_util.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <gtest/gtest.h> |
| |
| namespace brillo { |
| |
| namespace { |
| |
| constexpr int kPermissions600 = |
| base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER; |
| constexpr int kPermissions700 = base::FILE_PERMISSION_USER_MASK; |
| constexpr int kPermissions777 = base::FILE_PERMISSION_MASK; |
| constexpr int kPermissions755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; |
| |
| std::string GetRandomSuffix() { |
| const int kBufferSize = 6; |
| unsigned char buffer[kBufferSize]; |
| base::RandBytes(buffer, base::size(buffer)); |
| return base::HexEncode(buffer, base::size(buffer)); |
| } |
| |
| bool IsNonBlockingFD(int fd) { |
| return fcntl(fd, F_GETFL) & O_NONBLOCK; |
| } |
| |
| } // namespace |
| |
| class FileUtilsTest : public testing::Test { |
| public: |
| FileUtilsTest() { |
| CHECK(temp_dir_.CreateUniqueTempDir()); |
| file_path_ = temp_dir_.GetPath().Append("test.temp"); |
| } |
| |
| protected: |
| base::FilePath file_path_; |
| base::ScopedTempDir temp_dir_; |
| |
| // Writes |contents| to |file_path_|. Pulled into a separate function just |
| // to improve readability of tests. |
| void WriteFile(const std::string& contents) { |
| EXPECT_EQ(contents.length(), |
| base::WriteFile(file_path_, contents.c_str(), contents.length())); |
| } |
| |
| // Verifies that the file at |file_path_| exists and contains |contents|. |
| void ExpectFileContains(const std::string& contents) { |
| EXPECT_TRUE(base::PathExists(file_path_)); |
| std::string new_contents; |
| EXPECT_TRUE(base::ReadFileToString(file_path_, &new_contents)); |
| EXPECT_EQ(contents, new_contents); |
| } |
| |
| // Verifies that the file at |file_path_| has |permissions|. |
| void ExpectFilePermissions(int permissions) { |
| int actual_permissions; |
| EXPECT_TRUE(base::GetPosixFilePermissions(file_path_, &actual_permissions)); |
| EXPECT_EQ(permissions, actual_permissions); |
| } |
| |
| // Creates a file with a random name in the temporary directory. |
| base::FilePath GetTempName() { |
| return temp_dir_.GetPath().Append(GetRandomSuffix()); |
| } |
| }; |
| |
| TEST_F(FileUtilsTest, TouchFileCreate) { |
| EXPECT_TRUE(TouchFile(file_path_)); |
| ExpectFileContains(""); |
| ExpectFilePermissions(kPermissions600); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileCreateThroughUmask) { |
| mode_t old_umask = umask(kPermissions777); |
| EXPECT_TRUE(TouchFile(file_path_)); |
| umask(old_umask); |
| ExpectFileContains(""); |
| ExpectFilePermissions(kPermissions600); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileCreateDirectoryStructure) { |
| file_path_ = temp_dir_.GetPath().Append("foo/bar/baz/test.temp"); |
| EXPECT_TRUE(TouchFile(file_path_)); |
| ExpectFileContains(""); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileExisting) { |
| WriteFile("abcd"); |
| EXPECT_TRUE(TouchFile(file_path_)); |
| ExpectFileContains("abcd"); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileReplaceDirectory) { |
| EXPECT_TRUE(base::CreateDirectory(file_path_)); |
| EXPECT_TRUE(TouchFile(file_path_)); |
| EXPECT_FALSE(base::DirectoryExists(file_path_)); |
| ExpectFileContains(""); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileReplaceSymlink) { |
| base::FilePath symlink_target = temp_dir_.GetPath().Append("target.temp"); |
| EXPECT_TRUE(base::CreateSymbolicLink(symlink_target, file_path_)); |
| EXPECT_TRUE(TouchFile(file_path_)); |
| EXPECT_FALSE(base::IsLink(file_path_)); |
| ExpectFileContains(""); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileReplaceOtherUser) { |
| WriteFile("abcd"); |
| EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid() + 1, getegid())); |
| ExpectFileContains(""); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileReplaceOtherGroup) { |
| WriteFile("abcd"); |
| EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid() + 1)); |
| ExpectFileContains(""); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileCreateWithAllPermissions) { |
| EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid())); |
| ExpectFileContains(""); |
| ExpectFilePermissions(kPermissions777); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileCreateWithOwnerPermissions) { |
| EXPECT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); |
| ExpectFileContains(""); |
| ExpectFilePermissions(kPermissions700); |
| } |
| |
| TEST_F(FileUtilsTest, TouchFileExistingPermissionsUnchanged) { |
| EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid())); |
| EXPECT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); |
| ExpectFileContains(""); |
| ExpectFilePermissions(kPermissions777); |
| } |
| |
| // Other parts of OpenSafely are tested in Arcsetup.TestInstallDirectory*. |
| TEST_F(FileUtilsTest, TestOpenSafelyWithoutNonblocking) { |
| ASSERT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); |
| base::ScopedFD fd(OpenSafely(file_path_, O_RDONLY, 0)); |
| EXPECT_TRUE(fd.is_valid()); |
| EXPECT_FALSE(IsNonBlockingFD(fd.get())); |
| } |
| |
| TEST_F(FileUtilsTest, TestOpenSafelyWithNonblocking) { |
| ASSERT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); |
| base::ScopedFD fd = OpenSafely(file_path_, O_RDONLY | O_NONBLOCK, 0); |
| EXPECT_TRUE(fd.is_valid()); |
| EXPECT_TRUE(IsNonBlockingFD(fd.get())); |
| } |
| |
| TEST_F(FileUtilsTest, TestOpenFifoSafelySuccess) { |
| ASSERT_EQ(0, mkfifo(file_path_.value().c_str(), kPermissions700)); |
| base::ScopedFD fd(OpenFifoSafely(file_path_, O_RDONLY, 0)); |
| EXPECT_TRUE(fd.is_valid()); |
| EXPECT_FALSE(IsNonBlockingFD(fd.get())); |
| } |
| |
| TEST_F(FileUtilsTest, TestOpenFifoSafelyRegularFile) { |
| ASSERT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); |
| base::ScopedFD fd = OpenFifoSafely(file_path_, O_RDONLY, 0); |
| EXPECT_FALSE(fd.is_valid()); |
| } |
| |
| TEST_F(FileUtilsTest, TestMkdirRecursivelyRoot) { |
| // Try to create an existing directory ("/") should still succeed. |
| EXPECT_TRUE( |
| MkdirRecursively(base::FilePath("/"), kPermissions755).is_valid()); |
| } |
| |
| TEST_F(FileUtilsTest, TestMkdirRecursivelySuccess) { |
| // Set |temp_directory| to 0707. |
| EXPECT_TRUE(base::SetPosixFilePermissions(temp_dir_.GetPath(), 0707)); |
| |
| EXPECT_TRUE( |
| MkdirRecursively(temp_dir_.GetPath().Append("a/b/c"), kPermissions755) |
| .is_valid()); |
| // Confirm the 3 directories are there. |
| EXPECT_TRUE(base::DirectoryExists(temp_dir_.GetPath().Append("a"))); |
| EXPECT_TRUE(base::DirectoryExists(temp_dir_.GetPath().Append("a/b"))); |
| EXPECT_TRUE(base::DirectoryExists(temp_dir_.GetPath().Append("a/b/c"))); |
| |
| // Confirm that the newly created directories have 0755 mode. |
| int mode = 0; |
| EXPECT_TRUE( |
| base::GetPosixFilePermissions(temp_dir_.GetPath().Append("a"), &mode)); |
| EXPECT_EQ(kPermissions755, mode); |
| mode = 0; |
| EXPECT_TRUE( |
| base::GetPosixFilePermissions(temp_dir_.GetPath().Append("a/b"), &mode)); |
| EXPECT_EQ(kPermissions755, mode); |
| mode = 0; |
| EXPECT_TRUE(base::GetPosixFilePermissions(temp_dir_.GetPath().Append("a/b/c"), |
| &mode)); |
| EXPECT_EQ(kPermissions755, mode); |
| |
| // Confirm that the existing directory |temp_directory| still has 0707 mode. |
| mode = 0; |
| EXPECT_TRUE(base::GetPosixFilePermissions(temp_dir_.GetPath(), &mode)); |
| EXPECT_EQ(0707, mode); |
| |
| // Call the API again which should still succeed. |
| EXPECT_TRUE( |
| MkdirRecursively(temp_dir_.GetPath().Append("a/b/c"), kPermissions755) |
| .is_valid()); |
| EXPECT_TRUE( |
| MkdirRecursively(temp_dir_.GetPath().Append("a/b/c/d"), kPermissions755) |
| .is_valid()); |
| EXPECT_TRUE(base::DirectoryExists(temp_dir_.GetPath().Append("a/b/c/d"))); |
| mode = 0; |
| EXPECT_TRUE(base::GetPosixFilePermissions( |
| temp_dir_.GetPath().Append("a/b/c/d"), &mode)); |
| EXPECT_EQ(kPermissions755, mode); |
| |
| // Call the API again which should still succeed. |
| EXPECT_TRUE( |
| MkdirRecursively(temp_dir_.GetPath().Append("a/b"), kPermissions755) |
| .is_valid()); |
| EXPECT_TRUE(MkdirRecursively(temp_dir_.GetPath().Append("a"), kPermissions755) |
| .is_valid()); |
| } |
| |
| TEST_F(FileUtilsTest, TestMkdirRecursivelyRelativePath) { |
| // Try to pass a relative or empty directory. They should all fail. |
| EXPECT_FALSE( |
| MkdirRecursively(base::FilePath("foo"), kPermissions755).is_valid()); |
| EXPECT_FALSE( |
| MkdirRecursively(base::FilePath("bar/"), kPermissions755).is_valid()); |
| EXPECT_FALSE(MkdirRecursively(base::FilePath(), kPermissions755).is_valid()); |
| } |
| |
| TEST_F(FileUtilsTest, WriteFileCanBeReadBack) { |
| const base::FilePath filename(GetTempName()); |
| const std::string content("blablabla"); |
| EXPECT_TRUE(WriteStringToFile(filename, content)); |
| std::string output; |
| EXPECT_TRUE(ReadFileToString(filename, &output)); |
| EXPECT_EQ(content, output); |
| } |
| |
| TEST_F(FileUtilsTest, WriteFileSets0666) { |
| const mode_t mask = 0000; |
| const mode_t mode = 0666; |
| const base::FilePath filename(GetTempName()); |
| const std::string content("blablabla"); |
| const mode_t old_mask = umask(mask); |
| EXPECT_TRUE(WriteStringToFile(filename, content)); |
| int file_mode = 0; |
| EXPECT_TRUE(base::GetPosixFilePermissions(filename, &file_mode)); |
| EXPECT_EQ(mode & ~mask, file_mode & 0777); |
| umask(old_mask); |
| } |
| |
| TEST_F(FileUtilsTest, WriteFileCreatesMissingParentDirectoriesWith0700) { |
| const mode_t mask = 0000; |
| const mode_t mode = 0700; |
| const base::FilePath dirname(GetTempName()); |
| const base::FilePath subdirname(dirname.Append(GetRandomSuffix())); |
| const base::FilePath filename(subdirname.Append(GetRandomSuffix())); |
| const std::string content("blablabla"); |
| EXPECT_TRUE(WriteStringToFile(filename, content)); |
| int dir_mode = 0; |
| int subdir_mode = 0; |
| EXPECT_TRUE(base::GetPosixFilePermissions(dirname, &dir_mode)); |
| EXPECT_TRUE(base::GetPosixFilePermissions(subdirname, &subdir_mode)); |
| EXPECT_EQ(mode & ~mask, dir_mode & 0777); |
| EXPECT_EQ(mode & ~mask, subdir_mode & 0777); |
| const mode_t old_mask = umask(mask); |
| umask(old_mask); |
| } |
| |
| TEST_F(FileUtilsTest, WriteToFileAtomicCanBeReadBack) { |
| const base::FilePath filename(GetTempName()); |
| const std::string content("blablabla"); |
| EXPECT_TRUE( |
| WriteToFileAtomic(filename, content.data(), content.size(), 0644)); |
| std::string output; |
| EXPECT_TRUE(ReadFileToString(filename, &output)); |
| EXPECT_EQ(content, output); |
| } |
| |
| TEST_F(FileUtilsTest, WriteToFileAtomicHonorsMode) { |
| const mode_t mask = 0000; |
| const mode_t mode = 0616; |
| const base::FilePath filename(GetTempName()); |
| const std::string content("blablabla"); |
| const mode_t old_mask = umask(mask); |
| EXPECT_TRUE( |
| WriteToFileAtomic(filename, content.data(), content.size(), mode)); |
| int file_mode = 0; |
| EXPECT_TRUE(base::GetPosixFilePermissions(filename, &file_mode)); |
| EXPECT_EQ(mode & ~mask, file_mode & 0777); |
| umask(old_mask); |
| } |
| |
| TEST_F(FileUtilsTest, WriteToFileAtomicHonorsUmask) { |
| const mode_t mask = 0073; |
| const mode_t mode = 0777; |
| const base::FilePath filename(GetTempName()); |
| const std::string content("blablabla"); |
| const mode_t old_mask = umask(mask); |
| EXPECT_TRUE( |
| WriteToFileAtomic(filename, content.data(), content.size(), mode)); |
| int file_mode = 0; |
| EXPECT_TRUE(base::GetPosixFilePermissions(filename, &file_mode)); |
| EXPECT_EQ(mode & ~mask, file_mode & 0777); |
| umask(old_mask); |
| } |
| |
| TEST_F(FileUtilsTest, |
| WriteToFileAtomicCreatesMissingParentDirectoriesWith0700) { |
| const mode_t mask = 0000; |
| const mode_t mode = 0700; |
| const base::FilePath dirname(GetTempName()); |
| const base::FilePath subdirname(dirname.Append(GetRandomSuffix())); |
| const base::FilePath filename(subdirname.Append(GetRandomSuffix())); |
| const std::string content("blablabla"); |
| EXPECT_TRUE( |
| WriteToFileAtomic(filename, content.data(), content.size(), 0777)); |
| int dir_mode = 0; |
| int subdir_mode = 0; |
| EXPECT_TRUE(base::GetPosixFilePermissions(dirname, &dir_mode)); |
| EXPECT_TRUE(base::GetPosixFilePermissions(subdirname, &subdir_mode)); |
| EXPECT_EQ(mode & ~mask, dir_mode & 0777); |
| EXPECT_EQ(mode & ~mask, subdir_mode & 0777); |
| const mode_t old_mask = umask(mask); |
| umask(old_mask); |
| } |
| |
| TEST_F(FileUtilsTest, ComputeDirectoryDiskUsageNormalRandomFile) { |
| // 2MB test file. |
| constexpr size_t kFileSize = 2 * 1024 * 1024; |
| |
| const base::FilePath dirname(GetTempName()); |
| EXPECT_TRUE(base::CreateDirectory(dirname)); |
| const base::FilePath filename = dirname.Append("test.temp"); |
| |
| std::string file_content = base::RandBytesAsString(kFileSize); |
| EXPECT_TRUE(WriteStringToFile(filename, file_content)); |
| |
| int64_t result_usage = ComputeDirectoryDiskUsage(dirname); |
| int64_t result_size = base::ComputeDirectorySize(dirname); |
| |
| // result_usage (what we are testing here) should be within +/-10% of ground |
| // truth. The variation is to account for filesystem overhead variations. |
| EXPECT_GT(result_usage, kFileSize / 10 * 9); |
| EXPECT_LT(result_usage, kFileSize / 10 * 11); |
| |
| // result_usage should be close to result_size, because the test file is |
| // random so it's disk usage should be similar to apparent size. |
| EXPECT_GT(result_usage, result_size / 10 * 9); |
| EXPECT_LT(result_usage, result_size / 10 * 11); |
| } |
| |
| TEST_F(FileUtilsTest, ComputeDirectoryDiskUsageDeepRandomFile) { |
| // 2MB test file. |
| constexpr size_t kFileSize = 2 * 1024 * 1024; |
| |
| const base::FilePath dirname(GetTempName()); |
| EXPECT_TRUE(base::CreateDirectory(dirname)); |
| base::FilePath currentlevel = dirname; |
| for (int i = 0; i < 10; i++) { |
| base::FilePath nextlevel = currentlevel.Append("test.dir"); |
| EXPECT_TRUE(base::CreateDirectory(nextlevel)); |
| currentlevel = nextlevel; |
| } |
| const base::FilePath filename = currentlevel.Append("test.temp"); |
| |
| std::string file_content = base::RandBytesAsString(kFileSize); |
| EXPECT_TRUE(WriteStringToFile(filename, file_content)); |
| |
| int64_t result_usage = ComputeDirectoryDiskUsage(dirname); |
| int64_t result_size = base::ComputeDirectorySize(dirname); |
| |
| // result_usage (what we are testing here) should be within +/-10% of ground |
| // truth. The variation is to account for filesystem overhead variations. |
| EXPECT_GT(result_usage, kFileSize / 10 * 9); |
| EXPECT_LT(result_usage, kFileSize / 10 * 11); |
| |
| // result_usage should be close to result_size, because the test file is |
| // random so it's disk usage should be similar to apparent size. |
| EXPECT_GT(result_usage, result_size / 10 * 9); |
| EXPECT_LT(result_usage, result_size / 10 * 11); |
| } |
| |
| TEST_F(FileUtilsTest, ComputeDirectoryDiskUsageHiddenRandomFile) { |
| // 2MB test file. |
| constexpr size_t kFileSize = 2 * 1024 * 1024; |
| |
| const base::FilePath dirname(GetTempName()); |
| EXPECT_TRUE(base::CreateDirectory(dirname)); |
| // File name starts with a dot, so it's a hidden file. |
| const base::FilePath filename = dirname.Append(".test.temp"); |
| |
| std::string file_content = base::RandBytesAsString(kFileSize); |
| EXPECT_TRUE(WriteStringToFile(filename, file_content)); |
| |
| int64_t result_usage = ComputeDirectoryDiskUsage(dirname); |
| int64_t result_size = base::ComputeDirectorySize(dirname); |
| |
| // result_usage (what we are testing here) should be within +/-10% of ground |
| // truth. The variation is to account for filesystem overhead variations. |
| EXPECT_GT(result_usage, kFileSize / 10 * 9); |
| EXPECT_LT(result_usage, kFileSize / 10 * 11); |
| |
| // result_usage should be close to result_size, because the test file is |
| // random so it's disk usage should be similar to apparent size. |
| EXPECT_GT(result_usage, result_size / 10 * 9); |
| EXPECT_LT(result_usage, result_size / 10 * 11); |
| } |
| |
| TEST_F(FileUtilsTest, ComputeDirectoryDiskUsageSparseFile) { |
| // 128MB sparse test file. |
| constexpr size_t kFileSize = 128 * 1024 * 1024; |
| constexpr size_t kFileSizeThreshold = 64 * 1024; |
| |
| const base::FilePath dirname(GetTempName()); |
| EXPECT_TRUE(base::CreateDirectory(dirname)); |
| const base::FilePath filename = dirname.Append("test.temp"); |
| |
| int fd = |
| open(filename.value().c_str(), O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); |
| EXPECT_NE(fd, -1); |
| // Calling ftruncate on an empty file will create a sparse file. |
| EXPECT_EQ(0, ftruncate(fd, kFileSize)); |
| |
| int64_t result_usage = ComputeDirectoryDiskUsage(dirname); |
| int64_t result_size = base::ComputeDirectorySize(dirname); |
| |
| // result_usage (what we are testing here) should be less than |
| // kFileSizeThreshold, the threshold is to account for filesystem overhead |
| // variations. |
| EXPECT_LT(result_usage, kFileSizeThreshold); |
| |
| // Since we are dealing with sparse files here, the apparent size should be |
| // much much larger than the actual disk usage. |
| EXPECT_LT(result_usage, result_size / 1000); |
| } |
| |
| TEST_F(FileUtilsTest, ComputeDirectoryDiskUsageSymlinkFile) { |
| // 2MB test file. |
| constexpr size_t kFileSize = 2 * 1024 * 1024; |
| |
| const base::FilePath dirname(GetTempName()); |
| EXPECT_TRUE(base::CreateDirectory(dirname)); |
| const base::FilePath filename = dirname.Append("test.temp"); |
| const base::FilePath linkname = dirname.Append("test.link"); |
| |
| std::string file_content = base::RandBytesAsString(kFileSize); |
| EXPECT_TRUE(WriteStringToFile(filename, file_content)); |
| |
| // Create a symlink. |
| EXPECT_TRUE(base::CreateSymbolicLink(filename, linkname)); |
| |
| int64_t result_usage = ComputeDirectoryDiskUsage(dirname); |
| |
| // result_usage (what we are testing here) should be within +/-10% of ground |
| // truth. The variation is to account for filesystem overhead variations. |
| // Note that it's not 2x kFileSize because symblink is not counted twice. |
| EXPECT_GT(result_usage, kFileSize / 10 * 9); |
| EXPECT_LT(result_usage, kFileSize / 10 * 11); |
| } |
| |
| TEST_F(FileUtilsTest, ComputeDirectoryDiskUsageSymlinkDir) { |
| // 2MB test file. |
| constexpr size_t kFileSize = 2 * 1024 * 1024; |
| |
| const base::FilePath parentname(GetTempName()); |
| EXPECT_TRUE(base::CreateDirectory(parentname)); |
| const base::FilePath dirname = parentname.Append("target.dir"); |
| EXPECT_TRUE(base::CreateDirectory(dirname)); |
| const base::FilePath linkname = parentname.Append("link.dir"); |
| |
| const base::FilePath filename = dirname.Append("test.temp"); |
| |
| std::string file_content = base::RandBytesAsString(kFileSize); |
| EXPECT_TRUE(WriteStringToFile(filename, file_content)); |
| |
| // Create a symlink. |
| EXPECT_TRUE(base::CreateSymbolicLink(dirname, linkname)); |
| |
| int64_t result_usage = ComputeDirectoryDiskUsage(dirname); |
| |
| // result_usage (what we are testing here) should be within +/-10% of ground |
| // truth. The variation is to account for filesystem overhead variations. |
| // Note that it's not 2x kFileSize because symblink is not counted twice. |
| EXPECT_GT(result_usage, kFileSize / 10 * 9); |
| EXPECT_LT(result_usage, kFileSize / 10 * 11); |
| } |
| |
| } // namespace brillo |