blob: db3434b3b9b96dd41c55a7d1cfede07cbc9475ab [file] [log] [blame]
/*
* Copyright (C) 2018 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.
*/
#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
#include "apexd.h"
#include <ApexProperties.sysprop.h>
#include <android-base/chrono_utils.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <dirent.h>
#include <fcntl.h>
#include <google/protobuf/util/message_differencer.h>
#include <libavb/libavb.h>
#include <libdm/dm.h>
#include <libdm/dm_table.h>
#include <libdm/dm_target.h>
#include <linux/f2fs.h>
#include <linux/loop.h>
#include <selinux/android.h>
#include <statssocket_lazy.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <unistd.h>
#include <utils/Trace.h>
#include <vintf/VintfObject.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <future>
#include <iomanip>
#include <iterator>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include "VerityUtils.h"
#include "apex_constants.h"
#include "apex_database.h"
#include "apex_file.h"
#include "apex_file_repository.h"
#include "apex_manifest.h"
#include "apex_sha.h"
#include "apex_shim.h"
#include "apexd_checkpoint.h"
#include "apexd_lifecycle.h"
#include "apexd_loop.h"
#include "apexd_private.h"
#include "apexd_rollback_utils.h"
#include "apexd_session.h"
#include "apexd_utils.h"
#include "apexd_vendor_apex.h"
#include "apexd_verity.h"
#include "com_android_apex.h"
#include "statslog_apex.h"
using android::base::boot_clock;
using android::base::ConsumePrefix;
using android::base::ErrnoError;
using android::base::Error;
using android::base::GetProperty;
using android::base::Join;
using android::base::ParseUint;
using android::base::RemoveFileIfExists;
using android::base::Result;
using android::base::SetProperty;
using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmDeviceState;
using android::dm::DmTable;
using android::dm::DmTargetVerity;
using ::apex::proto::ApexManifest;
using apex::proto::SessionState;
using google::protobuf::util::MessageDifferencer;
namespace android {
namespace apex {
using MountedApexData = MountedApexDatabase::MountedApexData;
namespace {
static constexpr const char* kBuildFingerprintSysprop = "ro.build.fingerprint";
// This should be in UAPI, but it's not :-(
static constexpr const char* kDmVerityRestartOnCorruption =
"restart_on_corruption";
MountedApexDatabase gMountedApexes;
// Can be set by SetConfig()
std::optional<ApexdConfig> gConfig;
// Set by InitializeSessionManager
ApexSessionManager* gSessionManager;
CheckpointInterface* gVoldService;
bool gSupportsFsCheckpoints = false;
bool gInFsCheckpointMode = false;
// APEXEs for which a different version was activated than in the previous boot.
// This can happen in the following scenarios:
// 1. This APEX is part of the staged session that was applied during this
// boot.
// 2. This is a compressed APEX that was decompressed during this boot.
// 3. We failed to activate APEX from /data/apex/active and fallback to the
// pre-installed APEX.
std::set<std::string> gChangedActiveApexes;
static constexpr size_t kLoopDeviceSetupAttempts = 3u;
// Please DO NOT add new modules to this list without contacting mainline-modularization@ first.
static const std::vector<std::string> kBootstrapApexes = ([]() {
std::vector<std::string> ret = {
"com.android.i18n",
"com.android.runtime",
"com.android.tzdata",
};
auto vendor_vndk_ver = GetProperty("ro.vndk.version", "");
if (vendor_vndk_ver != "") {
ret.push_back("com.android.vndk.v" + vendor_vndk_ver);
}
auto product_vndk_ver = GetProperty("ro.product.vndk.version", "");
if (product_vndk_ver != "" && product_vndk_ver != vendor_vndk_ver) {
ret.push_back("com.android.vndk.v" + product_vndk_ver);
}
return ret;
})();
static constexpr const int kNumRetriesWhenCheckpointingEnabled = 1;
bool IsBootstrapApex(const ApexFile& apex) {
static std::vector<std::string> additional = []() {
std::vector<std::string> ret;
if (android::base::GetBoolProperty("ro.boot.apex.early_adbd", false)) {
ret.push_back("com.android.adbd");
}
return ret;
}();
if (IsVendorApex(apex) && apex.GetManifest().vendorbootstrap()) {
return true;
}
return std::find(kBootstrapApexes.begin(), kBootstrapApexes.end(),
apex.GetManifest().name()) != kBootstrapApexes.end() ||
std::find(additional.begin(), additional.end(),
apex.GetManifest().name()) != additional.end();
}
void ReleaseF2fsCompressedBlocks(const std::string& file_path) {
unique_fd fd(
TEMP_FAILURE_RETRY(open(file_path.c_str(), O_RDONLY | O_CLOEXEC, 0)));
if (fd.get() == -1) {
PLOG(ERROR) << "Failed to open " << file_path;
return;
}
unsigned int flags;
if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
PLOG(ERROR) << "Failed to call FS_IOC_GETFLAGS on " << file_path;
return;
}
if ((flags & FS_COMPR_FL) == 0) {
// Doesn't support f2fs-compression.
return;
}
uint64_t blk_cnt;
if (ioctl(fd, F2FS_IOC_RELEASE_COMPRESS_BLOCKS, &blk_cnt) == -1) {
PLOG(ERROR) << "Failed to call F2FS_IOC_RELEASE_COMPRESS_BLOCKS on "
<< file_path;
}
LOG(INFO) << "Released " << blk_cnt << " compressed blocks from "
<< file_path;
}
std::unique_ptr<DmTable> CreateVerityTable(const ApexVerityData& verity_data,
const std::string& block_device,
const std::string& hash_device,
bool restart_on_corruption) {
AvbHashtreeDescriptor* desc = verity_data.desc.get();
auto table = std::make_unique<DmTable>();
uint32_t hash_start_block = 0;
if (hash_device == block_device) {
hash_start_block = desc->tree_offset / desc->hash_block_size;
}
auto target = std::make_unique<DmTargetVerity>(
0, desc->image_size / 512, desc->dm_verity_version, block_device,
hash_device, desc->data_block_size, desc->hash_block_size,
desc->image_size / desc->data_block_size, hash_start_block,
verity_data.hash_algorithm, verity_data.root_digest, verity_data.salt);
target->IgnoreZeroBlocks();
if (restart_on_corruption) {
target->SetVerityMode(kDmVerityRestartOnCorruption);
}
table->AddTarget(std::move(target));
table->set_readonly(true);
return table;
}
// Deletes a dm-verity device with a given name and path
// Synchronizes on the device actually being deleted from userspace.
Result<void> DeleteVerityDevice(const std::string& name, bool deferred) {
DeviceMapper& dm = DeviceMapper::Instance();
if (deferred) {
if (!dm.DeleteDeviceDeferred(name)) {
return ErrnoError() << "Failed to issue deferred delete of verity device "
<< name;
}
return {};
}
auto timeout = std::chrono::milliseconds(
android::sysprop::ApexProperties::dm_delete_timeout().value_or(750));
if (!dm.DeleteDevice(name, timeout)) {
return Error() << "Failed to delete dm-device " << name;
}
return {};
}
class DmVerityDevice {
public:
DmVerityDevice() : cleared_(true) {}
explicit DmVerityDevice(std::string name)
: name_(std::move(name)), cleared_(false) {}
DmVerityDevice(std::string name, std::string dev_path)
: name_(std::move(name)),
dev_path_(std::move(dev_path)),
cleared_(false) {}
DmVerityDevice(DmVerityDevice&& other) noexcept
: name_(std::move(other.name_)),
dev_path_(std::move(other.dev_path_)),
cleared_(other.cleared_) {
other.cleared_ = true;
}
DmVerityDevice& operator=(DmVerityDevice&& other) noexcept {
name_ = other.name_;
dev_path_ = other.dev_path_;
cleared_ = other.cleared_;
other.cleared_ = true;
return *this;
}
~DmVerityDevice() {
if (!cleared_) {
Result<void> ret = DeleteVerityDevice(name_, /* deferred= */ false);
if (!ret.ok()) {
LOG(ERROR) << ret.error();
}
}
}
const std::string& GetName() const { return name_; }
const std::string& GetDevPath() const { return dev_path_; }
void Release() { cleared_ = true; }
private:
std::string name_;
std::string dev_path_;
bool cleared_;
};
Result<DmVerityDevice> CreateVerityDevice(
DeviceMapper& dm, const std::string& name, const DmTable& table,
const std::chrono::milliseconds& timeout) {
std::string dev_path;
if (!dm.CreateDevice(name, table, &dev_path, timeout)) {
return Errorf("Couldn't create verity device.");
}
return DmVerityDevice(name, dev_path);
}
Result<DmVerityDevice> CreateVerityDevice(const std::string& name,
const DmTable& table,
bool reuse_device) {
ATRACE_NAME("CreateVerityDevice");
LOG(VERBOSE) << "Creating verity device " << name;
auto timeout = std::chrono::milliseconds(
android::sysprop::ApexProperties::dm_create_timeout().value_or(1000));
DeviceMapper& dm = DeviceMapper::Instance();
auto state = dm.GetState(name);
if (state == DmDeviceState::INVALID) {
return CreateVerityDevice(dm, name, table, timeout);
}
if (reuse_device) {
if (state == DmDeviceState::ACTIVE) {
LOG(WARNING) << "Deleting existing active dm device " << name;
if (auto r = DeleteVerityDevice(name, /* deferred= */ false); !r.ok()) {
return r.error();
}
return CreateVerityDevice(dm, name, table, timeout);
}
if (!dm.LoadTableAndActivate(name, table)) {
dm.DeleteDevice(name);
return Error() << "Failed to activate dm device " << name;
}
std::string path;
if (!dm.WaitForDevice(name, timeout, &path)) {
dm.DeleteDevice(name);
return Error() << "Failed waiting for dm device " << name;
}
return DmVerityDevice(name, path);
} else {
if (state != DmDeviceState::INVALID) {
// Delete dangling dm-device. This can happen if apexd fails to delete it
// while unmounting an apex.
LOG(WARNING) << "Deleting existing dm device " << name;
if (auto r = DeleteVerityDevice(name, /* deferred= */ false); !r.ok()) {
return r.error();
}
}
return CreateVerityDevice(dm, name, table, timeout);
}
}
/**
* When we create hardlink for a new apex package in kActiveApexPackagesDataDir,
* there might be an older version of the same package already present in there.
* Since a new version of the same package is being installed on this boot, the
* old one needs to deleted so that we don't end up activating same package
* twice.
*
* @param affected_packages package names of the news apex that are being
* installed in this boot
* @param files_to_keep path to the new apex packages in
* kActiveApexPackagesDataDir
*/
Result<void> RemovePreviouslyActiveApexFiles(
const std::unordered_set<std::string>& affected_packages,
const std::unordered_set<std::string>& files_to_keep) {
auto all_active_apex_files =
FindFilesBySuffix(gConfig->active_apex_data_dir, {kApexPackageSuffix});
if (!all_active_apex_files.ok()) {
return all_active_apex_files.error();
}
for (const std::string& path : *all_active_apex_files) {
Result<ApexFile> apex_file = ApexFile::Open(path);
if (!apex_file.ok()) {
return apex_file.error();
}
const std::string& package_name = apex_file->GetManifest().name();
if (affected_packages.find(package_name) == affected_packages.end()) {
// This apex belongs to a package that wasn't part of this stage sessions,
// hence it should be kept.
continue;
}
if (files_to_keep.find(apex_file->GetPath()) != files_to_keep.end()) {
// This is a path that was staged and should be kept.
continue;
}
LOG(DEBUG) << "Deleting previously active apex " << apex_file->GetPath();
if (unlink(apex_file->GetPath().c_str()) != 0) {
return ErrnoError() << "Failed to unlink " << apex_file->GetPath();
}
}
return {};
}
// Reads the entire device to verify the image is authenticatic
Result<void> ReadVerityDevice(const std::string& verity_device,
uint64_t device_size) {
static constexpr int kBlockSize = 4096;
static constexpr size_t kBufSize = 1024 * kBlockSize;
std::vector<uint8_t> buffer(kBufSize);
unique_fd fd(
TEMP_FAILURE_RETRY(open(verity_device.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd.get() == -1) {
return ErrnoError() << "Can't open " << verity_device;
}
size_t bytes_left = device_size;
while (bytes_left > 0) {
size_t to_read = std::min(bytes_left, kBufSize);
if (!android::base::ReadFully(fd.get(), buffer.data(), to_read)) {
return ErrnoError() << "Can't verify " << verity_device << "; corrupted?";
}
bytes_left -= to_read;
}
return {};
}
Result<void> VerifyMountedImage(const ApexFile& apex,
const std::string& mount_point) {
// Verify that apex_manifest.pb inside mounted image matches the one in the
// outer .apex container.
Result<ApexManifest> verified_manifest =
ReadManifest(mount_point + "/" + kManifestFilenamePb);
if (!verified_manifest.ok()) {
return verified_manifest.error();
}
if (!MessageDifferencer::Equals(*verified_manifest, apex.GetManifest())) {
return Errorf(
"Manifest inside filesystem does not match manifest outside it");
}
if (shim::IsShimApex(apex)) {
return shim::ValidateShimApex(mount_point, apex);
}
return {};
}
Result<MountedApexData> MountPackageImpl(const ApexFile& apex,
const std::string& mount_point,
const std::string& device_name,
const std::string& hashtree_file,
bool verify_image, bool reuse_device,
bool temp_mount = false) {
auto tag = "MountPackageImpl: " + apex.GetManifest().name();
ATRACE_NAME(tag.c_str());
if (apex.IsCompressed()) {
return Error() << "Cannot directly mount compressed APEX "
<< apex.GetPath();
}
LOG(VERBOSE) << "Creating mount point: " << mount_point;
auto time_started = boot_clock::now();
// Note: the mount point could exist in case when the APEX was activated
// during the bootstrap phase (e.g., the runtime or tzdata APEX).
// Although we have separate mount namespaces to separate the early activated
// APEXes from the normally activate APEXes, the mount points themselves
// are shared across the two mount namespaces because /apex (a tmpfs) itself
// mounted at / which is (and has to be) a shared mount. Therefore, if apexd
// finds an empty directory under /apex, it's not a problem and apexd can use
// it.
auto exists = PathExists(mount_point);
if (!exists.ok()) {
return exists.error();
}
if (!*exists && mkdir(mount_point.c_str(), kMkdirMode) != 0) {
return ErrnoError() << "Could not create mount point " << mount_point;
}
auto deleter = [&mount_point]() {
if (rmdir(mount_point.c_str()) != 0) {
PLOG(WARNING) << "Could not rmdir " << mount_point;
}
};
auto scope_guard = android::base::make_scope_guard(deleter);
if (!IsEmptyDirectory(mount_point)) {
return ErrnoError() << mount_point << " is not empty";
}
const std::string& full_path = apex.GetPath();
if (!apex.GetImageOffset() || !apex.GetImageSize()) {
return Error() << "Cannot create mount point without image offset and size";
}
loop::LoopbackDeviceUniqueFd loopback_device;
for (size_t attempts = 1;; ++attempts) {
Result<loop::LoopbackDeviceUniqueFd> ret =
loop::CreateAndConfigureLoopDevice(full_path,
apex.GetImageOffset().value(),
apex.GetImageSize().value());
if (ret.ok()) {
loopback_device = std::move(*ret);
break;
}
if (attempts >= kLoopDeviceSetupAttempts) {
return Error() << "Could not create loop device for " << full_path << ": "
<< ret.error();
}
}
LOG(VERBOSE) << "Loopback device created: " << loopback_device.name;
auto& instance = ApexFileRepository::GetInstance();
auto public_key = instance.GetPublicKey(apex.GetManifest().name());
if (!public_key.ok()) {
return public_key.error();
}
auto verity_data = apex.VerifyApexVerity(*public_key);
if (!verity_data.ok()) {
return Error() << "Failed to verify Apex Verity data for " << full_path
<< ": " << verity_data.error();
}
if (instance.IsBlockApex(apex)) {
auto root_digest = instance.GetBlockApexRootDigest(apex.GetPath());
if (root_digest.has_value() &&
root_digest.value() != verity_data->root_digest) {
return Error() << "Failed to verify Apex Verity data for " << full_path
<< ": root digest (" << verity_data->root_digest
<< ") mismatches with the one (" << root_digest.value()
<< ") specified in config";
}
}
std::string block_device = loopback_device.name;
MountedApexData apex_data(apex.GetManifest().version(), loopback_device.name,
apex.GetPath(), mount_point,
/* device_name = */ "",
/* hashtree_loop_name = */ "",
/* is_temp_mount */ temp_mount);
// for APEXes in immutable partitions, we don't need to mount them on
// dm-verity because they are already in the dm-verity protected partition;
// system. However, note that we don't skip verification to ensure that APEXes
// are correctly signed.
const bool mount_on_verity = !instance.IsPreInstalledApex(apex) ||
// decompressed apexes are on /data
instance.IsDecompressedApex(apex) ||
// block apexes are from host
instance.IsBlockApex(apex);
DmVerityDevice verity_dev;
loop::LoopbackDeviceUniqueFd loop_for_hash;
if (mount_on_verity) {
std::string hash_device = loopback_device.name;
if (verity_data->desc->tree_size == 0) {
if (auto st = PrepareHashTree(apex, *verity_data, hashtree_file);
!st.ok()) {
return st.error();
}
auto create_loop_status =
loop::CreateAndConfigureLoopDevice(hashtree_file,
/* image_offset= */ 0,
/* image_size= */ 0);
if (!create_loop_status.ok()) {
return create_loop_status.error();
}
loop_for_hash = std::move(*create_loop_status);
hash_device = loop_for_hash.name;
apex_data.hashtree_loop_name = hash_device;
}
auto verity_table =
CreateVerityTable(*verity_data, loopback_device.name, hash_device,
/* restart_on_corruption = */ !verify_image);
Result<DmVerityDevice> verity_dev_res =
CreateVerityDevice(device_name, *verity_table, reuse_device);
if (!verity_dev_res.ok()) {
return Error() << "Failed to create Apex Verity device " << full_path
<< ": " << verity_dev_res.error();
}
verity_dev = std::move(*verity_dev_res);
apex_data.device_name = device_name;
block_device = verity_dev.GetDevPath();
Result<void> read_ahead_status =
loop::ConfigureReadAhead(verity_dev.GetDevPath());
if (!read_ahead_status.ok()) {
return read_ahead_status.error();
}
}
// TODO(b/158467418): consider moving this inside RunVerifyFnInsideTempMount.
if (mount_on_verity && verify_image) {
Result<void> verity_status =
ReadVerityDevice(block_device, (*verity_data).desc->image_size);
if (!verity_status.ok()) {
return verity_status.error();
}
}
uint32_t mount_flags = MS_NOATIME | MS_NODEV | MS_DIRSYNC | MS_RDONLY;
if (apex.GetManifest().nocode()) {
mount_flags |= MS_NOEXEC;
}
if (!apex.GetFsType()) {
return Error() << "Cannot mount package without FsType";
}
if (mount(block_device.c_str(), mount_point.c_str(),
apex.GetFsType().value().c_str(), mount_flags, nullptr) == 0) {
auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
boot_clock::now() - time_started).count();
LOG(INFO) << "Successfully mounted package " << full_path << " on "
<< mount_point << " duration=" << time_elapsed;
auto status = VerifyMountedImage(apex, mount_point);
if (!status.ok()) {
if (umount2(mount_point.c_str(), UMOUNT_NOFOLLOW) != 0) {
PLOG(ERROR) << "Failed to umount " << mount_point;
}
return Error() << "Failed to verify " << full_path << ": "
<< status.error();
}
// Time to accept the temporaries as good.
verity_dev.Release();
loopback_device.CloseGood();
loop_for_hash.CloseGood();
scope_guard.Disable(); // Accept the mount.
return apex_data;
} else {
return ErrnoError() << "Mounting failed for package " << full_path;
}
}
std::string GetHashTreeFileName(const ApexFile& apex, bool is_new) {
const std::string& id = GetPackageId(apex.GetManifest());
std::string ret =
StringPrintf("%s/%s", gConfig->apex_hash_tree_dir, id.c_str());
return is_new ? ret + ".new" : ret;
}
Result<MountedApexData> VerifyAndTempMountPackage(
const ApexFile& apex, const std::string& mount_point) {
const std::string& package_id = GetPackageId(apex.GetManifest());
LOG(DEBUG) << "Temp mounting " << package_id << " to " << mount_point;
const std::string& temp_device_name = package_id + ".tmp";
std::string hashtree_file = GetHashTreeFileName(apex, /* is_new = */ true);
if (access(hashtree_file.c_str(), F_OK) == 0) {
LOG(DEBUG) << hashtree_file << " already exists. Deleting it";
if (TEMP_FAILURE_RETRY(unlink(hashtree_file.c_str())) != 0) {
return ErrnoError() << "Failed to unlink " << hashtree_file;
}
}
auto ret =
MountPackageImpl(apex, mount_point, temp_device_name, hashtree_file,
/* verify_image = */ true, /* reuse_device= */ false,
/* temp_mount = */ true);
if (!ret.ok()) {
LOG(DEBUG) << "Cleaning up " << hashtree_file;
if (TEMP_FAILURE_RETRY(unlink(hashtree_file.c_str())) != 0) {
PLOG(ERROR) << "Failed to unlink " << hashtree_file;
}
} else {
gMountedApexes.AddMountedApex(apex.GetManifest().name(), *ret);
}
return ret;
}
} // namespace
Result<void> Unmount(const MountedApexData& data, bool deferred) {
LOG(DEBUG) << "Unmounting " << data.full_path << " from mount point "
<< data.mount_point << " deferred = " << deferred;
// Unmount whatever is mounted.
if (umount2(data.mount_point.c_str(), UMOUNT_NOFOLLOW) != 0 &&
errno != EINVAL && errno != ENOENT) {
return ErrnoError() << "Failed to unmount directory " << data.mount_point;
}
if (!deferred) {
if (rmdir(data.mount_point.c_str()) != 0) {
PLOG(ERROR) << "Failed to rmdir " << data.mount_point;
}
}
// Try to free up the device-mapper device.
if (!data.device_name.empty()) {
const auto& result = DeleteVerityDevice(data.device_name, deferred);
if (!result.ok()) {
return result;
}
}
// Try to free up the loop device.
auto log_fn = [](const std::string& path, const std::string& /*id*/) {
LOG(VERBOSE) << "Freeing loop device " << path << " for unmount.";
};
// Since we now use LO_FLAGS_AUTOCLEAR when configuring loop devices, in
// theory we don't need to manually call DestroyLoopDevice here even if
// |deferred| is false. However we prefer to call it to ensure the invariant
// of SubmitStagedSession (after it's done, loop devices created for temp
// mount are freed).
if (!data.loop_name.empty() && !deferred) {
loop::DestroyLoopDevice(data.loop_name, log_fn);
}
if (!data.hashtree_loop_name.empty() && !deferred) {
loop::DestroyLoopDevice(data.hashtree_loop_name, log_fn);
}
return {};
}
void SendApexInstallationRequestedAtom(const std::string& package_path,
const bool is_rollback,
const unsigned int install_type) {
if (!statssocket::lazy::IsAvailable()) {
LOG(WARNING) << "Unable to send Apex Atom; libstatssocket is not available";
return;
}
auto apex_file = ApexFile::Open(package_path);
if (!apex_file.ok()) {
LOG(WARNING) << "Unable to send Apex Atom; Failed to open ApexFile "
<< package_path << ": " << apex_file.error();
return;
}
const std::string& module_name = apex_file->GetManifest().name();
struct stat stat_buf;
intmax_t apex_file_size;
if (stat(package_path.c_str(), &stat_buf) == 0) {
apex_file_size = stat_buf.st_size;
} else {
PLOG(WARNING) << "Failed to stat " << package_path;
apex_file_size = 0;
}
Result<std::string> apex_file_sha256_str = CalculateSha256(package_path);
if (!apex_file_sha256_str.ok()) {
LOG(WARNING) << "Unable to get sha256 of ApexFile: "
<< apex_file_sha256_str.error();
}
const std::vector<const char*> hal_cstr_list;
int ret = stats::apex::stats_write(
stats::apex::APEX_INSTALLATION_REQUESTED, module_name.c_str(),
apex_file->GetManifest().version(), apex_file_size,
apex_file_sha256_str->c_str(), GetPreinstallPartitionEnum(*apex_file),
install_type, is_rollback,
apex_file->GetManifest().providesharedapexlibs(), hal_cstr_list);
if (ret < 0) {
LOG(WARNING) << "Failed to report apex_installation_requested stats";
}
}
void SendApexInstallationEndedAtom(const std::string apex_package_path,
int install_result) {
if (!statssocket::lazy::IsAvailable()) {
LOG(WARNING) << "Unable to send Apex Atom; libstatssocket is not available";
return;
}
Result<std::string> apex_file_sha256_str = CalculateSha256(apex_package_path);
if (!apex_file_sha256_str.ok()) {
LOG(WARNING) << "Unable to get sha256 of ApexFile: "
<< apex_file_sha256_str.error();
}
int ret =
stats::apex::stats_write(stats::apex::APEX_INSTALLATION_ENDED,
apex_file_sha256_str->c_str(), install_result);
if (ret < 0) {
LOG(WARNING) << "Failed to report apex_installation_ended stats";
}
}
namespace {
template <typename VerifyFn>
Result<void> RunVerifyFnInsideTempMount(const ApexFile& apex,
const VerifyFn& verify_fn,
bool unmount_during_cleanup) {
// Temp mount image of this apex to validate it was properly signed;
// this will also read the entire block device through dm-verity, so
// we can be sure there is no corruption.
const std::string& temp_mount_point =
apexd_private::GetPackageTempMountPoint(apex.GetManifest());
Result<MountedApexData> mount_status =
VerifyAndTempMountPackage(apex, temp_mount_point);
if (!mount_status.ok()) {
LOG(ERROR) << "Failed to temp mount to " << temp_mount_point << " : "
<< mount_status.error();
return mount_status.error();
}
auto cleaner = [&]() {
LOG(DEBUG) << "Unmounting " << temp_mount_point;
Result<void> result = Unmount(*mount_status, /* deferred= */ false);
if (!result.ok()) {
LOG(WARNING) << "Failed to unmount " << temp_mount_point << " : "
<< result.error();
}
gMountedApexes.RemoveMountedApex(apex.GetManifest().name(), apex.GetPath(),
true);
};
auto scope_guard = android::base::make_scope_guard(cleaner);
auto result = verify_fn(temp_mount_point);
if (!result.ok()) {
return result.error();
}
if (!unmount_during_cleanup) {
scope_guard.Disable();
}
return {};
}
// Converts a list of apex file paths into a list of ApexFile objects
//
// Returns error when trying to open empty set of inputs.
Result<std::vector<ApexFile>> OpenApexFiles(
const std::vector<std::string>& paths) {
if (paths.empty()) {
return Errorf("Empty set of inputs");
}
std::vector<ApexFile> ret;
for (const std::string& path : paths) {
Result<ApexFile> apex_file = ApexFile::Open(path);
if (!apex_file.ok()) {
return apex_file.error();
}
ret.emplace_back(std::move(*apex_file));
}
return ret;
}
Result<void> ValidateStagingShimApex(const ApexFile& to) {
using android::base::StringPrintf;
auto system_shim = ApexFile::Open(
StringPrintf("%s/%s", kApexPackageSystemDir, shim::kSystemShimApexName));
if (!system_shim.ok()) {
return system_shim.error();
}
auto verify_fn = [&](const std::string& system_apex_path) {
return shim::ValidateUpdate(system_apex_path, to.GetPath());
};
return RunVerifyFnInsideTempMount(*system_shim, verify_fn, true);
}
Result<void> VerifyVndkVersion(const ApexFile& apex_file) {
const std::string& vndk_version = apex_file.GetManifest().vndkversion();
if (vndk_version.empty()) {
return {};
}
static std::string vendor_vndk_version = GetProperty("ro.vndk.version", "");
static std::string product_vndk_version =
GetProperty("ro.product.vndk.version", "");
const auto& instance = ApexFileRepository::GetInstance();
const auto& preinstalled =
instance.GetPreInstalledApex(apex_file.GetManifest().name());
const auto& preinstalled_path = preinstalled.get().GetPath();
if (StartsWith(preinstalled_path, "/vendor/apex/") ||
StartsWith(preinstalled_path, "/system/vendor/apex/")) {
if (vndk_version != vendor_vndk_version) {
return Error() << "vndkVersion(" << vndk_version
<< ") doesn't match with device VNDK version("
<< vendor_vndk_version << ")";
}
return {};
}
if (StartsWith(preinstalled_path, "/product/apex/") ||
StartsWith(preinstalled_path, "/system/product/apex/")) {
if (vndk_version != product_vndk_version) {
return Error() << "vndkVersion(" << vndk_version
<< ") doesn't match with device VNDK version("
<< product_vndk_version << ")";
}
return {};
}
return Error() << "vndkVersion(" << vndk_version << ") is set";
}
// A version of apex verification that happens during boot.
// This function should only verification checks that are necessary to run on
// each boot. Try to avoid putting expensive checks inside this function.
Result<void> VerifyPackageBoot(const ApexFile& apex_file) {
// TODO(ioffe): why do we need this here?
auto& instance = ApexFileRepository::GetInstance();
auto public_key = instance.GetPublicKey(apex_file.GetManifest().name());
if (!public_key.ok()) {
return public_key.error();
}
Result<ApexVerityData> verity_or = apex_file.VerifyApexVerity(*public_key);
if (!verity_or.ok()) {
return verity_or.error();
}
if (shim::IsShimApex(apex_file)) {
// Validating shim is not a very cheap operation, but it's fine to perform
// it here since it only runs during CTS tests and will never be triggered
// during normal flow.
const auto& result = ValidateStagingShimApex(apex_file);
if (!result.ok()) {
return result;
}
}
if (auto result = VerifyVndkVersion(apex_file); !result.ok()) {
return result;
}
return {};
}
// A version of apex verification that happens on SubmitStagedSession.
// This function contains checks that might be expensive to perform, e.g. temp
// mounting a package and reading entire dm-verity device, and shouldn't be run
// during boot.
Result<void> VerifyPackageStagedInstall(const ApexFile& apex_file) {
const auto& verify_package_boot_status = VerifyPackageBoot(apex_file);
if (!verify_package_boot_status.ok()) {
return verify_package_boot_status;
}
const auto validate_fn = [&apex_file](const std::string& mount_point) {
if (IsVendorApex(apex_file)) {
return CheckVendorApexUpdate(apex_file, mount_point);
}
return Result<void>{};
};
return RunVerifyFnInsideTempMount(apex_file, validate_fn, false);
}
template <typename VerifyApexFn>
Result<std::vector<ApexFile>> VerifyPackages(
const std::vector<std::string>& paths, const VerifyApexFn& verify_apex_fn) {
Result<std::vector<ApexFile>> apex_files = OpenApexFiles(paths);
if (!apex_files.ok()) {
return apex_files.error();
}
LOG(DEBUG) << "VerifyPackages() for " << Join(paths, ',');
for (const ApexFile& apex_file : *apex_files) {
Result<void> result = verify_apex_fn(apex_file);
if (!result.ok()) {
return result.error();
}
}
return std::move(*apex_files);
}
Result<ApexFile> VerifySessionDir(int session_id) {
std::string session_dir_path =
StringPrintf("%s/session_%d", gConfig->staged_session_dir, session_id);
LOG(INFO) << "Scanning " << session_dir_path
<< " looking for packages to be validated";
Result<std::vector<std::string>> scan =
FindFilesBySuffix(session_dir_path, {kApexPackageSuffix});
if (!scan.ok()) {
LOG(WARNING) << scan.error();
return scan.error();
}
if (scan->size() > 1) {
return Errorf(
"More than one APEX package found in the same session directory.");
}
auto verified = VerifyPackages(*scan, VerifyPackageStagedInstall);
if (!verified.ok()) {
return verified.error();
}
return std::move((*verified)[0]);
}
Result<void> DeleteBackup() {
auto exists = PathExists(std::string(kApexBackupDir));
if (!exists.ok()) {
return Error() << "Can't clean " << kApexBackupDir << " : "
<< exists.error();
}
if (!*exists) {
LOG(DEBUG) << kApexBackupDir << " does not exist. Nothing to clean";
return {};
}
return DeleteDirContent(std::string(kApexBackupDir));
}
Result<void> BackupActivePackages() {
LOG(DEBUG) << "Initializing backup of " << gConfig->active_apex_data_dir;
// Previous restore might've delete backups folder.
auto create_status = CreateDirIfNeeded(kApexBackupDir, 0700);
if (!create_status.ok()) {
return Error() << "Backup failed : " << create_status.error();
}
auto apex_active_exists =
PathExists(std::string(gConfig->active_apex_data_dir));
if (!apex_active_exists.ok()) {
return Error() << "Backup failed : " << apex_active_exists.error();
}
if (!*apex_active_exists) {
LOG(DEBUG) << gConfig->active_apex_data_dir
<< " does not exist. Nothing to backup";
return {};
}
auto active_packages =
FindFilesBySuffix(gConfig->active_apex_data_dir, {kApexPackageSuffix});
if (!active_packages.ok()) {
return Error() << "Backup failed : " << active_packages.error();
}
auto cleanup_status = DeleteBackup();
if (!cleanup_status.ok()) {
return Error() << "Backup failed : " << cleanup_status.error();
}
auto backup_path_fn = [](const ApexFile& apex_file) {
return StringPrintf("%s/%s%s", kApexBackupDir,
GetPackageId(apex_file.GetManifest()).c_str(),
kApexPackageSuffix);
};
auto deleter = []() {
auto result = DeleteDirContent(std::string(kApexBackupDir));
if (!result.ok()) {
LOG(ERROR) << "Failed to cleanup " << kApexBackupDir << " : "
<< result.error();
}
};
auto scope_guard = android::base::make_scope_guard(deleter);
for (const std::string& path : *active_packages) {
Result<ApexFile> apex_file = ApexFile::Open(path);
if (!apex_file.ok()) {
return Error() << "Backup failed : " << apex_file.error();
}
const auto& dest_path = backup_path_fn(*apex_file);
if (link(apex_file->GetPath().c_str(), dest_path.c_str()) != 0) {
return ErrnoError() << "Failed to backup " << apex_file->GetPath();
}
}
scope_guard.Disable(); // Accept the backup.
return {};
}
Result<void> RestoreActivePackages() {
LOG(DEBUG) << "Initializing restore of " << gConfig->active_apex_data_dir;
auto backup_exists = PathExists(std::string(kApexBackupDir));
if (!backup_exists.ok()) {
return backup_exists.error();
}
if (!*backup_exists) {
return Error() << kApexBackupDir << " does not exist";
}
struct stat stat_data;
if (stat(gConfig->active_apex_data_dir, &stat_data) != 0) {
return ErrnoError() << "Failed to access " << gConfig->active_apex_data_dir;
}
LOG(DEBUG) << "Deleting existing packages in "
<< gConfig->active_apex_data_dir;
auto delete_status =
DeleteDirContent(std::string(gConfig->active_apex_data_dir));
if (!delete_status.ok()) {
return delete_status;
}
LOG(DEBUG) << "Renaming " << kApexBackupDir << " to "
<< gConfig->active_apex_data_dir;
if (rename(kApexBackupDir, gConfig->active_apex_data_dir) != 0) {
return ErrnoError() << "Failed to rename " << kApexBackupDir << " to "
<< gConfig->active_apex_data_dir;
}
LOG(DEBUG) << "Restoring original permissions for "
<< gConfig->active_apex_data_dir;
if (chmod(gConfig->active_apex_data_dir, stat_data.st_mode & ALLPERMS) != 0) {
return ErrnoError() << "Failed to restore original permissions for "
<< gConfig->active_apex_data_dir;
}
return {};
}
Result<void> UnmountPackage(const ApexFile& apex, bool allow_latest,
bool deferred, bool detach_mount_point) {
LOG(INFO) << "Unmounting " << GetPackageId(apex.GetManifest())
<< " allow_latest : " << allow_latest << " deferred : " << deferred
<< " detach_mount_point : " << detach_mount_point;
const ApexManifest& manifest = apex.GetManifest();
std::optional<MountedApexData> data;
bool latest = false;
auto fn = [&](const MountedApexData& d, bool l) {
if (d.full_path == apex.GetPath()) {
data.emplace(d);
latest = l;
}
};
gMountedApexes.ForallMountedApexes(manifest.name(), fn);
if (!data) {
return Error() << "Did not find " << apex.GetPath();
}
// Concept of latest sharedlibs apex is somewhat blurred. Since this is only
// used in testing, it is ok to always allow unmounting sharedlibs apex.
if (latest && !manifest.providesharedapexlibs()) {
if (!allow_latest) {
return Error() << "Package " << apex.GetPath() << " is active";
}
std::string mount_point = apexd_private::GetActiveMountPoint(manifest);
LOG(INFO) << "Unmounting " << mount_point;
int flags = UMOUNT_NOFOLLOW;
if (detach_mount_point) {
flags |= MNT_DETACH;
}
if (umount2(mount_point.c_str(), flags) != 0) {
return ErrnoError() << "Failed to unmount " << mount_point;
}
if (!deferred) {
if (rmdir(mount_point.c_str()) != 0) {
PLOG(ERROR) << "Failed to rmdir " << mount_point;
}
}
}
// Clean up gMountedApexes now, even though we're not fully done.
gMountedApexes.RemoveMountedApex(manifest.name(), apex.GetPath());
return Unmount(*data, deferred);
}
} // namespace
void SetConfig(const ApexdConfig& config) { gConfig = config; }
Result<void> MountPackage(const ApexFile& apex, const std::string& mount_point,
const std::string& device_name, bool reuse_device,
bool temp_mount) {
auto ret =
MountPackageImpl(apex, mount_point, device_name,
GetHashTreeFileName(apex, /* is_new= */ false),
/* verify_image = */ false, reuse_device, temp_mount);
if (!ret.ok()) {
return ret.error();
}
gMountedApexes.AddMountedApex(apex.GetManifest().name(), *ret);
return {};
}
namespace apexd_private {
Result<void> UnmountTempMount(const ApexFile& apex) {
const ApexManifest& manifest = apex.GetManifest();
LOG(VERBOSE) << "Unmounting all temp mounts for package " << manifest.name();
bool finished_unmounting = false;
// If multiple temp mounts exist, ensure that all are unmounted.
while (!finished_unmounting) {
Result<MountedApexData> data =
apexd_private::GetTempMountedApexData(manifest.name());
if (!data.ok()) {
finished_unmounting = true;
} else {
gMountedApexes.RemoveMountedApex(manifest.name(), data->full_path, true);
Unmount(*data, /* deferred= */ false);
}
}
return {};
}
Result<MountedApexData> GetTempMountedApexData(const std::string& package) {
bool found = false;
Result<MountedApexData> mount_data;
gMountedApexes.ForallMountedApexes(
package,
[&](const MountedApexData& data, [[maybe_unused]] bool latest) {
if (!found) {
mount_data = data;
found = true;
}
},
true);
if (found) {
return mount_data;
}
return Error() << "No temp mount data found for " << package;
}
bool IsMounted(const std::string& full_path) {
bool found_mounted = false;
gMountedApexes.ForallMountedApexes([&](const std::string&,
const MountedApexData& data,
[[maybe_unused]] bool latest) {
if (full_path == data.full_path) {
found_mounted = true;
}
});
return found_mounted;
}
std::string GetPackageMountPoint(const ApexManifest& manifest) {
return StringPrintf("%s/%s", kApexRoot, GetPackageId(manifest).c_str());
}
std::string GetPackageTempMountPoint(const ApexManifest& manifest) {
return StringPrintf("%s.tmp", GetPackageMountPoint(manifest).c_str());
}
std::string GetActiveMountPoint(const ApexManifest& manifest) {
return StringPrintf("%s/%s", kApexRoot, manifest.name().c_str());
}
} // namespace apexd_private
Result<void> ResumeRevertIfNeeded() {
auto sessions =
gSessionManager->GetSessionsInState(SessionState::REVERT_IN_PROGRESS);
if (sessions.empty()) {
return {};
}
return RevertActiveSessions("", "");
}
Result<void> ContributeToSharedLibs(const std::string& mount_point) {
for (const auto& lib_path : {"lib", "lib64"}) {
std::string apex_lib_path = mount_point + "/" + lib_path;
auto lib_dir = PathExists(apex_lib_path);
if (!lib_dir.ok() || !*lib_dir) {
continue;
}
auto iter = std::filesystem::directory_iterator(apex_lib_path);
std::error_code ec;
while (iter != std::filesystem::end(iter)) {
const auto& lib_entry = *iter;
if (!lib_entry.is_directory()) {
iter = iter.increment(ec);
if (ec) {
return Error() << "Failed to scan " << apex_lib_path << " : "
<< ec.message();
}
continue;
}
const auto library_name = lib_entry.path().filename();
const std::string library_symlink_dir =
StringPrintf("%s/%s/%s/%s", kApexRoot, kApexSharedLibsSubDir,
lib_path, library_name.c_str());
auto symlink_dir = PathExists(library_symlink_dir);
if (!symlink_dir.ok() || !*symlink_dir) {
std::filesystem::create_directory(library_symlink_dir, ec);
if (ec) {
return Error() << "Failed to create directory " << library_symlink_dir
<< ": " << ec.message();
}
}
auto inner_iter =
std::filesystem::directory_iterator(lib_entry.path().string());
while (inner_iter != std::filesystem::end(inner_iter)) {
const auto& lib_items = *inner_iter;
const auto hash_value = lib_items.path().filename();
const std::string library_symlink_hash = StringPrintf(
"%s/%s", library_symlink_dir.c_str(), hash_value.c_str());
auto hash_dir = PathExists(library_symlink_hash);
if (hash_dir.ok() && *hash_dir) {
// Compare file size for two library files with same name and hash
// value
auto existing_file_path =
library_symlink_hash + "/" + library_name.string();
auto existing_file_size = GetFileSize(existing_file_path);
if (!existing_file_size.ok()) {
return existing_file_size.error();
}
auto new_file_path =
lib_items.path().string() + "/" + library_name.string();
auto new_file_size = GetFileSize(new_file_path);
if (!new_file_size.ok()) {
return new_file_size.error();
}
if (*existing_file_size != *new_file_size) {
return Error() << "There are two libraries with same hash and "
"different file size : "
<< existing_file_path << " and " << new_file_path;
}
inner_iter = inner_iter.increment(ec);
if (ec) {
return Error() << "Failed to scan " << lib_entry.path().string()
<< " : " << ec.message();
}
continue;
}
std::filesystem::create_directory_symlink(lib_items.path(),
library_symlink_hash, ec);
if (ec) {
return Error() << "Failed to create symlink from " << lib_items.path()
<< " to " << library_symlink_hash << ec.message();
}
inner_iter = inner_iter.increment(ec);
if (ec) {
return Error() << "Failed to scan " << lib_entry.path().string()
<< " : " << ec.message();
}
}
iter = iter.increment(ec);
if (ec) {
return Error() << "Failed to scan " << apex_lib_path << " : "
<< ec.message();
}
}
}
return {};
}
bool IsValidPackageName(const std::string& package_name) {
return kBannedApexName.count(package_name) == 0;
}
// Activates given APEX file.
//
// In a nutshel activation of an APEX consist of the following steps:
// 1. Create loop devices that is backed by the given apex_file
// 2. If apex_file resides on /data partition then create a dm-verity device
// backed by the loop device created in step (1).
// 3. Create a mount point under /apex for this APEX.
// 4. Mount the dm-verity device on that mount point.
// 4.1 In case APEX file comes from a partition that is already
// dm-verity protected (e.g. /system) then we mount the loop device.
//
//
// Note: this function only does the job to activate this single APEX.
// In case this APEX file contributes to the /apex/sharedlibs mount point, then
// you must also call ContributeToSharedLibs after finishing activating all
// APEXes. See ActivateApexPackages for more context.
Result<void> ActivatePackageImpl(const ApexFile& apex_file,
const std::string& device_name,
bool reuse_device) {
ATRACE_NAME("ActivatePackageImpl");
const ApexManifest& manifest = apex_file.GetManifest();
if (!IsValidPackageName(manifest.name())) {
return Errorf("Package name {} is not allowed.", manifest.name());
}
// Validate upgraded shim apex
if (shim::IsShimApex(apex_file) &&
!ApexFileRepository::GetInstance().IsPreInstalledApex(apex_file)) {
// This is not cheap for shim apex, but it is fine here since we have
// upgraded shim apex only during CTS tests.
Result<void> result = VerifyPackageBoot(apex_file);
if (!result.ok()) {
LOG(ERROR) << "Failed to validate shim apex: " << apex_file.GetPath();
return result;
}
}
// See whether we think it's active, and do not allow to activate the same
// version. Also detect whether this is the highest version.
// We roll this into a single check.
bool version_found_mounted = false;
{
uint64_t new_version = manifest.version();
bool version_found_active = false;
gMountedApexes.ForallMountedApexes(
manifest.name(), [&](const MountedApexData& data, bool latest) {
Result<ApexFile> other_apex = ApexFile::Open(data.full_path);
if (!other_apex.ok()) {
return;
}
if (static_cast<uint64_t>(other_apex->GetManifest().version()) ==
new_version) {
version_found_mounted = true;
version_found_active = latest;
}
});
// If the package provides shared libraries to other APEXs, we need to
// activate all versions available (i.e. preloaded on /system/apex and
// available on /data/apex/active). The reason is that there might be some
// APEXs loaded from /system/apex that reference the libraries contained on
// the preloaded version of the apex providing shared libraries.
if (version_found_active && !manifest.providesharedapexlibs()) {
LOG(DEBUG) << "Package " << manifest.name() << " with version "
<< manifest.version() << " already active";
return {};
}
}
const std::string& mount_point =
apexd_private::GetPackageMountPoint(manifest);
if (!version_found_mounted) {
auto mount_status = MountPackage(apex_file, mount_point, device_name,
reuse_device, /*temp_mount=*/false);
if (!mount_status.ok()) {
return mount_status;
}
}
// Bind mount the latest version to /apex/<package_name>, unless the
// package provides shared libraries to other APEXs.
if (!manifest.providesharedapexlibs()) {
auto st = gMountedApexes.DoIfLatest(
manifest.name(), apex_file.GetPath(), [&]() -> Result<void> {
return apexd_private::BindMount(
apexd_private::GetActiveMountPoint(manifest), mount_point);
});
if (!st.ok()) {
return Error() << "Failed to update package " << manifest.name()
<< " to version " << manifest.version() << " : "
<< st.error();
}
}
LOG(DEBUG) << "Successfully activated " << apex_file.GetPath()
<< " package_name: " << manifest.name()
<< " version: " << manifest.version();
return {};
}
// Wrapper around ActivatePackageImpl.
// Do not use, this wrapper is going away.
Result<void> ActivatePackage(const std::string& full_path) {
LOG(INFO) << "Trying to activate " << full_path;
Result<ApexFile> apex_file = ApexFile::Open(full_path);
if (!apex_file.ok()) {
return apex_file.error();
}
return ActivatePackageImpl(*apex_file, GetPackageId(apex_file->GetManifest()),
/* reuse_device= */ false);
}
Result<void> DeactivatePackage(const std::string& full_path) {
LOG(INFO) << "Trying to deactivate " << full_path;
Result<ApexFile> apex_file = ApexFile::Open(full_path);
if (!apex_file.ok()) {
return apex_file.error();
}
return UnmountPackage(*apex_file, /* allow_latest= */ true,
/* deferred= */ false, /* detach_mount_point= */ false);
}
Result<std::vector<ApexFile>> GetStagedApexFiles(
int session_id, const std::vector<int>& child_session_ids) {
auto session = gSessionManager->GetSession(session_id);
if (!session.ok()) {
return session.error();
}
// We should only accept sessions in SessionState::STAGED state
auto session_state = (*session).GetState();
if (session_state != SessionState::STAGED) {
return Error() << "Session " << session_id << " is not in state STAGED";
}
std::vector<int> ids_to_scan;
if (!child_session_ids.empty()) {
ids_to_scan = child_session_ids;
} else {
ids_to_scan = {session_id};
}
// Find apex files in the staging directory
std::vector<std::string> apex_file_paths;
for (int id_to_scan : ids_to_scan) {
std::string session_dir_path = std::string(gConfig->staged_session_dir) +
"/session_" + std::to_string(id_to_scan);
Result<std::vector<std::string>> scan =
FindFilesBySuffix(session_dir_path, {kApexPackageSuffix});
if (!scan.ok()) {
return scan.error();
}
if (scan->size() != 1) {
return Error() << "Expected exactly one APEX file in directory "
<< session_dir_path << ". Found: " << scan->size();
}
std::string& apex_file_path = (*scan)[0];
apex_file_paths.push_back(std::move(apex_file_path));
}
return OpenApexFiles(apex_file_paths);
}
Result<ClassPath> MountAndDeriveClassPath(
const std::vector<ApexFile>& apex_files) {
auto guard = android::base::make_scope_guard([&]() {
for (const auto& apex : apex_files) {
apexd_private::UnmountTempMount(apex);
}
});
// Mount the staged apex files
std::vector<std::string> temp_mounted_apex_paths;
for (const auto& apex : apex_files) {
const std::string& temp_mount_point =
apexd_private::GetPackageTempMountPoint(apex.GetManifest());
const std::string& package_id = GetPackageId(apex.GetManifest());
const std::string& temp_device_name = package_id + ".tmp";
auto mount_status =
MountPackage(apex, temp_mount_point, temp_device_name,
/*reuse_device=*/false, /*temp_mount=*/true);
if (!mount_status.ok()) {
return mount_status.error();
}
temp_mounted_apex_paths.push_back(temp_mount_point);
}
// Calculate classpaths of temp mounted staged apexs
return ClassPath::DeriveClassPath(temp_mounted_apex_paths);
}
std::vector<ApexFile> GetActivePackages() {
std::vector<ApexFile> ret;
gMountedApexes.ForallMountedApexes(
[&](const std::string&, const MountedApexData& data, bool latest) {
if (!latest) {
return;
}
Result<ApexFile> apex_file = ApexFile::Open(data.full_path);
if (!apex_file.ok()) {
return;
}
ret.emplace_back(std::move(*apex_file));
});
return ret;
}
std::vector<ApexFile> CalculateInactivePackages(
const std::vector<ApexFile>& active) {
std::vector<ApexFile> inactive = GetFactoryPackages();
auto new_end = std::remove_if(
inactive.begin(), inactive.end(), [&active](const ApexFile& apex) {
return std::any_of(active.begin(), active.end(),
[&apex](const ApexFile& active_apex) {
return apex.GetPath() == active_apex.GetPath();
});
});
inactive.erase(new_end, inactive.end());
return std::move(inactive);
}
Result<void> EmitApexInfoList(bool is_bootstrap) {
// Apexd runs both in "bootstrap" and "default" mount namespace.
// To expose /apex/apex-info-list.xml separately in each mount namespaces,
// we write /apex/.<namespace>-apex-info-list .xml file first and then
// bind mount it to the canonical file (/apex/apex-info-list.xml).
const std::string file_name =
fmt::format("{}/.{}-{}", kApexRoot,
is_bootstrap ? "bootstrap" : "default", kApexInfoList);
unique_fd fd(TEMP_FAILURE_RETRY(
open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)));
if (fd.get() == -1) {
return ErrnoErrorf("Can't open {}", file_name);
}
const std::vector<ApexFile> active(GetActivePackages());
std::vector<ApexFile> inactive;
// we skip for non-activated built-in apexes in bootstrap mode
// in order to avoid boottime increase
if (!is_bootstrap) {
inactive = CalculateInactivePackages(active);
}
std::stringstream xml;
CollectApexInfoList(xml, active, inactive);
if (!android::base::WriteStringToFd(xml.str(), fd)) {
return ErrnoErrorf("Can't write to {}", file_name);
}
fd.reset();
const std::string mount_point =
fmt::format("{}/{}", kApexRoot, kApexInfoList);
if (access(mount_point.c_str(), F_OK) != 0) {
close(open(mount_point.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
0644));
}
if (mount(file_name.c_str(), mount_point.c_str(), nullptr, MS_BIND,
nullptr) == -1) {
return ErrnoErrorf("Can't bind mount {} to {}", file_name, mount_point);
}
return RestoreconPath(file_name);
}
namespace {
std::unordered_map<std::string, uint64_t> GetActivePackagesMap() {
std::vector<ApexFile> active_packages = GetActivePackages();
std::unordered_map<std::string, uint64_t> ret;
for (const auto& package : active_packages) {
const ApexManifest& manifest = package.GetManifest();
ret.insert({manifest.name(), manifest.version()});
}
return ret;
}
} // namespace
std::vector<ApexFile> GetFactoryPackages() {
std::vector<ApexFile> ret;
// Decompressed APEX is considered factory package
std::vector<std::string> decompressed_pkg_names;
auto active_pkgs = GetActivePackages();
for (ApexFile& apex : active_pkgs) {
if (ApexFileRepository::GetInstance().IsDecompressedApex(apex)) {
decompressed_pkg_names.push_back(apex.GetManifest().name());
ret.emplace_back(std::move(apex));
}
}
const auto& file_repository = ApexFileRepository::GetInstance();
for (const auto& ref : file_repository.GetPreInstalledApexFiles()) {
Result<ApexFile> apex_file = ApexFile::Open(ref.get().GetPath());
if (!apex_file.ok()) {
LOG(ERROR) << apex_file.error();
continue;
}
// Ignore compressed APEX if it has been decompressed already
if (apex_file->IsCompressed() &&
std::find(decompressed_pkg_names.begin(), decompressed_pkg_names.end(),
apex_file->GetManifest().name()) !=
decompressed_pkg_names.end()) {
continue;
}
ret.emplace_back(std::move(*apex_file));
}
return ret;
}
Result<ApexFile> GetActivePackage(const std::string& packageName) {
std::vector<ApexFile> packages = GetActivePackages();
for (ApexFile& apex : packages) {
if (apex.GetManifest().name() == packageName) {
return std::move(apex);
}
}
return ErrnoError() << "Cannot find matching package for: " << packageName;
}
/**
* Abort individual staged session.
*
* Returns without error only if session was successfully aborted.
**/
Result<void> AbortStagedSession(int session_id) {
auto session = gSessionManager->GetSession(session_id);
if (!session.ok()) {
return Error() << "No session found with id " << session_id;
}
switch (session->GetState()) {
case SessionState::VERIFIED:
[[clang::fallthrough]];
case SessionState::STAGED:
return session->DeleteSession();
default:
return Error() << "Session " << *session << " can't be aborted";
}
}
namespace {
enum ActivationMode { kBootstrapMode = 0, kBootMode, kOtaChrootMode, kVmMode };
std::vector<Result<const ApexFile*>> ActivateApexWorker(
ActivationMode mode, std::queue<const ApexFile*>& apex_queue,
std::mutex& mutex) {
ATRACE_NAME("ActivateApexWorker");
std::vector<Result<const ApexFile*>> ret;
while (true) {
const ApexFile* apex;
{
std::lock_guard lock(mutex);
if (apex_queue.empty()) break;
apex = apex_queue.front();
apex_queue.pop();
}
std::string device_name;
if (mode == ActivationMode::kBootMode) {
device_name = apex->GetManifest().name();
} else {
device_name = GetPackageId(apex->GetManifest());
}
if (mode == ActivationMode::kOtaChrootMode) {
device_name += ".chroot";
}
bool reuse_device = mode == ActivationMode::kBootMode;
auto res = ActivatePackageImpl(*apex, device_name, reuse_device);
if (!res.ok()) {
ret.push_back(Error() << "Failed to activate " << apex->GetPath() << "("
<< device_name << "): " << res.error());
} else {
ret.push_back({apex});
}
}
return ret;
}
Result<void> ActivateApexPackages(const std::vector<ApexFileRef>& apexes,
ActivationMode mode) {
ATRACE_NAME("ActivateApexPackages");
std::queue<const ApexFile*> apex_queue;
std::mutex apex_queue_mutex;
for (const ApexFile& apex : apexes) {
apex_queue.emplace(&apex);
}
size_t worker_num =
android::sysprop::ApexProperties::boot_activation_threads().value_or(0);
// Setting number of workers to the number of packages to load
// This seems to provide the best performance
if (worker_num == 0) {
worker_num = apex_queue.size();
}
worker_num = std::min(apex_queue.size(), worker_num);
// On -eng builds there might be two different pre-installed art apexes.
// Attempting to activate them in parallel will result in UB (e.g.
// apexd-bootstrap might crash). In order to avoid this, for the time being on
// -eng builds activate apexes sequentially.
// TODO(b/176497601): remove this.
if (GetProperty("ro.build.type", "") == "eng") {
worker_num = 1;
}
std::vector<std::future<std::vector<Result<const ApexFile*>>>> futures;
futures.reserve(worker_num);
for (size_t i = 0; i < worker_num; i++) {
futures.push_back(std::async(std::launch::async, ActivateApexWorker,
std::ref(mode), std::ref(apex_queue),
std::ref(apex_queue_mutex)));
}
size_t activated_cnt = 0;
size_t failed_cnt = 0;
std::string error_message;
std::vector<const ApexFile*> activated_sharedlibs_apexes;
for (size_t i = 0; i < futures.size(); i++) {
for (const auto& res : futures[i].get()) {
if (res.ok()) {
++activated_cnt;
if (res.value()->GetManifest().providesharedapexlibs()) {
activated_sharedlibs_apexes.push_back(res.value());
}
} else {
++failed_cnt;
LOG(ERROR) << res.error();
if (failed_cnt == 1) {
error_message = res.error().message();
}
}
}
}
// We finished activation of APEX packages and now are ready to populate the
// /apex/sharedlibs mount point. Since there can be multiple different APEXes
// contributing to shared libs (at the point of writing this comment there can
// be up 2 APEXes: pre-installed sharedlibs APEX and its updated counterpart)
// we need to call ContributeToSharedLibs sequentially to avoid potential race
// conditions. See b/240291921
const auto& apex_repo = ApexFileRepository::GetInstance();
// To make things simpler we also provide an order in which APEXes contribute
// to sharedlibs.
auto cmp = [&apex_repo](const auto& apex_a, const auto& apex_b) {
// An APEX with higher version should contribute first
if (apex_a->GetManifest().version() != apex_b->GetManifest().version()) {
return apex_a->GetManifest().version() > apex_b->GetManifest().version();
}
// If they have the same version, then we pick the updated APEX first.
return !apex_repo.IsPreInstalledApex(*apex_a);
};
std::sort(activated_sharedlibs_apexes.begin(),
activated_sharedlibs_apexes.end(), cmp);
for (const auto& sharedlibs_apex : activated_sharedlibs_apexes) {
LOG(DEBUG) << "Populating sharedlibs with APEX "
<< sharedlibs_apex->GetPath() << " ( "
<< sharedlibs_apex->GetManifest().name()
<< " ) version : " << sharedlibs_apex->GetManifest().version();
auto mount_point =
apexd_private::GetPackageMountPoint(sharedlibs_apex->GetManifest());
if (auto ret = ContributeToSharedLibs(mount_point); !ret.ok()) {
LOG(ERROR) << "Failed to populate sharedlibs with APEX package "
<< sharedlibs_apex->GetPath() << " : " << ret.error();
++failed_cnt;
if (failed_cnt == 1) {
error_message = ret.error().message();
}
}
}
if (failed_cnt > 0) {
return Error() << "Failed to activate " << failed_cnt
<< " APEX packages. One of the errors: " << error_message;
}
LOG(INFO) << "Activated " << activated_cnt << " packages.";
return {};
}
// A fallback function in case some of the apexes failed to activate. For all
// such apexes that were coming from /data partition we will attempt to activate
// their corresponding pre-installed copies.
Result<void> ActivateMissingApexes(const std::vector<ApexFileRef>& apexes,
ActivationMode mode) {
LOG(INFO) << "Trying to activate pre-installed versions of missing apexes";
const auto& file_repository = ApexFileRepository::GetInstance();
const auto& activated_apexes = GetActivePackagesMap();
std::vector<ApexFileRef> fallback_apexes;
for (const auto& apex_ref : apexes) {
const auto& apex = apex_ref.get();
if (apex.GetManifest().providesharedapexlibs()) {
// We must mount both versions of sharedlibs apex anyway. Not much we can
// do here.
continue;
}
if (file_repository.IsPreInstalledApex(apex)) {
// We tried to activate pre-installed apex in the first place. No need to
// try again.
continue;
}
const std::string& name = apex.GetManifest().name();
if (activated_apexes.find(name) == activated_apexes.end()) {
fallback_apexes.push_back(file_repository.GetPreInstalledApex(name));
}
}
// Process compressed APEX, if any
std::vector<ApexFileRef> compressed_apex;
for (auto it = fallback_apexes.begin(); it != fallback_apexes.end();) {
if (it->get().IsCompressed()) {
compressed_apex.emplace_back(*it);
it = fallback_apexes.erase(it);
} else {
it++;
}
}
std::vector<ApexFile> decompressed_apex;
if (!compressed_apex.empty()) {
decompressed_apex = ProcessCompressedApex(
compressed_apex,
/* is_ota_chroot= */ mode == ActivationMode::kOtaChrootMode);
for (const ApexFile& apex_file : decompressed_apex) {
fallback_apexes.emplace_back(std::cref(apex_file));
}
}
if (mode == kBootMode) {
// Treat fallback to pre-installed APEXes as a change of the acitve APEX,
// since we are already in a pretty dire situation, so it's better if we
// drop all the caches.
for (const auto& apex : fallback_apexes) {
gChangedActiveApexes.insert(apex.get().GetManifest().name());
}
}
return ActivateApexPackages(fallback_apexes, mode);
}
} // namespace
/**
* Snapshots data from base_dir/apexdata/<apex name> to
* base_dir/apexrollback/<rollback id>/<apex name>.
*/
Result<void> SnapshotDataDirectory(const std::string& base_dir,
const int rollback_id,
const std::string& apex_name,
bool pre_restore = false) {
auto rollback_path =
StringPrintf("%s/%s/%d%s", base_dir.c_str(), kApexSnapshotSubDir,
rollback_id, pre_restore ? kPreRestoreSuffix : "");
const Result<void> result = CreateDirIfNeeded(rollback_path, 0700);
if (!result.ok()) {
return Error() << "Failed to create snapshot directory for rollback "
<< rollback_id << " : " << result.error();
}
auto from_path = StringPrintf("%s/%s/%s", base_dir.c_str(), kApexDataSubDir,
apex_name.c_str());
auto to_path =
StringPrintf("%s/%s", rollback_path.c_str(), apex_name.c_str());
return ReplaceFiles(from_path, to_path);
}
/**
* Restores snapshot from base_dir/apexrollback/<rollback id>/<apex name>
* to base_dir/apexdata/<apex name>.
* Note the snapshot will be deleted after restoration succeeded.
*/
Result<void> RestoreDataDirectory(const std::string& base_dir,
const int rollback_id,
const std::string& apex_name,
bool pre_restore = false) {
auto from_path = StringPrintf(
"%s/%s/%d%s/%s", base_dir.c_str(), kApexSnapshotSubDir, rollback_id,
pre_restore ? kPreRestoreSuffix : "", apex_name.c_str());
auto to_path = StringPrintf("%s/%s/%s", base_dir.c_str(), kApexDataSubDir,
apex_name.c_str());
Result<void> result = ReplaceFiles(from_path, to_path);
if (!result.ok()) {
return result;
}
result = RestoreconPath(to_path);
if (!result.ok()) {
return result;
}
result = DeleteDir(from_path);
if (!result.ok()) {
LOG(ERROR) << "Failed to delete the snapshot: " << result.error();
}
return {};
}
void SnapshotOrRestoreDeIfNeeded(const std::string& base_dir,
const ApexSession& session) {
if (session.HasRollbackEnabled()) {
for (const auto& apex_name : session.GetApexNames()) {
Result<void> result =
SnapshotDataDirectory(base_dir, session.GetRollbackId(), apex_name);
if (!result.ok()) {
LOG(ERROR) << "Snapshot failed for " << apex_name << ": "
<< result.error();
}
}
} else if (session.IsRollback()) {
for (const auto& apex_name : session.GetApexNames()) {
if (!gSupportsFsCheckpoints) {
// Snapshot before restore so this rollback can be reverted.
SnapshotDataDirectory(base_dir, session.GetRollbackId(), apex_name,
true /* pre_restore */);
}
Result<void> result =
RestoreDataDirectory(base_dir, session.GetRollbackId(), apex_name);
if (!result.ok()) {
LOG(ERROR) << "Restore of data failed for " << apex_name << ": "
<< result.error();
}
}
}
}
void SnapshotOrRestoreDeSysData() {
auto sessions = gSessionManager->GetSessionsInState(SessionState::ACTIVATED);
for (const ApexSession& session : sessions) {
SnapshotOrRestoreDeIfNeeded(kDeSysDataDir, session);
}
}
int SnapshotOrRestoreDeUserData() {
auto user_dirs = GetDeUserDirs();
if (!user_dirs.ok()) {
LOG(ERROR) << "Error reading dirs " << user_dirs.error();
return 1;
}
auto sessions = gSessionManager->GetSessionsInState(SessionState::ACTIVATED);
for (const ApexSession& session : sessions) {
for (const auto& user_dir : *user_dirs) {
SnapshotOrRestoreDeIfNeeded(user_dir, session);
}
}
return 0;
}
Result<void> SnapshotCeData(const int user_id, const int rollback_id,
const std::string& apex_name) {
auto base_dir = StringPrintf("%s/%d", kCeDataDir, user_id);
return SnapshotDataDirectory(base_dir, rollback_id, apex_name);
}
Result<void> RestoreCeData(const int user_id, const int rollback_id,
const std::string& apex_name) {
auto base_dir = StringPrintf("%s/%d", kCeDataDir, user_id);
return RestoreDataDirectory(base_dir, rollback_id, apex_name);
}
Result<void> DestroySnapshots(const std::string& base_dir,
const int rollback_id) {
auto path = StringPrintf("%s/%s/%d", base_dir.c_str(), kApexSnapshotSubDir,
rollback_id);
return DeleteDir(path);
}
Result<void> DestroyDeSnapshots(const int rollback_id) {
DestroySnapshots(kDeSysDataDir, rollback_id);
auto user_dirs = GetDeUserDirs();
if (!user_dirs.ok()) {
return Error() << "Error reading user dirs " << user_dirs.error();
}
for (const auto& user_dir : *user_dirs) {
DestroySnapshots(user_dir, rollback_id);
}
return {};
}
Result<void> DestroyCeSnapshots(const int user_id, const int rollback_id) {
auto path = StringPrintf("%s/%d/%s/%d", kCeDataDir, user_id,
kApexSnapshotSubDir, rollback_id);
return DeleteDir(path);
}
/**
* Deletes all credential-encrypted snapshots for the given user, except for
* those listed in retain_rollback_ids.
*/
Result<void> DestroyCeSnapshotsNotSpecified(
int user_id, const std::vector<int>& retain_rollback_ids) {
auto snapshot_root =
StringPrintf("%s/%d/%s", kCeDataDir, user_id, kApexSnapshotSubDir);
auto snapshot_dirs = GetSubdirs(snapshot_root);
if (!snapshot_dirs.ok()) {
return Error() << "Error reading snapshot dirs " << snapshot_dirs.error();
}
for (const auto& snapshot_dir : *snapshot_dirs) {
uint snapshot_id;
bool parse_ok = ParseUint(
std::filesystem::path(snapshot_dir).filename().c_str(), &snapshot_id);
if (parse_ok &&
std::find(retain_rollback_ids.begin(), retain_rollback_ids.end(),
snapshot_id) == retain_rollback_ids.end()) {
Result<void> result = DeleteDir(snapshot_dir);
if (!result.ok()) {
return Error() << "Destroy CE snapshot failed for " << snapshot_dir
<< " : " << result.error();
}
}
}
return {};
}
void RestorePreRestoreSnapshotsIfPresent(const std::string& base_dir,
const ApexSession& session) {
auto pre_restore_snapshot_path =
StringPrintf("%s/%s/%d%s", base_dir.c_str(), kApexSnapshotSubDir,
session.GetRollbackId(), kPreRestoreSuffix);
if (PathExists(pre_restore_snapshot_path).ok()) {
for (const auto& apex_name : session.GetApexNames()) {
Result<void> result = RestoreDataDirectory(
base_dir, session.GetRollbackId(), apex_name, true /* pre_restore */);
if (!result.ok()) {
LOG(ERROR) << "Restore of pre-restore snapshot failed for " << apex_name
<< ": " << result.error();
}
}
}
}
void RestoreDePreRestoreSnapshotsIfPresent(const ApexSession& session) {
RestorePreRestoreSnapshotsIfPresent(kDeSysDataDir, session);
auto user_dirs = GetDeUserDirs();
if (!user_dirs.ok()) {
LOG(ERROR) << "Error reading user dirs to restore pre-restore snapshots"
<< user_dirs.error();
}
for (const auto& user_dir : *user_dirs) {
RestorePreRestoreSnapshotsIfPresent(user_dir, session);
}
}
void DeleteDePreRestoreSnapshots(const std::string& base_dir,
const ApexSession& session) {
auto pre_restore_snapshot_path =
StringPrintf("%s/%s/%d%s", base_dir.c_str(), kApexSnapshotSubDir,
session.GetRollbackId(), kPreRestoreSuffix);
Result<void> result = DeleteDir(pre_restore_snapshot_path);
if (!result.ok()) {
LOG(ERROR) << "Deletion of pre-restore snapshot failed: " << result.error();
}
}
void DeleteDePreRestoreSnapshots(const ApexSession& session) {
DeleteDePreRestoreSnapshots(kDeSysDataDir, session);
auto user_dirs = GetDeUserDirs();
if (!user_dirs.ok()) {
LOG(ERROR) << "Error reading user dirs to delete pre-restore snapshots"
<< user_dirs.error();
}
for (const auto& user_dir : *user_dirs) {
DeleteDePreRestoreSnapshots(user_dir, session);
}
}
void OnBootCompleted() {
ApexdLifecycle::GetInstance().MarkBootCompleted();
}
// Returns true if any session gets staged
void ScanStagedSessionsDirAndStage() {
LOG(INFO) << "Scanning " << GetSessionsDir()
<< " looking for sessions to be activated.";
auto sessions_to_activate =
gSessionManager->GetSessionsInState(SessionState::STAGED);
if (gSupportsFsCheckpoints) {
// A session that is in the ACTIVATED state should still be re-activated if
// fs checkpointing is supported. In this case, a session may be in the
// ACTIVATED state yet the data/apex/active directory may have been
// reverted. The session should be reverted in this scenario.
auto activated_sessions =
gSessionManager->GetSessionsInState(SessionState::ACTIVATED);
sessions_to_activate.insert(sessions_to_activate.end(),
activated_sessions.begin(),
activated_sessions.end());
}
for (auto& session : sessions_to_activate) {
auto session_id = session.GetId();
auto session_failed_fn = [&]() {
LOG(WARNING) << "Marking session " << session_id << " as failed.";
auto st = session.UpdateStateAndCommit(SessionState::ACTIVATION_FAILED);
if (!st.ok()) {
LOG(WARNING) << "Failed to mark session " << session_id
<< " as failed : " << st.error();
}
};
auto scope_guard = android::base::make_scope_guard(session_failed_fn);
std::string build_fingerprint = GetProperty(kBuildFingerprintSysprop, "");
if (session.GetBuildFingerprint().compare(build_fingerprint) != 0) {
auto error_message = "APEX build fingerprint has changed";
LOG(ERROR) << error_message;
session.SetErrorMessage(error_message);
continue;
}
// If device supports fs-checkpoint, then apex session should only be
// installed when in checkpoint-mode. Otherwise, we will not be able to
// revert /data on error.
if (gSupportsFsCheckpoints && !gInFsCheckpointMode) {
auto error_message =
"Cannot install apex session if not in fs-checkpoint mode";
LOG(ERROR) << error_message;
session.SetErrorMessage(error_message);
continue;
}
std::vector<std::string> dirs_to_scan =
session.GetStagedApexDirs(gConfig->staged_session_dir);
std::vector<std::string> apexes;
bool scan_successful = true;
for (const auto& dir_to_scan : dirs_to_scan) {
Result<std::vector<std::string>> scan =
FindFilesBySuffix(dir_to_scan, {kApexPackageSuffix});
if (!scan.ok()) {
LOG(WARNING) << scan.error();
session.SetErrorMessage(scan.error().message());
scan_successful = false;
break;
}
if (scan->size() > 1) {
std::string error_message = StringPrintf(
"More than one APEX package found in the same session directory %s "
", skipping activation",
dir_to_scan.c_str());
LOG(WARNING) << error_message;
session.SetErrorMessage(error_message);
scan_successful = false;
break;
}
if (scan->empty()) {
std::string error_message = StringPrintf(
"No APEX packages found while scanning %s session id: %d.",
dir_to_scan.c_str(), session_id);
LOG(WARNING) << error_message;
session.SetErrorMessage(error_message);
scan_successful = false;
break;
}
apexes.push_back(std::move((*scan)[0]));
}
if (!scan_successful) {
continue;
}
std::vector<std::string> staged_apex_names;
for (const auto& apex : apexes) {
// TODO(b/158470836): Avoid opening ApexFile repeatedly.
Result<ApexFile> apex_file = ApexFile::Open(apex);
if (!apex_file.ok()) {
LOG(ERROR) << "Cannot open apex file during staging: " << apex;
continue;
}
staged_apex_names.push_back(apex_file->GetManifest().name());
}
const Result<void> result = StagePackages(apexes);
if (!result.ok()) {
std::string error_message = StringPrintf(
"Activation failed for packages %s : %s", Join(apexes, ',').c_str(),
result.error().message().c_str());
LOG(ERROR) << error_message;
session.SetErrorMessage(error_message);
continue;
}
// Session was OK, release scopeguard.
scope_guard.Disable();
for (const std::string& apex : staged_apex_names) {
gChangedActiveApexes.insert(apex);
}
auto st = session.UpdateStateAndCommit(SessionState::ACTIVATED);
if (!st.ok()) {
LOG(ERROR) << "Failed to mark " << session
<< " as activated : " << st.error();
}
}
}
namespace {
std::string StageDestPath(const ApexFile& apex_file) {
return StringPrintf("%s/%s%s", gConfig->active_apex_data_dir,
GetPackageId(apex_file.GetManifest()).c_str(),
kApexPackageSuffix);
}
} // namespace
Result<void> StagePackages(const std::vector<std::string>& tmp_paths) {
if (tmp_paths.empty()) {
return Errorf("Empty set of inputs");
}
LOG(DEBUG) << "StagePackages() for " << Join(tmp_paths, ',');
// Note: this function is temporary. As such the code is not optimized, e.g.,
// it will open ApexFiles multiple times.
// 1) Verify all packages.
Result<std::vector<ApexFile>> apex_files = OpenApexFiles(tmp_paths);
if (!apex_files.ok()) {
return apex_files.error();
}
for (const ApexFile& apex_file : *apex_files) {
if (shim::IsShimApex(apex_file)) {
// Shim apex will be validated on every boot. No need to do it here.
continue;
}
Result<void> result = VerifyPackageBoot(apex_file);
if (!result.ok()) {
return result.error();
}
}
// Make sure that kActiveApexPackagesDataDir exists.
auto create_dir_status =
CreateDirIfNeeded(std::string(gConfig->active_apex_data_dir), 0755);
if (!create_dir_status.ok()) {
return create_dir_status.error();
}
// 2) Now stage all of them.
// Ensure the APEX gets removed on failure.
std::unordered_set<std::string> staged_files;
std::vector<std::string> changed_hashtree_files;
auto deleter = [&staged_files, &changed_hashtree_files]() {
for (const std::string& staged_path : staged_files) {
if (TEMP_FAILURE_RETRY(unlink(staged_path.c_str())) != 0) {
PLOG(ERROR) << "Unable to unlink " << staged_path;
}
}
for (const std::string& hashtree_file : changed_hashtree_files) {
if (TEMP_FAILURE_RETRY(unlink(hashtree_file.c_str())) != 0) {
PLOG(ERROR) << "Unable to unlink " << hashtree_file;
}
}
};
auto scope_guard = android::base::make_scope_guard(deleter);
std::unordered_set<std::string> staged_packages;
for (const ApexFile& apex_file : *apex_files) {
// First promote new hashtree file to the one that will be used when
// mounting apex.
std::string new_hashtree_file = GetHashTreeFileName(apex_file,
/* is_new = */ true);
std::string old_hashtree_file = GetHashTreeFileName(apex_file,
/* is_new = */ false);
if (access(new_hashtree_file.c_str(), F_OK) == 0) {
if (TEMP_FAILURE_RETRY(rename(new_hashtree_file.c_str(),
old_hashtree_file.c_str())) != 0) {
return ErrnoError() << "Failed to move " << new_hashtree_file << " to "
<< old_hashtree_file;
}
changed_hashtree_files.emplace_back(std::move(old_hashtree_file));
}
// And only then move apex to /data/apex/active.
std::string dest_path = StageDestPath(apex_file);
if (access(dest_path.c_str(), F_OK) == 0) {
LOG(DEBUG) << dest_path << " already exists. Deleting";
if (TEMP_FAILURE_RETRY(unlink(dest_path.c_str())) != 0) {
return ErrnoError() << "Failed to unlink " << dest_path;
}
}
if (link(apex_file.GetPath().c_str(), dest_path.c_str()) != 0) {
return ErrnoError() << "Unable to link " << apex_file.GetPath() << " to "
<< dest_path;
}
staged_files.insert(dest_path);
staged_packages.insert(apex_file.GetManifest().name());
LOG(DEBUG) << "Success linking " << apex_file.GetPath() << " to "
<< dest_path;
}
scope_guard.Disable(); // Accept the state.
return RemovePreviouslyActiveApexFiles(staged_packages, staged_files);
}
Result<void> UnstagePackages(const std::vector<std::string>& paths) {
if (paths.empty()) {
return Errorf("Empty set of inputs");
}
LOG(DEBUG) << "UnstagePackages() for " << Join(paths, ',');
for (const std::string& path : paths) {
auto apex = ApexFile::Open(path);
if (!apex.ok()) {
return apex.error();
}
if (ApexFileRepository::GetInstance().IsPreInstalledApex(*apex)) {
return Error() << "Can't uninstall pre-installed apex " << path;
}
}
for (const std::string& path : paths) {
if (unlink(path.c_str()) != 0) {
return ErrnoError() << "Can't unlink " << path;
}
}
return {};
}
/**
* During apex installation, staged sessions located in /data/apex/sessions
* mutate the active sessions in /data/apex/active. If some error occurs during
* installation of apex, we need to revert /data/apex/active to its original
* state and reboot.
*
* Also, we need to put staged sessions in /data/apex/sessions in REVERTED state
* so that they do not get activated on next reboot.
*/
Result<void> RevertActiveSessions(const std::string& crashing_native_process,
const std::string& error_message) {
// First check whenever there is anything to revert. If there is none, then
// fail. This prevents apexd from boot looping a device in case a native
// process is crashing and there are no apex updates.
auto active_sessions = gSessionManager->GetSessions();
active_sessions.erase(
std::remove_if(active_sessions.begin(), active_sessions.end(),
[](const auto& s) {
return s.IsFinalized() ||
s.GetState() == SessionState::UNKNOWN;
}),
active_sessions.end());
if (active_sessions.empty()) {
return Error() << "Revert requested, when there are no active sessions.";
}
for (auto& session : active_sessions) {
if (!crashing_native_process.empty()) {
session.SetCrashingNativeProcess(crashing_native_process);
}
if (!error_message.empty()) {
session.SetErrorMessage(error_message);
}
auto status =
session.UpdateStateAndCommit(SessionState::REVERT_IN_PROGRESS);
if (!status.ok()) {
return Error() << "Revert of session " << session
<< " failed : " << status.error();
}
}
if (!gSupportsFsCheckpoints) {
auto restore_status = RestoreActivePackages();
if (!restore_status.ok()) {
for (auto& session : active_sessions) {
auto st = session.UpdateStateAndCommit(SessionState::REVERT_FAILED);
LOG(DEBUG) << "Marking " << session << " as failed to revert";
if (!st.ok()) {
LOG(WARNING) << "Failed to mark session " << session
<< " as failed to revert : " << st.error();
}
}
return restore_status;
}
} else {
LOG(INFO) << "Not restoring active packages in checkpoint mode.";
}
for (auto& session : active_sessions) {
if (!gSupportsFsCheckpoints && session.IsRollback()) {
// If snapshots have already been restored, undo that by restoring the
// pre-restore snapshot.
RestoreDePreRestoreSnapshotsIfPresent(session);
}
auto status = session.UpdateStateAndCommit(SessionState::REVERTED);
if (!status.ok()) {
LOG(WARNING) << "Failed to mark session " << session
<< " as reverted : " << status.error();
}
}
return {};
}
Result<void> RevertActiveSessionsAndReboot(
const std::string& crashing_native_process,
const std::string& error_message) {
auto status = RevertActiveSessions(crashing_native_process, error_message);
if (!status.ok()) {
return status;
}
LOG(ERROR) << "Successfully reverted. Time to reboot device.";
if (gInFsCheckpointMode) {
Result<void> res = gVoldService->AbortChanges(
"apexd_initiated" /* message */, false /* retry */);
if (!res.ok()) {
LOG(ERROR) << res.error();
}
}
Reboot();
return {};
}
Result<void> CreateSharedLibsApexDir() {
// Creates /apex/sharedlibs/lib{,64} for SharedLibs APEXes.
std::string shared_libs_sub_dir =
StringPrintf("%s/%s", kApexRoot, kApexSharedLibsSubDir);
auto dir_exists = PathExists(shared_libs_sub_dir);
if (!dir_exists.ok() || !*dir_exists) {
std::error_code error_code;
std::filesystem::create_directory(shared_libs_sub_dir, error_code);
if (error_code) {
return Error() << "Failed to create directory " << shared_libs_sub_dir
<< ": " << error_code.message();
}
}
for (const auto& lib_path : {"lib", "lib64"}) {
std::string apex_lib_path =
StringPrintf("%s/%s", shared_libs_sub_dir.c_str(), lib_path);
auto lib_dir_exists = PathExists(apex_lib_path);
if (!lib_dir_exists.ok() || !*lib_dir_exists) {
std::error_code error_code;
std::filesystem::create_directory(apex_lib_path, error_code);
if (error_code) {
return Error() << "Failed to create directory " << apex_lib_path << ": "
<< error_code.message();
}
}
}
return {};
}
int OnBootstrap() {
ATRACE_NAME("OnBootstrap");
auto time_started = boot_clock::now();
ApexFileRepository& instance = ApexFileRepository::GetInstance();
Result<void> status =
instance.AddPreInstalledApex(gConfig->apex_built_in_dirs);
if (!status.ok()) {
LOG(ERROR) << "Failed to collect APEX keys : " << status.error();
return 1;
}
const auto& pre_installed_apexes = instance.GetPreInstalledApexFiles();
int loop_device_cnt = pre_installed_apexes.size();
// Find all bootstrap apexes
std::vector<ApexFileRef> bootstrap_apexes;
for (const auto& apex : pre_installed_apexes) {
if (IsBootstrapApex(apex.get())) {
LOG(INFO) << "Found bootstrap APEX " << apex.get().GetPath();
bootstrap_apexes.push_back(apex);
loop_device_cnt++;
}
if (apex.get().GetManifest().providesharedapexlibs()) {
LOG(INFO) << "Found sharedlibs APEX " << apex.get().GetPath();
// Sharedlis APEX might be mounted 2 times:
// * Pre-installed sharedlibs APEX will be mounted in OnStart
// * Updated sharedlibs APEX (if it exists) will be mounted in OnStart
//
// We already counted a loop device for one of these 2 mounts, need to add
// 1 more.
loop_device_cnt++;
}
}
LOG(INFO) << "Need to pre-allocate " << loop_device_cnt
<< " loop devices for " << pre_installed_apexes.size()
<< " APEX packages";
// TODO(b/209491448) Remove this.
auto block_count = AddBlockApex(instance);
if (!block_count.ok()) {
LOG(ERROR) << status.error();
return 1;
}
if (*block_count > 0) {
LOG(INFO) << "Also need to pre-allocate " << *block_count
<< " loop devices for block APEXes";
loop_device_cnt += *block_count;
}
if (auto res = loop::PreAllocateLoopDevices(loop_device_cnt); !res.ok()) {
LOG(ERROR) << "Failed to pre-allocate loop devices : " << res.error();
}
DeviceMapper& dm = DeviceMapper::Instance();
// Create empty dm device for each found APEX.
// This is a boot time optimization that makes use of the fact that user space
// paths will be created by ueventd before apexd is started, and hence
// reducing the time to activate APEXEs on /data.
// Note: since at this point we don't know which APEXes are updated, we are
// optimistically creating a verity device for all of them. Once boot
// finishes, apexd will clean up unused devices.
// TODO(b/192241176): move to apexd_verity.{h,cpp}
for (const auto& apex : pre_installed_apexes) {
const std::string& name = apex.get().GetManifest().name();
if (!dm.CreatePlaceholderDevice(name)) {
LOG(ERROR) << "Failed to create empty device " << name;
}
}
// Create directories for APEX shared libraries.
auto sharedlibs_apex_dir = CreateSharedLibsApexDir();
if (!sharedlibs_apex_dir.ok()) {
LOG(ERROR) << sharedlibs_apex_dir.error();
return 1;
}
// Now activate bootstrap apexes.
auto ret =
ActivateApexPackages(bootstrap_apexes, ActivationMode::kBootstrapMode);
if (!ret.ok()) {
LOG(ERROR) << "Failed to activate bootstrap apex files : " << ret.error();
return 1;
}
OnAllPackagesActivated(/*is_bootstrap=*/true);
auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
boot_clock::now() - time_started).count();
LOG(INFO) << "OnBootstrap done, duration=" << time_elapsed;
return 0;
}
Result<void> RemountApexFile(const std::string& path) {
if (auto ret = DeactivatePackage(path); !ret.ok()) {
return ret;
}
return ActivatePackage(path);
}
void InitializeVold(CheckpointInterface* checkpoint_service) {
if (checkpoint_service != nullptr) {
gVoldService = checkpoint_service;
Result<bool> supports_fs_checkpoints =
gVoldService->SupportsFsCheckpoints();
if (supports_fs_checkpoints.ok()) {
gSupportsFsCheckpoints = *supports_fs_checkpoints;
} else {
LOG(ERROR) << "Failed to check if filesystem checkpoints are supported: "
<< supports_fs_checkpoints.error();
}
if (gSupportsFsCheckpoints) {
Result<bool> needs_checkpoint = gVoldService->NeedsCheckpoint();
if (needs_checkpoint.ok()) {
gInFsCheckpointMode = *needs_checkpoint;
} else {
LOG(ERROR) << "Failed to check if we're in filesystem checkpoint mode: "
<< needs_checkpoint.error();
}
}
}
}
void InitializeSessionManager(ApexSessionManager* session_manager) {
gSessionManager = session_manager;
}
void Initialize(CheckpointInterface* checkpoint_service) {
InitializeVold(checkpoint_service);
ApexFileRepository& instance = ApexFileRepository::GetInstance();
Result<void> status = instance.AddPreInstalledApex(kApexPackageBuiltinDirs);
if (!status.ok()) {
LOG(ERROR) << "Failed to collect pre-installed APEX files : "
<< status.error();
return;
}
// TODO(b/209491448) Remove this.
if (auto block_status = AddBlockApex(instance); !block_status.ok()) {
LOG(ERROR) << status.error();
return;
}
gMountedApexes.PopulateFromMounts(
{gConfig->active_apex_data_dir, gConfig->decompression_dir},
gConfig->apex_hash_tree_dir);
}
// Note: Pre-installed apex are initialized in Initialize(CheckpointInterface*)
// TODO(b/172911822): Consolidate this with Initialize() when
// ApexFileRepository can act as cache and re-scanning is not expensive
void InitializeDataApex() {
ApexFileRepository& instance = ApexFileRepository::GetInstance();
Result<void> status = instance.AddDataApex(kActiveApexPackagesDataDir);
if (!status.ok()) {
LOG(ERROR) << "Failed to collect data APEX files : " << status.error();
return;
}
}
/**
* For every package X, there can be at most two APEX, pre-installed vs
* installed on data. We usually select only one of these APEX for each package
* based on the following conditions:
* - Package X must be pre-installed on one of the built-in directories.
* - If there are multiple APEX, we select the one with highest version.
* - If there are multiple with same version, we give priority to APEX on
* /data partition.
*
* Typically, only one APEX is activated for each package, but APEX that provide
* shared libs are exceptions. We have to activate both APEX for them.
*
* @param all_apex all the APEX grouped by their package name
* @return list of ApexFile that needs to be activated
*/
std::vector<ApexFileRef> SelectApexForActivation(
const std::unordered_map<std::string, std::vector<ApexFileRef>>& all_apex,
const ApexFileRepository& instance) {
LOG(INFO) << "Selecting APEX for activation";
std::vector<ApexFileRef> activation_list;
// For every package X, select which APEX to activate
for (auto& apex_it : all_apex) {
const std::string& package_name = apex_it.first;
const std::vector<ApexFileRef>& apex_files = apex_it.second;
if (apex_files.size() > 2 || apex_files.size() == 0) {
LOG(FATAL) << "Unexpectedly found more than two versions or none for "
"APEX package "
<< package_name;
continue;
}
// The package must have a pre-installed version before we consider it for
// activation
if (!instance.HasPreInstalledVersion(package_name)) {
LOG(INFO) << "Package " << package_name << " is not pre-installed";
continue;
}
if (apex_files.size() == 1) {
LOG(DEBUG) << "Selecting the only APEX: " << package_name << " "
<< apex_files[0].get().GetPath();
activation_list.emplace_back(apex_files[0]);
continue;
}
// TODO(b/179497746): Now that we are dealing with list of reference, this
// selection process can be simplified by sorting the vector.
// Given an APEX A and the version of the other APEX B, should we activate
// it?
auto select_apex = [&instance, &activation_list](