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