/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "file_utils.h" #include #include #include #include #include #include #include #include #include #include #include "aidl/com/android/server/art/FsPermission.h" #include "android-base/errors.h" #include "android-base/logging.h" #include "android-base/result.h" #include "android-base/scopeguard.h" #include "base/os.h" #include "base/unix_file/fd_file.h" #include "fmt/format.h" namespace art { namespace artd { namespace { using ::aidl::com::android::server::art::FsPermission; using ::android::base::make_scope_guard; using ::android::base::Result; using ::fmt::literals::operator""_format; // NOLINT void UnlinkIfExists(const std::string& path) { std::error_code ec; std::filesystem::remove(path, ec); if (ec) { LOG(WARNING) << "Failed to remove file '{}': {}"_format(path, ec.message()); } } } // namespace Result> NewFile::Create(const std::string& path, const FsPermission& fs_permission) { std::unique_ptr output_file(new NewFile(path, fs_permission)); OR_RETURN(output_file->Init()); return output_file; } NewFile::~NewFile() { Cleanup(); } Result NewFile::Keep() { if (close(std::exchange(fd_, -1)) != 0) { return ErrnoErrorf("Failed to close file '{}'", temp_path_); } return {}; } Result NewFile::CommitOrAbandon() { auto cleanup = make_scope_guard([this] { Unlink(); }); OR_RETURN(Keep()); std::error_code ec; std::filesystem::rename(temp_path_, final_path_, ec); if (ec) { // If this fails because the temp file doesn't exist, it could be that the file is deleted by // `Artd::cleanup` if that method is run simultaneously. At the time of writing, this should // never happen because `Artd::cleanup` is only called at the end of the backgrond dexopt job. return Errorf( "Failed to move new file '{}' to path '{}': {}", temp_path_, final_path_, ec.message()); } cleanup.Disable(); committed_ = true; return {}; } void NewFile::Cleanup() { if (fd_ >= 0) { Unlink(); if (close(std::exchange(fd_, -1)) != 0) { // Nothing we can do. If the file is already unlinked, it will go away when the process exits. PLOG(WARNING) << "Failed to close file '" << temp_path_ << "'"; } } } Result NewFile::Init() { mode_t mode = FileFsPermissionToMode(fs_permission_); // ".XXXXXX.tmp". temp_path_ = BuildTempPath(final_path_, "XXXXXX"); fd_ = mkstemps(temp_path_.data(), /*suffixlen=*/4); if (fd_ < 0) { return ErrnoErrorf("Failed to create temp file for '{}'", final_path_); } temp_id_ = temp_path_.substr(/*pos=*/final_path_.length() + 1, /*count=*/6); if (fchmod(fd_, mode) != 0) { return ErrnoErrorf("Failed to chmod file '{}'", temp_path_); } OR_RETURN(Chown(temp_path_, fs_permission_)); return {}; } void NewFile::Unlink() { // This should never fail. We were able to create the file, so we should be able to remove it. UnlinkIfExists(temp_path_); } Result NewFile::CommitAllOrAbandon(const std::vector& files_to_commit, const std::vector& files_to_remove) { std::vector> moved_files; auto cleanup = make_scope_guard([&]() { // Clean up new files. for (NewFile* new_file : files_to_commit) { if (new_file->committed_) { UnlinkIfExists(new_file->FinalPath()); } else { new_file->Cleanup(); } } // Move old files back. for (const auto& [original_path, temp_path] : moved_files) { std::error_code ec; std::filesystem::rename(temp_path, original_path, ec); if (ec) { // This should never happen. We were able to move the file from `original_path` to // `temp_path`. We should be able to move it back. LOG(WARNING) << "Failed to move old file '{}' back from temporary path '{}': {}"_format( original_path, temp_path, ec.message()); } } }); // Move old files to temporary locations. std::vector all_files_to_remove; all_files_to_remove.reserve(files_to_commit.size() + files_to_remove.size()); for (NewFile* file : files_to_commit) { all_files_to_remove.push_back(file->FinalPath()); } all_files_to_remove.insert( all_files_to_remove.end(), files_to_remove.begin(), files_to_remove.end()); for (std::string_view original_path : all_files_to_remove) { std::error_code ec; std::filesystem::file_status status = std::filesystem::status(original_path, ec); if (!std::filesystem::status_known(status)) { return Errorf("Failed to get status of old file '{}': {}", original_path, ec.message()); } if (std::filesystem::is_directory(status)) { return ErrnoErrorf("Old file '{}' is a directory", original_path); } if (std::filesystem::exists(status)) { std::string temp_path = BuildTempPath(original_path, "XXXXXX"); int fd = mkstemps(temp_path.data(), /*suffixlen=*/4); if (fd < 0) { return ErrnoErrorf("Failed to create temporary path for old file '{}'", original_path); } close(fd); std::filesystem::rename(original_path, temp_path, ec); if (ec) { UnlinkIfExists(temp_path); return Errorf("Failed to move old file '{}' to temporary path '{}': {}", original_path, temp_path, ec.message()); } moved_files.push_back({original_path, std::move(temp_path)}); } } // Commit new files. for (NewFile* file : files_to_commit) { OR_RETURN(file->CommitOrAbandon()); } cleanup.Disable(); // Clean up old files. for (const auto& [original_path, temp_path] : moved_files) { // This should never fail. We were able to move the file to `temp_path`. We should be able to // remove it. UnlinkIfExists(temp_path); } return {}; } std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) { return "{}.{}.tmp"_format(final_path, id); } Result> OpenFileForReading(const std::string& path) { std::unique_ptr file(OS::OpenFileForReading(path.c_str())); if (file == nullptr) { return ErrnoErrorf("Failed to open file '{}'", path); } return file; } mode_t FileFsPermissionToMode(const FsPermission& fs_permission) { return S_IRUSR | S_IWUSR | S_IRGRP | (fs_permission.isOtherReadable ? S_IROTH : 0) | (fs_permission.isOtherExecutable ? S_IXOTH : 0); } mode_t DirFsPermissionToMode(const FsPermission& fs_permission) { return FileFsPermissionToMode(fs_permission) | S_IXUSR | S_IXGRP; } Result Chown(const std::string& path, const FsPermission& fs_permission) { if (fs_permission.uid < 0 && fs_permission.gid < 0) { // Keep the default owner. } else if (fs_permission.uid < 0 || fs_permission.gid < 0) { return Errorf("uid and gid must be both non-negative or both negative, got {} and {}.", fs_permission.uid, fs_permission.gid); } if (chown(path.c_str(), fs_permission.uid, fs_permission.gid) != 0) { return ErrnoErrorf("Failed to chown '{}'", path); } return {}; } } // namespace artd } // namespace art