blob: 45344e13434f9932bdabc8780ab0cb9918b453c0 [file] [log] [blame]
Jiakai Zhanga2ba6962022-07-04 16:56:12 +01001/*
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"
Jiakai Zhanga2ba6962022-07-04 16:56:12 +010036#include "base/os.h"
37#include "base/unix_file/fd_file.h"
Jiakai Zhang3aaecf02022-08-10 15:35:28 +010038#include "fmt/format.h"
Jiakai Zhanga2ba6962022-07-04 16:56:12 +010039
40namespace art {
41namespace artd {
42
43namespace {
44
45using ::aidl::com::android::server::art::FsPermission;
46using ::android::base::make_scope_guard;
47using ::android::base::Result;
Jiakai Zhang3aaecf02022-08-10 15:35:28 +010048
49using ::fmt::literals::operator""_format; // NOLINT
Jiakai Zhanga2ba6962022-07-04 16:56:12 +010050
51void UnlinkIfExists(const std::string& path) {
52 std::error_code ec;
53 if (!std::filesystem::remove(path, ec)) {
54 if (ec.value() != ENOENT) {
Jiakai Zhang3aaecf02022-08-10 15:35:28 +010055 LOG(WARNING) << "Failed to remove file '{}': {}"_format(path, ec.message());
Jiakai Zhanga2ba6962022-07-04 16:56:12 +010056 }
57 }
58}
59
60} // namespace
61
62Result<std::unique_ptr<NewFile>> NewFile::Create(const std::string& path,
63 const FsPermission& fs_permission) {
64 std::unique_ptr<NewFile> output_file(new NewFile(path, fs_permission));
65 OR_RETURN(output_file->Init());
66 return output_file;
67}
68
69NewFile::~NewFile() { Cleanup(); }
70
71Result<void> NewFile::Keep() {
72 if (close(std::exchange(fd_, -1)) != 0) {
73 return ErrnoErrorf("Failed to close file '{}'", temp_path_);
74 }
75 return {};
76}
77
78Result<void> NewFile::CommitOrAbandon() {
79 auto cleanup = make_scope_guard([this] { Unlink(); });
80 OR_RETURN(Keep());
81 std::error_code ec;
82 std::filesystem::rename(temp_path_, final_path_, ec);
83 if (ec) {
84 return Errorf(
85 "Failed to move new file '{}' to path '{}': {}", temp_path_, final_path_, ec.message());
86 }
87 cleanup.Disable();
88 committed_ = true;
89 return {};
90}
91
92void 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
102Result<void> NewFile::Init() {
Jiakai Zhang3aaecf02022-08-10 15:35:28 +0100103 mode_t mode = FileFsPermissionToMode(fs_permission_);
Jiakai Zhanga2ba6962022-07-04 16:56:12 +0100104 // "<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 }
Jiakai Zhang3aaecf02022-08-10 15:35:28 +0100114 OR_RETURN(Chown(temp_path_, fs_permission_));
Jiakai Zhanga2ba6962022-07-04 16:56:12 +0100115 return {};
116}
117
118void 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
123Result<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>> moved_files;
126
127 auto cleanup = make_scope_guard([&]() {
128 // Clean up new files.
129 for (NewFile* new_file : files_to_commit) {
130 if (new_file->committed_) {
131 UnlinkIfExists(new_file->FinalPath());
132 } else {
133 new_file->Cleanup();
134 }
135 }
136
137 // Move old files back.
138 for (const auto& [original_path, temp_path] : moved_files) {
139 std::error_code ec;
140 std::filesystem::rename(temp_path, original_path, ec);
141 if (ec) {
142 // This should never happen. We were able to move the file from `original_path` to
143 // `temp_path`. We should be able to move it back.
Jiakai Zhang3aaecf02022-08-10 15:35:28 +0100144 LOG(WARNING) << "Failed to move old file '{}' back from temporary path '{}': {}"_format(
145 original_path, temp_path, ec.message());
Jiakai Zhanga2ba6962022-07-04 16:56:12 +0100146 }
147 }
148 });
149
150 // Move old files to temporary locations.
151 std::vector<std::string_view> all_files_to_remove;
152 for (NewFile* file : files_to_commit) {
153 all_files_to_remove.push_back(file->FinalPath());
154 }
155 all_files_to_remove.insert(
156 all_files_to_remove.end(), files_to_remove.begin(), files_to_remove.end());
157
158 for (std::string_view original_path : all_files_to_remove) {
159 std::error_code ec;
160 std::filesystem::file_status status = std::filesystem::status(original_path, ec);
161 if (!std::filesystem::status_known(status)) {
162 return Errorf("Failed to get status of old file '{}': {}", original_path, ec.message());
163 }
164 if (std::filesystem::is_directory(status)) {
165 return ErrnoErrorf("Old file '{}' is a directory", original_path);
166 }
167 if (std::filesystem::exists(status)) {
168 std::string temp_path = BuildTempPath(original_path, "XXXXXX");
169 int fd = mkstemps(temp_path.data(), /*suffixlen=*/4);
170 if (fd < 0) {
171 return ErrnoErrorf("Failed to create temporary path for old file '{}'", original_path);
172 }
173 close(fd);
174
175 std::filesystem::rename(original_path, temp_path, ec);
176 if (ec) {
177 UnlinkIfExists(temp_path);
178 return Errorf("Failed to move old file '{}' to temporary path '{}': {}",
179 original_path,
180 temp_path,
181 ec.message());
182 }
183
184 moved_files.push_back({original_path, std::move(temp_path)});
185 }
186 }
187
188 // Commit new files.
189 for (NewFile* file : files_to_commit) {
190 OR_RETURN(file->CommitOrAbandon());
191 }
192
193 cleanup.Disable();
194
195 // Clean up old files.
196 for (const auto& [original_path, temp_path] : moved_files) {
197 // This should never fail. We were able to move the file to `temp_path`. We should be able to
198 // remove it.
199 UnlinkIfExists(temp_path);
200 }
201
202 return {};
203}
204
205std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) {
Jiakai Zhang3aaecf02022-08-10 15:35:28 +0100206 return "{}.{}.tmp"_format(final_path, id);
Jiakai Zhanga2ba6962022-07-04 16:56:12 +0100207}
208
209Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path) {
210 std::unique_ptr<File> file(OS::OpenFileForReading(path.c_str()));
211 if (file == nullptr) {
212 return ErrnoErrorf("Failed to open file '{}'", path);
213 }
214 return file;
215}
216
Jiakai Zhang3aaecf02022-08-10 15:35:28 +0100217mode_t FileFsPermissionToMode(const FsPermission& fs_permission) {
Jiakai Zhanga2ba6962022-07-04 16:56:12 +0100218 return S_IRUSR | S_IWUSR | S_IRGRP | (fs_permission.isOtherReadable ? S_IROTH : 0) |
219 (fs_permission.isOtherExecutable ? S_IXOTH : 0);
220}
221
Jiakai Zhang3aaecf02022-08-10 15:35:28 +0100222mode_t DirFsPermissionToMode(const FsPermission& fs_permission) {
223 return FileFsPermissionToMode(fs_permission) | S_IXUSR | S_IXGRP;
224}
225
226Result<void> Chown(const std::string& path, const FsPermission& fs_permission) {
227 if (fs_permission.uid < 0 && fs_permission.gid < 0) {
228 // Keep the default owner.
229 } else if (fs_permission.uid < 0 || fs_permission.gid < 0) {
230 return Errorf("uid and gid must be both non-negative or both negative, got {} and {}.",
231 fs_permission.uid,
232 fs_permission.gid);
233 }
234 if (chown(path.c_str(), fs_permission.uid, fs_permission.gid) != 0) {
235 return ErrnoErrorf("Failed to chown '{}'", path);
236 }
237 return {};
238}
239
Jiakai Zhanga2ba6962022-07-04 16:56:12 +0100240} // namespace artd
241} // namespace art