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 <sys/types.h>
22 #include <unistd.h>
23 
24 #include <filesystem>
25 #include <memory>
26 #include <string>
27 #include <string_view>
28 #include <system_error>
29 #include <unordered_set>
30 #include <utility>
31 
32 #include "aidl/com/android/server/art/FsPermission.h"
33 #include "android-base/errors.h"
34 #include "android-base/logging.h"
35 #include "android-base/result.h"
36 #include "android-base/scopeguard.h"
37 #include "base/macros.h"
38 #include "base/os.h"
39 #include "base/unix_file/fd_file.h"
40 
41 namespace art {
42 namespace artd {
43 
44 namespace {
45 
46 using ::aidl::com::android::server::art::FsPermission;
47 using ::android::base::make_scope_guard;
48 using ::android::base::Result;
49 
UnlinkIfExists(std::string_view path)50 void UnlinkIfExists(std::string_view path) {
51   std::error_code ec;
52   std::filesystem::remove(path, ec);
53   if (ec) {
54     LOG(WARNING) << ART_FORMAT("Failed to remove file '{}': {}", path, ec.message());
55   }
56 }
57 
58 }  // namespace
59 
Create(const std::string & path,const FsPermission & fs_permission)60 Result<std::unique_ptr<NewFile>> NewFile::Create(const std::string& path,
61                                                  const FsPermission& fs_permission) {
62   std::unique_ptr<NewFile> output_file(new NewFile(path, fs_permission));
63   OR_RETURN(output_file->Init());
64   return output_file;
65 }
66 
~NewFile()67 NewFile::~NewFile() { Cleanup(); }
68 
Keep()69 Result<void> NewFile::Keep() {
70   if (close(std::exchange(fd_, -1)) != 0) {
71     return ErrnoErrorf("Failed to close file '{}'", temp_path_);
72   }
73   return {};
74 }
75 
CommitOrAbandon()76 Result<void> NewFile::CommitOrAbandon() {
77   auto cleanup = make_scope_guard([this] { Unlink(); });
78   OR_RETURN(Keep());
79   std::error_code ec;
80   std::filesystem::rename(temp_path_, final_path_, ec);
81   if (ec) {
82     // If this fails because the temp file doesn't exist, it could be that the file is deleted by
83     // `Artd::cleanup` if that method is run simultaneously. At the time of writing, this should
84     // never happen because `Artd::cleanup` is only called at the end of the backgrond dexopt job.
85     return Errorf(
86         "Failed to move new file '{}' to path '{}': {}", temp_path_, final_path_, ec.message());
87   }
88   cleanup.Disable();
89   return {};
90 }
91 
Cleanup()92 void NewFile::Cleanup() {
93   if (fd_ >= 0) {
94     Unlink();
95     if (close(std::exchange(fd_, -1)) != 0) {
96       // Nothing we can do. If the file is already unlinked, it will go away when the process exits.
97       PLOG(WARNING) << "Failed to close file '" << temp_path_ << "'";
98     }
99   }
100 }
101 
Init()102 Result<void> NewFile::Init() {
103   mode_t mode = FileFsPermissionToMode(fs_permission_);
104   // "<path_>.XXXXXX.tmp".
105   temp_path_ = BuildTempPath(final_path_, "XXXXXX");
106   fd_ = mkstemps(temp_path_.data(), /*suffixlen=*/4);
107   if (fd_ < 0) {
108     return ErrnoErrorf("Failed to create temp file for '{}'", final_path_);
109   }
110   temp_id_ = temp_path_.substr(/*pos=*/final_path_.length() + 1, /*count=*/6);
111   if (fchmod(fd_, mode) != 0) {
112     return ErrnoErrorf("Failed to chmod file '{}'", temp_path_);
113   }
114   OR_RETURN(Chown(temp_path_, fs_permission_));
115   return {};
116 }
117 
Unlink()118 void NewFile::Unlink() {
119   // This should never fail. We were able to create the file, so we should be able to remove it.
120   UnlinkIfExists(temp_path_);
121 }
122 
CommitAllOrAbandon(const std::vector<NewFile * > & files_to_commit,const std::vector<std::string_view> & files_to_remove)123 Result<void> NewFile::CommitAllOrAbandon(const std::vector<NewFile*>& files_to_commit,
124                                          const std::vector<std::string_view>& files_to_remove) {
125   std::vector<std::pair<std::string_view, std::string_view>> files_to_move;
126 
127   auto cleanup = make_scope_guard([&]() {
128     for (NewFile* file : files_to_commit) {
129       file->Unlink();
130     }
131   });
132   for (NewFile* file : files_to_commit) {
133     OR_RETURN(file->Keep());
134     files_to_move.emplace_back(file->TempPath(), file->FinalPath());
135   }
136   cleanup.Disable();
137 
138   return MoveAllOrAbandon(files_to_move, files_to_remove);
139 }
140 
MoveAllOrAbandon(const std::vector<std::pair<std::string_view,std::string_view>> & files_to_move,const std::vector<std::string_view> & files_to_remove)141 Result<void> MoveAllOrAbandon(
142     const std::vector<std::pair<std::string_view, std::string_view>>& files_to_move,
143     const std::vector<std::string_view>& files_to_remove) {
144   std::vector<std::pair<std::string_view, std::string>> moved_files;
145   std::unordered_set<std::string_view> committed_files;
146 
147   auto cleanup = make_scope_guard([&]() {
148     // Clean up `files_to_move`.
149     for (const auto& [src_path, dst_path] : files_to_move) {
150       if (committed_files.find(dst_path) != committed_files.end()) {
151         UnlinkIfExists(dst_path);
152       } else {
153         UnlinkIfExists(src_path);
154       }
155     }
156 
157     // Move old files back.
158     for (const auto& [original_path, temp_path] : moved_files) {
159       std::error_code ec;
160       std::filesystem::rename(temp_path, original_path, ec);
161       if (ec) {
162         // This should never happen. We were able to move the file from `original_path` to
163         // `temp_path`. We should be able to move it back.
164         LOG(WARNING) << ART_FORMAT("Failed to move old file '{}' back from temporary path '{}': {}",
165                                    original_path,
166                                    temp_path,
167                                    ec.message());
168       }
169     }
170   });
171 
172   // Move old files to temporary locations.
173   std::vector<std::string_view> all_files_to_remove;
174   all_files_to_remove.reserve(files_to_move.size() + files_to_remove.size());
175   for (const auto& [src_path, dst_path] : files_to_move) {
176     all_files_to_remove.push_back(dst_path);
177   }
178   all_files_to_remove.insert(
179       all_files_to_remove.end(), files_to_remove.begin(), files_to_remove.end());
180 
181   for (std::string_view original_path : all_files_to_remove) {
182     std::error_code ec;
183     std::filesystem::file_status status = std::filesystem::status(original_path, ec);
184     if (!std::filesystem::status_known(status)) {
185       return Errorf("Failed to get status of old file '{}': {}", original_path, ec.message());
186     }
187     if (std::filesystem::is_directory(status)) {
188       return ErrnoErrorf("Old file '{}' is a directory", original_path);
189     }
190     if (std::filesystem::exists(status)) {
191       std::string temp_path = NewFile::BuildTempPath(original_path, "XXXXXX");
192       int fd = mkstemps(temp_path.data(), /*suffixlen=*/4);
193       if (fd < 0) {
194         return ErrnoErrorf("Failed to create temporary path for old file '{}'", original_path);
195       }
196       close(fd);
197 
198       std::filesystem::rename(original_path, temp_path, ec);
199       if (ec) {
200         UnlinkIfExists(temp_path);
201         return Errorf("Failed to move old file '{}' to temporary path '{}': {}",
202                       original_path,
203                       temp_path,
204                       ec.message());
205       }
206 
207       moved_files.push_back({original_path, std::move(temp_path)});
208     }
209   }
210 
211   // Move `files_to_move`.
212   for (const auto& [src_path, dst_path] : files_to_move) {
213     std::error_code ec;
214     std::filesystem::rename(src_path, dst_path, ec);
215     if (ec) {
216       return Errorf("Failed to move file from '{}' to '{}': {}", src_path, dst_path, ec.message());
217     }
218     committed_files.insert(dst_path);
219   }
220 
221   cleanup.Disable();
222 
223   // Clean up old files.
224   for (const auto& [original_path, temp_path] : moved_files) {
225     // This should never fail.  We were able to move the file to `temp_path`. We should be able to
226     // remove it.
227     UnlinkIfExists(temp_path);
228   }
229 
230   return {};
231 }
232 
MoveAllOrAbandon(const std::vector<std::pair<std::string,std::string>> & files_to_move,const std::vector<std::string> & files_to_remove)233 android::base::Result<void> MoveAllOrAbandon(
234     const std::vector<std::pair<std::string, std::string>>& files_to_move,
235     const std::vector<std::string>& files_to_remove) {
236   std::vector<std::pair<std::string_view, std::string_view>> files_to_move_view;
237   files_to_move_view.reserve(files_to_move.size());
238   for (const auto& [src, dst] : files_to_move) {
239     files_to_move_view.emplace_back(src, dst);
240   }
241 
242   std::vector<std::string_view> files_to_remove_view;
243   files_to_remove_view.reserve(files_to_remove.size());
244   for (const std::string& file : files_to_remove) {
245     files_to_remove_view.emplace_back(file);
246   }
247   return MoveAllOrAbandon(files_to_move_view, files_to_remove_view);
248 }
249 
BuildTempPath(std::string_view final_path,const std::string & id)250 std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) {
251   return ART_FORMAT("{}.{}.tmp", final_path, id);
252 }
253 
OpenFileForReading(const std::string & path)254 Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path) {
255   std::unique_ptr<File> file(OS::OpenFileForReading(path.c_str()));
256   if (file == nullptr) {
257     return ErrnoErrorf("Failed to open file '{}'", path);
258   }
259   return file;
260 }
261 
FileFsPermissionToMode(const FsPermission & fs_permission)262 mode_t FileFsPermissionToMode(const FsPermission& fs_permission) {
263   return S_IRUSR | S_IWUSR | S_IRGRP | (fs_permission.isOtherReadable ? S_IROTH : 0) |
264          (fs_permission.isOtherExecutable ? S_IXOTH : 0);
265 }
266 
DirFsPermissionToMode(const FsPermission & fs_permission)267 mode_t DirFsPermissionToMode(const FsPermission& fs_permission) {
268   return FileFsPermissionToMode(fs_permission) | S_IXUSR | S_IXGRP;
269 }
270 
Chown(const std::string & path,const FsPermission & fs_permission)271 Result<void> Chown(const std::string& path, const FsPermission& fs_permission) {
272   if (fs_permission.uid < 0 && fs_permission.gid < 0) {
273     // Keep the default owner.
274   } else if (fs_permission.uid < 0 || fs_permission.gid < 0) {
275     return Errorf("uid and gid must be both non-negative or both negative, got {} and {}.",
276                   fs_permission.uid,
277                   fs_permission.gid);
278   }
279   if (chown(path.c_str(), fs_permission.uid, fs_permission.gid) != 0) {
280     return ErrnoErrorf("Failed to chown '{}'", path);
281   }
282   return {};
283 }
284 
285 }  // namespace artd
286 }  // namespace art
287