245 lines
7.9 KiB
C++
245 lines
7.9 KiB
C++
/*
|
|
* 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 <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <filesystem>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <system_error>
|
|
#include <utility>
|
|
|
|
#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<std::unique_ptr<NewFile>> NewFile::Create(const std::string& path,
|
|
const FsPermission& fs_permission) {
|
|
std::unique_ptr<NewFile> output_file(new NewFile(path, fs_permission));
|
|
OR_RETURN(output_file->Init());
|
|
return output_file;
|
|
}
|
|
|
|
NewFile::~NewFile() { Cleanup(); }
|
|
|
|
Result<void> NewFile::Keep() {
|
|
if (close(std::exchange(fd_, -1)) != 0) {
|
|
return ErrnoErrorf("Failed to close file '{}'", temp_path_);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
Result<void> 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<void> NewFile::Init() {
|
|
mode_t mode = FileFsPermissionToMode(fs_permission_);
|
|
// "<path_>.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<void> NewFile::CommitAllOrAbandon(const std::vector<NewFile*>& files_to_commit,
|
|
const std::vector<std::string_view>& files_to_remove) {
|
|
std::vector<std::pair<std::string_view, std::string>> 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<std::string_view> 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<std::unique_ptr<File>> OpenFileForReading(const std::string& path) {
|
|
std::unique_ptr<File> 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<void> 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
|