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