• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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