/* * Copyright (C) 2022 The Android Open Source Project * * 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 "file_utils.h" #include #include #include #include #include #include #include "aidl/com/android/server/art/FsPermission.h" #include "android-base/errors.h" #include "android-base/file.h" #include "android-base/result-gmock.h" #include "android-base/result.h" #include "base/common_art_test.h" #include "gmock/gmock.h" #include "gtest/gtest.h" namespace art { namespace artd { namespace { using ::aidl::com::android::server::art::FsPermission; using ::android::base::Error; using ::android::base::ReadFileToString; using ::android::base::Result; using ::android::base::WriteStringToFd; using ::android::base::WriteStringToFile; using ::android::base::testing::HasError; using ::android::base::testing::HasValue; using ::android::base::testing::Ok; using ::android::base::testing::WithMessage; using ::testing::ContainsRegex; using ::testing::IsEmpty; using ::testing::NotNull; void CheckContent(const std::string& path, const std::string& expected_content) { std::string actual_content; ASSERT_TRUE(ReadFileToString(path, &actual_content)); EXPECT_EQ(actual_content, expected_content); } // A file that will always fail on `Commit`. class UncommittableFile : public NewFile { public: static Result> Create(const std::string& path, const FsPermission& fs_permission) { std::unique_ptr new_file = OR_RETURN(NewFile::Create(path, fs_permission)); return std::unique_ptr(new UncommittableFile(std::move(*new_file))); } Result Keep() override { return Error() << "Uncommittable file"; } private: explicit UncommittableFile(NewFile&& other) : NewFile(std::move(other)) {} }; class FileUtilsTest : public CommonArtTest { protected: void SetUp() override { CommonArtTest::SetUp(); scratch_dir_ = std::make_unique(); struct stat st; ASSERT_EQ(stat(scratch_dir_->GetPath().c_str(), &st), 0); fs_permission_ = FsPermission{.uid = static_cast(st.st_uid), .gid = static_cast(st.st_gid)}; } void TearDown() override { scratch_dir_.reset(); CommonArtTest::TearDown(); } FsPermission fs_permission_; std::unique_ptr scratch_dir_; }; TEST_F(FileUtilsTest, NewFileCreate) { std::string path = scratch_dir_->GetPath() + "/file.tmp"; Result> new_file = NewFile::Create(path, fs_permission_); ASSERT_THAT(new_file, HasValue(NotNull())); EXPECT_GE((*new_file)->Fd(), 0); EXPECT_EQ((*new_file)->FinalPath(), path); EXPECT_THAT((*new_file)->TempPath(), Not(IsEmpty())); EXPECT_THAT((*new_file)->TempId(), Not(IsEmpty())); EXPECT_FALSE(std::filesystem::exists((*new_file)->FinalPath())); EXPECT_TRUE(std::filesystem::exists((*new_file)->TempPath())); } TEST_F(FileUtilsTest, NewFileCreateNonExistentDir) { std::string path = scratch_dir_->GetPath() + "/non_existent_dir/file.tmp"; EXPECT_THAT(NewFile::Create(path, fs_permission_), HasError(WithMessage( ContainsRegex("Failed to create temp file for .*/non_existent_dir/file.tmp")))); } TEST_F(FileUtilsTest, NewFileExplicitCleanup) { std::string path = scratch_dir_->GetPath() + "/file.tmp"; std::unique_ptr new_file = OR_FATAL(NewFile::Create(path, fs_permission_)); new_file->Cleanup(); EXPECT_FALSE(std::filesystem::exists(path)); EXPECT_FALSE(std::filesystem::exists(new_file->TempPath())); } TEST_F(FileUtilsTest, NewFileImplicitCleanup) { std::string path = scratch_dir_->GetPath() + "/file.tmp"; std::string temp_path; // Cleanup on object destruction. { std::unique_ptr new_file = OR_FATAL(NewFile::Create(path, fs_permission_)); temp_path = new_file->TempPath(); } EXPECT_FALSE(std::filesystem::exists(path)); EXPECT_FALSE(std::filesystem::exists(temp_path)); } TEST_F(FileUtilsTest, NewFileCommit) { std::string path = scratch_dir_->GetPath() + "/file.tmp"; std::string temp_path; { std::unique_ptr new_file = OR_FATAL(NewFile::Create(path, fs_permission_)); temp_path = new_file->TempPath(); new_file->CommitOrAbandon(); } EXPECT_TRUE(std::filesystem::exists(path)); EXPECT_FALSE(std::filesystem::exists(temp_path)); } TEST_F(FileUtilsTest, NewFileCommitAllNoOldFile) { std::string file_1_path = scratch_dir_->GetPath() + "/file_1"; std::string file_2_path = scratch_dir_->GetPath() + "/file_2"; std::unique_ptr new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_)); std::unique_ptr new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_)); ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd())); ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd())); EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok()); // New files are committed. CheckContent(file_1_path, "new_file_1"); CheckContent(file_2_path, "new_file_2"); // New files are no longer at the temporary paths. EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath())); EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath())); } TEST_F(FileUtilsTest, NewFileCommitAllReplacesOldFiles) { std::string file_1_path = scratch_dir_->GetPath() + "/file_1"; std::string file_2_path = scratch_dir_->GetPath() + "/file_2"; ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path)); ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path)); std::unique_ptr new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_)); std::unique_ptr new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_)); ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd())); ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd())); EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok()); // New files are committed. CheckContent(file_1_path, "new_file_1"); CheckContent(file_2_path, "new_file_2"); // New files are no longer at the temporary paths. EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath())); EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath())); } TEST_F(FileUtilsTest, NewFileCommitAllReplacesLessOldFiles) { std::string file_1_path = scratch_dir_->GetPath() + "/file_1"; std::string file_2_path = scratch_dir_->GetPath() + "/file_2"; ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path)); // No old_file_2. std::unique_ptr new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_)); std::unique_ptr new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_)); ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd())); ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd())); EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok()); // New files are committed. CheckContent(file_1_path, "new_file_1"); CheckContent(file_2_path, "new_file_2"); // New files are no longer at the temporary paths. EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath())); EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath())); } TEST_F(FileUtilsTest, NewFileCommitAllReplacesMoreOldFiles) { std::string file_1_path = scratch_dir_->GetPath() + "/file_1"; std::string file_2_path = scratch_dir_->GetPath() + "/file_2"; std::string file_3_path = scratch_dir_->GetPath() + "/file_3"; ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path)); ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path)); ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path)); // Extra file. std::unique_ptr new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_)); std::unique_ptr new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_)); ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd())); ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd())); EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}), Ok()); // New files are committed. CheckContent(file_1_path, "new_file_1"); CheckContent(file_2_path, "new_file_2"); EXPECT_FALSE(std::filesystem::exists(file_3_path)); // Extra file removed. // New files are no longer at the temporary paths. EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath())); EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath())); } TEST_F(FileUtilsTest, NewFileCommitAllFailedToCommit) { std::string file_1_path = scratch_dir_->GetPath() + "/file_1"; std::string file_2_path = scratch_dir_->GetPath() + "/file_2"; std::string file_3_path = scratch_dir_->GetPath() + "/file_3"; ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path)); ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path)); ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path)); // Extra file. std::unique_ptr new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_)); // Uncommittable file. std::unique_ptr new_file_2 = OR_FATAL(UncommittableFile::Create(file_2_path, fs_permission_)); ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd())); ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd())); EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}), HasError(WithMessage("Uncommittable file"))); // Old files are fine. CheckContent(file_1_path, "old_file_1"); CheckContent(file_2_path, "old_file_2"); CheckContent(file_3_path, "old_file_3"); // New files are abandoned. EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath())); EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath())); } TEST_F(FileUtilsTest, NewFileCommitAllFailedToMoveOldFile) { std::string file_1_path = scratch_dir_->GetPath() + "/file_1"; std::string file_2_path = scratch_dir_->GetPath() + "/file_2"; std::filesystem::create_directory(file_2_path); std::string file_3_path = scratch_dir_->GetPath() + "/file_3"; ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path)); ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path)); // Extra file. std::unique_ptr new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_)); std::unique_ptr new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_)); ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd())); ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd())); // file_2 is not movable because it is a directory. EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}), HasError(WithMessage(ContainsRegex("Old file '.*/file_2' is a directory")))); // Old files are fine. CheckContent(file_1_path, "old_file_1"); EXPECT_TRUE(std::filesystem::is_directory(file_2_path)); CheckContent(file_3_path, "old_file_3"); // New files are abandoned. EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath())); EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath())); } TEST_F(FileUtilsTest, BuildTempPath) { EXPECT_EQ(NewFile::BuildTempPath("/a/b/original_path", "123456"), "/a/b/original_path.123456.tmp"); } TEST_F(FileUtilsTest, OpenFileForReading) { std::string path = scratch_dir_->GetPath() + "/foo"; ASSERT_TRUE(WriteStringToFile("foo", path)); EXPECT_THAT(OpenFileForReading(path), HasValue(NotNull())); } TEST_F(FileUtilsTest, OpenFileForReadingFailed) { std::string path = scratch_dir_->GetPath() + "/foo"; EXPECT_THAT(OpenFileForReading(path), HasError(WithMessage(ContainsRegex("Failed to open file .*/foo")))); } TEST_F(FileUtilsTest, FileFsPermissionToMode) { EXPECT_EQ(FileFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IRGRP); EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherReadable = true}), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherExecutable = true}), S_IRUSR | S_IWUSR | S_IRGRP | S_IXOTH); EXPECT_EQ( FileFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IXOTH); } TEST_F(FileUtilsTest, DirFsPermissionToMode) { EXPECT_EQ(DirFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP); EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH); EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherExecutable = true}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IXOTH); EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); } } // namespace } // namespace artd } // namespace art