1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "file_utils.h"
18 
19 #include <fcntl.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 
23 #include <filesystem>
24 #include <memory>
25 #include <string>
26 
27 #include "aidl/com/android/server/art/FsPermission.h"
28 #include "android-base/errors.h"
29 #include "android-base/file.h"
30 #include "android-base/result-gmock.h"
31 #include "android-base/result.h"
32 #include "base/common_art_test.h"
33 #include "gmock/gmock.h"
34 #include "gtest/gtest.h"
35 
36 namespace art {
37 namespace artd {
38 namespace {
39 
40 using ::aidl::com::android::server::art::FsPermission;
41 using ::android::base::Error;
42 using ::android::base::ReadFileToString;
43 using ::android::base::Result;
44 using ::android::base::WriteStringToFd;
45 using ::android::base::WriteStringToFile;
46 using ::android::base::testing::HasError;
47 using ::android::base::testing::HasValue;
48 using ::android::base::testing::Ok;
49 using ::android::base::testing::WithMessage;
50 using ::testing::ContainsRegex;
51 using ::testing::IsEmpty;
52 using ::testing::NotNull;
53 
CheckContent(const std::string & path,const std::string & expected_content)54 void CheckContent(const std::string& path, const std::string& expected_content) {
55   std::string actual_content;
56   ASSERT_TRUE(ReadFileToString(path, &actual_content));
57   EXPECT_EQ(actual_content, expected_content);
58 }
59 
60 // A file that will always fail on `Keep`.
61 class UnkeepableFile : public NewFile {
62  public:
Create(const std::string & path,const FsPermission & fs_permission)63   static Result<std::unique_ptr<UnkeepableFile>> Create(const std::string& path,
64                                                         const FsPermission& fs_permission) {
65     std::unique_ptr<NewFile> new_file = OR_RETURN(NewFile::Create(path, fs_permission));
66     return std::unique_ptr<UnkeepableFile>(new UnkeepableFile(std::move(*new_file)));
67   }
68 
Keep()69   Result<void> Keep() override { return Error() << "Unkeepable file"; }
70 
71  private:
UnkeepableFile(NewFile && other)72   explicit UnkeepableFile(NewFile&& other) : NewFile(std::move(other)) {}
73 };
74 
75 class FileUtilsTest : public CommonArtTest {
76  protected:
SetUp()77   void SetUp() override {
78     CommonArtTest::SetUp();
79     scratch_dir_ = std::make_unique<ScratchDir>();
80     struct stat st;
81     ASSERT_EQ(stat(scratch_dir_->GetPath().c_str(), &st), 0);
82     fs_permission_ = FsPermission{.uid = static_cast<int32_t>(st.st_uid),
83                                   .gid = static_cast<int32_t>(st.st_gid)};
84   }
85 
TearDown()86   void TearDown() override {
87     scratch_dir_.reset();
88     CommonArtTest::TearDown();
89   }
90 
91   FsPermission fs_permission_;
92   std::unique_ptr<ScratchDir> scratch_dir_;
93 };
94 
TEST_F(FileUtilsTest,NewFileCreate)95 TEST_F(FileUtilsTest, NewFileCreate) {
96   std::string path = scratch_dir_->GetPath() + "/file.tmp";
97 
98   Result<std::unique_ptr<NewFile>> new_file = NewFile::Create(path, fs_permission_);
99   ASSERT_THAT(new_file, HasValue(NotNull()));
100   EXPECT_GE((*new_file)->Fd(), 0);
101   EXPECT_EQ((*new_file)->FinalPath(), path);
102   EXPECT_THAT((*new_file)->TempPath(), Not(IsEmpty()));
103   EXPECT_THAT((*new_file)->TempId(), Not(IsEmpty()));
104 
105   EXPECT_FALSE(std::filesystem::exists((*new_file)->FinalPath()));
106   EXPECT_TRUE(std::filesystem::exists((*new_file)->TempPath()));
107 }
108 
TEST_F(FileUtilsTest,NewFileCreateNonExistentDir)109 TEST_F(FileUtilsTest, NewFileCreateNonExistentDir) {
110   std::string path = scratch_dir_->GetPath() + "/non_existent_dir/file.tmp";
111 
112   EXPECT_THAT(NewFile::Create(path, fs_permission_),
113               HasError(WithMessage(
114                   ContainsRegex("Failed to create temp file for .*/non_existent_dir/file.tmp"))));
115 }
116 
TEST_F(FileUtilsTest,NewFileExplicitCleanup)117 TEST_F(FileUtilsTest, NewFileExplicitCleanup) {
118   std::string path = scratch_dir_->GetPath() + "/file.tmp";
119   std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
120   new_file->Cleanup();
121 
122   EXPECT_FALSE(std::filesystem::exists(path));
123   EXPECT_FALSE(std::filesystem::exists(new_file->TempPath()));
124 }
125 
TEST_F(FileUtilsTest,NewFileImplicitCleanup)126 TEST_F(FileUtilsTest, NewFileImplicitCleanup) {
127   std::string path = scratch_dir_->GetPath() + "/file.tmp";
128   std::string temp_path;
129 
130   // Cleanup on object destruction.
131   {
132     std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
133     temp_path = new_file->TempPath();
134   }
135 
136   EXPECT_FALSE(std::filesystem::exists(path));
137   EXPECT_FALSE(std::filesystem::exists(temp_path));
138 }
139 
TEST_F(FileUtilsTest,NewFileCommit)140 TEST_F(FileUtilsTest, NewFileCommit) {
141   std::string path = scratch_dir_->GetPath() + "/file.tmp";
142   std::string temp_path;
143 
144   {
145     std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
146     temp_path = new_file->TempPath();
147     new_file->CommitOrAbandon();
148   }
149 
150   EXPECT_TRUE(std::filesystem::exists(path));
151   EXPECT_FALSE(std::filesystem::exists(temp_path));
152 }
153 
TEST_F(FileUtilsTest,NewFileCommitAllNoOldFile)154 TEST_F(FileUtilsTest, NewFileCommitAllNoOldFile) {
155   std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
156   std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
157 
158   std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
159   std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
160 
161   ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
162   ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
163 
164   EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
165 
166   // New files are committed.
167   CheckContent(file_1_path, "new_file_1");
168   CheckContent(file_2_path, "new_file_2");
169 
170   // New files are no longer at the temporary paths.
171   EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
172   EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
173 }
174 
TEST_F(FileUtilsTest,NewFileCommitAllReplacesOldFiles)175 TEST_F(FileUtilsTest, NewFileCommitAllReplacesOldFiles) {
176   std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
177   std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
178 
179   ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
180   ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
181 
182   std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
183   std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
184 
185   ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
186   ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
187 
188   EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
189 
190   // New files are committed.
191   CheckContent(file_1_path, "new_file_1");
192   CheckContent(file_2_path, "new_file_2");
193 
194   // New files are no longer at the temporary paths.
195   EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
196   EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
197 }
198 
TEST_F(FileUtilsTest,NewFileCommitAllReplacesLessOldFiles)199 TEST_F(FileUtilsTest, NewFileCommitAllReplacesLessOldFiles) {
200   std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
201   std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
202 
203   ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));  // No old_file_2.
204 
205   std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
206   std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
207 
208   ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
209   ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
210 
211   EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
212 
213   // New files are committed.
214   CheckContent(file_1_path, "new_file_1");
215   CheckContent(file_2_path, "new_file_2");
216 
217   // New files are no longer at the temporary paths.
218   EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
219   EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
220 }
221 
TEST_F(FileUtilsTest,NewFileCommitAllReplacesMoreOldFiles)222 TEST_F(FileUtilsTest, NewFileCommitAllReplacesMoreOldFiles) {
223   std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
224   std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
225   std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
226 
227   ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
228   ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
229   ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
230 
231   std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
232   std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
233 
234   ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
235   ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
236 
237   EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
238               Ok());
239 
240   // New files are committed.
241   CheckContent(file_1_path, "new_file_1");
242   CheckContent(file_2_path, "new_file_2");
243   EXPECT_FALSE(std::filesystem::exists(file_3_path));  // Extra file removed.
244 
245   // New files are no longer at the temporary paths.
246   EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
247   EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
248 }
249 
TEST_F(FileUtilsTest,NewFileCommitAllFailedToKeep)250 TEST_F(FileUtilsTest, NewFileCommitAllFailedToKeep) {
251   std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
252   std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
253   std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
254 
255   ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
256   ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
257   ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
258 
259   std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
260   // Unkeepable file.
261   std::unique_ptr<NewFile> new_file_2 =
262       OR_FATAL(UnkeepableFile::Create(file_2_path, fs_permission_));
263 
264   ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
265   ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
266 
267   EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
268               HasError(WithMessage("Unkeepable file")));
269 
270   // Old files are fine.
271   CheckContent(file_1_path, "old_file_1");
272   CheckContent(file_2_path, "old_file_2");
273   CheckContent(file_3_path, "old_file_3");
274 
275   // New files are abandoned.
276   EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
277   EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
278 }
279 
TEST_F(FileUtilsTest,NewFileCommitAllFailedToCommit)280 TEST_F(FileUtilsTest, NewFileCommitAllFailedToCommit) {
281   std::string dir_1_path = scratch_dir_->GetPath() + "/dir_1";
282   std::string dir_2_path = scratch_dir_->GetPath() + "/dir_2";
283 
284   ASSERT_TRUE(std::filesystem::create_directory(dir_1_path));
285   ASSERT_TRUE(std::filesystem::create_directory(dir_2_path));
286 
287   std::string file_1_path = dir_1_path + "/file_1";
288   std::string file_2_path = dir_2_path + "/file_2";
289 
290   std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
291   std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
292 
293   ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
294   ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
295 
296   {
297     // Make `new_file_2` fail to commit.
298     auto scoped_inaccessible = ScopedInaccessible(dir_2_path);
299     std::filesystem::permissions(
300         dir_2_path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add);
301     auto scoped_unroot = ScopedUnroot();
302 
303     EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}),
304                 HasError(WithMessage(ContainsRegex("Failed to move file .*file_2.*"))));
305   }
306 
307   // Files are abandoned at best effort. File 1 is abandoned, but file 2 cannot be abandoned due to
308   // permission denied.
309   EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
310   EXPECT_FALSE(std::filesystem::exists(new_file_1->FinalPath()));
311   EXPECT_TRUE(std::filesystem::exists(new_file_2->TempPath()));
312   EXPECT_FALSE(std::filesystem::exists(new_file_2->FinalPath()));
313 }
314 
TEST_F(FileUtilsTest,NewFileCommitAllFailedToMoveOldFile)315 TEST_F(FileUtilsTest, NewFileCommitAllFailedToMoveOldFile) {
316   std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
317   std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
318   std::filesystem::create_directory(file_2_path);
319   std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
320 
321   ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
322   ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
323 
324   std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
325   std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
326 
327   ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
328   ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
329 
330   // file_2 is not movable because it is a directory.
331   EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
332               HasError(WithMessage(ContainsRegex("Old file '.*/file_2' is a directory"))));
333 
334   // Old files are fine.
335   CheckContent(file_1_path, "old_file_1");
336   EXPECT_TRUE(std::filesystem::is_directory(file_2_path));
337   CheckContent(file_3_path, "old_file_3");
338 
339   // New files are abandoned.
340   EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
341   EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
342 }
343 
TEST_F(FileUtilsTest,BuildTempPath)344 TEST_F(FileUtilsTest, BuildTempPath) {
345   EXPECT_EQ(NewFile::BuildTempPath("/a/b/original_path", "123456"),
346             "/a/b/original_path.123456.tmp");
347 }
348 
TEST_F(FileUtilsTest,OpenFileForReading)349 TEST_F(FileUtilsTest, OpenFileForReading) {
350   std::string path = scratch_dir_->GetPath() + "/foo";
351   ASSERT_TRUE(WriteStringToFile("foo", path));
352 
353   EXPECT_THAT(OpenFileForReading(path), HasValue(NotNull()));
354 }
355 
TEST_F(FileUtilsTest,OpenFileForReadingFailed)356 TEST_F(FileUtilsTest, OpenFileForReadingFailed) {
357   std::string path = scratch_dir_->GetPath() + "/foo";
358 
359   EXPECT_THAT(OpenFileForReading(path),
360               HasError(WithMessage(ContainsRegex("Failed to open file .*/foo"))));
361 }
362 
TEST_F(FileUtilsTest,FileFsPermissionToMode)363 TEST_F(FileUtilsTest, FileFsPermissionToMode) {
364   EXPECT_EQ(FileFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IRGRP);
365   EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherReadable = true}),
366             S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
367   EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherExecutable = true}),
368             S_IRUSR | S_IWUSR | S_IRGRP | S_IXOTH);
369   EXPECT_EQ(
370       FileFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}),
371       S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IXOTH);
372 }
373 
TEST_F(FileUtilsTest,DirFsPermissionToMode)374 TEST_F(FileUtilsTest, DirFsPermissionToMode) {
375   EXPECT_EQ(DirFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
376   EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true}),
377             S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH);
378   EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherExecutable = true}),
379             S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IXOTH);
380   EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}),
381             S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
382 }
383 
384 }  // namespace
385 }  // namespace artd
386 }  // namespace art
387