| /* |
| * 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 LOG_TAG "apexd" |
| |
| #include "apexd.h" |
| #include "apexd_private.h" |
| |
| #include "apex_database.h" |
| #include "apex_file.h" |
| #include "apex_key.h" |
| #include "apex_manifest.h" |
| #include "apex_shim.h" |
| #include "apexd_checkpoint.h" |
| #include "apexd_loop.h" |
| #include "apexd_prepostinstall.h" |
| #include "apexd_prop.h" |
| #include "apexd_session.h" |
| #include "apexd_utils.h" |
| #include "status_or.h" |
| #include "string_log.h" |
| |
| #include <ApexProperties.sysprop.h> |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/macros.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 <libavb/libavb.h> |
| #include <libdm/dm.h> |
| #include <libdm/dm_table.h> |
| #include <libdm/dm_target.h> |
| #include <selinux/android.h> |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <linux/loop.h> |
| #include <sys/ioctl.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <filesystem> |
| #include <fstream> |
| #include <iomanip> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| using android::base::EndsWith; |
| using android::base::Join; |
| using android::base::ReadFullyAtOffset; |
| 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::SessionState; |
| |
| namespace android { |
| namespace apex { |
| |
| using MountedApexData = MountedApexDatabase::MountedApexData; |
| |
| namespace { |
| |
| // These should be in-sync with system/sepolicy/public/property_contexts |
| static constexpr const char* kApexStatusSysprop = "apexd.status"; |
| static constexpr const char* kApexStatusStarting = "starting"; |
| static constexpr const char* kApexStatusReady = "ready"; |
| |
| static constexpr const char* kApexVerityOnSystemProp = |
| "persist.apexd.verity_on_system"; |
| static bool gForceDmVerityOnSystem = |
| android::base::GetBoolProperty(kApexVerityOnSystemProp, false); |
| |
| // This should be in UAPI, but it's not :-( |
| static constexpr const char* kDmVerityRestartOnCorruption = |
| "restart_on_corruption"; |
| |
| MountedApexDatabase gMountedApexes; |
| |
| CheckpointInterface* gVoldService; |
| bool gSupportsFsCheckpoints = false; |
| bool gInFsCheckpointMode = false; |
| |
| static constexpr size_t kLoopDeviceSetupAttempts = 3u; |
| |
| static const bool kUpdatable = |
| android::sysprop::ApexProperties::updatable().value_or(false); |
| |
| bool gBootstrap = false; |
| static const std::vector<const std::string> kBootstrapApexes = { |
| "com.android.runtime", |
| "com.android.tzdata", |
| }; |
| |
| static constexpr const int kNumRetriesWhenCheckpointingEnabled = 1; |
| |
| bool isBootstrapApex(const ApexFile& apex) { |
| return std::find(kBootstrapApexes.begin(), kBootstrapApexes.end(), |
| apex.GetManifest().name()) != kBootstrapApexes.end(); |
| } |
| |
| // Pre-allocate loop devices so that we don't have to wait for them |
| // later when actually activating APEXes. |
| Status preAllocateLoopDevices() { |
| auto scan = FindApexes(kApexPackageBuiltinDirs); |
| if (!scan.Ok()) { |
| return scan.ErrorStatus(); |
| } |
| |
| auto size = 0; |
| for (const auto& path : *scan) { |
| auto apexFile = ApexFile::Open(path); |
| if (!apexFile.Ok() || apexFile->IsFlattened()) { |
| continue; |
| } |
| size++; |
| // bootstrap Apexes may be activated on separate namespaces. |
| if (isBootstrapApex(*apexFile)) { |
| size++; |
| } |
| } |
| |
| // note: do not call preAllocateLoopDevices() if size == 0 |
| // or the device does not support updatable APEX. |
| // For devices (e.g. ARC) which doesn't support loop-control |
| // preAllocateLoopDevices() can cause problem when it tries |
| // to access /dev/loop-control. |
| if (size == 0 || !kUpdatable) { |
| return Status::Success(); |
| } |
| return loop::preAllocateLoopDevices(size); |
| } |
| |
| std::unique_ptr<DmTable> createVerityTable(const ApexVerityData& verity_data, |
| const std::string& loop, |
| bool restart_on_corruption) { |
| AvbHashtreeDescriptor* desc = verity_data.desc.get(); |
| auto table = std::make_unique<DmTable>(); |
| |
| std::ostringstream hash_algorithm; |
| hash_algorithm << desc->hash_algorithm; |
| |
| auto target = std::make_unique<DmTargetVerity>( |
| 0, desc->image_size / 512, desc->dm_verity_version, loop, loop, |
| desc->data_block_size, desc->hash_block_size, |
| desc->image_size / desc->data_block_size, |
| desc->tree_offset / desc->hash_block_size, hash_algorithm.str(), |
| 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; |
| } |
| |
| enum WaitForDeviceMode { |
| kWaitToBeCreated = 0, |
| kWaitToBeDeleted, |
| }; |
| |
| Status waitForDevice(const std::string& device, const WaitForDeviceMode& mode) { |
| // TODO(b/122059364): Make this more efficient |
| // TODO: use std::chrono? |
| |
| // Deleting a device might take more time, so wait a little bit longer. |
| size_t num_tries = mode == kWaitToBeCreated ? 10u : 15u; |
| |
| LOG(DEBUG) << "Waiting for " << device << " to be " |
| << (mode == kWaitToBeCreated ? "created" : " deleted"); |
| for (size_t i = 0; i < num_tries; ++i) { |
| StatusOr<bool> status = PathExists(device); |
| if (status.Ok()) { |
| if (mode == kWaitToBeCreated && *status) { |
| return Status::Success(); |
| } |
| if (mode == kWaitToBeDeleted && !*status) { |
| return Status::Success(); |
| } |
| } |
| if (i + 1 < num_tries) { |
| usleep(50000); |
| } |
| } |
| |
| return Status::Fail(StringLog() |
| << "Failed to wait for device " << device << " to be " |
| << (mode == kWaitToBeCreated ? " created" : " deleted")); |
| } |
| |
| // Deletes a dm-verity device with a given name and path. |
| // Synchronizes on the device actually being deleted from userspace. |
| Status DeleteVerityDevice(const std::string& name, const std::string& path) { |
| DeviceMapper& dm = DeviceMapper::Instance(); |
| if (!dm.DeleteDevice(name)) { |
| return Status::Fail(StringLog() << "Failed to delete device " << name |
| << " with path " << path); |
| } |
| // Block until device is deleted from userspace. |
| return waitForDevice(path, kWaitToBeDeleted); |
| } |
| |
| // Deletes dm-verity device with a given name. |
| // See function above. |
| Status DeleteVerityDevice(const std::string& name) { |
| DeviceMapper& dm = DeviceMapper::Instance(); |
| std::string path; |
| if (!dm.GetDmDevicePathByName(name, &path)) { |
| return Status::Fail(StringLog() |
| << "Unable to get path for dm-verity device " << name); |
| } |
| return DeleteVerityDevice(name, path); |
| } |
| |
| class DmVerityDevice { |
| public: |
| DmVerityDevice() : cleared_(true) {} |
| explicit DmVerityDevice(const std::string& name) |
| : name_(name), cleared_(false) {} |
| DmVerityDevice(const std::string& name, const std::string& dev_path) |
| : name_(name), dev_path_(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_) { |
| Status ret = DeleteVerityDevice(name_, dev_path_); |
| if (!ret.Ok()) { |
| LOG(ERROR) << ret.ErrorMessage(); |
| } |
| } |
| } |
| |
| const std::string& GetName() const { return name_; } |
| const std::string& GetDevPath() const { return dev_path_; } |
| void SetDevPath(const std::string& dev_path) { dev_path_ = dev_path; } |
| |
| void Release() { cleared_ = true; } |
| |
| private: |
| std::string name_; |
| std::string dev_path_; |
| bool cleared_; |
| }; |
| |
| StatusOr<DmVerityDevice> createVerityDevice(const std::string& name, |
| const DmTable& table) { |
| DeviceMapper& dm = DeviceMapper::Instance(); |
| |
| if (dm.GetState(name) != DmDeviceState::INVALID) { |
| // TODO: since apexd tears down devices during unmount, can this happen? |
| LOG(WARNING) << "Deleting existing dm device " << name; |
| const Status& status = DeleteVerityDevice(name); |
| if (!status.Ok()) { |
| // TODO: should we fail instead? |
| LOG(ERROR) << "Failed to delete device " << name << " : " |
| << status.ErrorMessage(); |
| } |
| } |
| |
| if (!dm.CreateDevice(name, table)) { |
| return StatusOr<DmVerityDevice>::MakeError( |
| "Couldn't create verity device."); |
| } |
| DmVerityDevice dev(name); |
| |
| std::string dev_path; |
| if (!dm.GetDmDevicePathByName(name, &dev_path)) { |
| return StatusOr<DmVerityDevice>::MakeError( |
| "Couldn't get verity device path!"); |
| } |
| dev.SetDevPath(dev_path); |
| |
| return StatusOr<DmVerityDevice>(std::move(dev)); |
| } |
| |
| Status RemovePreviouslyActiveApexFiles( |
| const std::unordered_set<std::string>& affected_packages, |
| const std::unordered_set<std::string>& files_to_keep) { |
| auto all_active_apex_files = |
| FindApexFilesByName(kActiveApexPackagesDataDir, false /* include_dirs */); |
| |
| if (!all_active_apex_files.Ok()) { |
| return all_active_apex_files.ErrorStatus(); |
| } |
| |
| for (const std::string& path : *all_active_apex_files) { |
| StatusOr<ApexFile> apex_file = ApexFile::Open(path); |
| if (!apex_file.Ok()) { |
| return apex_file.ErrorStatus(); |
| } |
| |
| 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 Status::Fail(PStringLog() |
| << "Failed to unlink " << apex_file->GetPath()); |
| } |
| } |
| |
| return Status::Success(); |
| } |
| |
| // Reads the entire device to verify the image is authenticatic |
| Status 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))); |
| if (fd.get() == -1) { |
| return Status::Fail(PStringLog() << "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 Status::Fail(PStringLog() << "Can't verify " << verity_device |
| << "; corrupted?"); |
| } |
| bytes_left -= to_read; |
| } |
| |
| return Status::Success(); |
| } |
| |
| Status VerifyMountedImage(const ApexFile& apex, |
| const std::string& mount_point) { |
| auto status = apex.VerifyManifestMatches(mount_point); |
| if (!status.Ok()) { |
| return status; |
| } |
| if (shim::IsShimApex(apex)) { |
| return shim::ValidateShimApex(mount_point, apex); |
| } |
| return Status::Success(); |
| } |
| |
| StatusOr<MountedApexData> mountNonFlattened(const ApexFile& apex, |
| const std::string& mountPoint, |
| const std::string& device_name, |
| bool verifyImage) { |
| using StatusM = StatusOr<MountedApexData>; |
| const std::string& full_path = apex.GetPath(); |
| |
| if (!kUpdatable) { |
| return StatusM::Fail(StringLog() |
| << "Unable to mount non-flattened apex package " |
| << full_path << " because device doesn't support it"); |
| } |
| |
| loop::LoopbackDeviceUniqueFd loopbackDevice; |
| for (size_t attempts = 1;; ++attempts) { |
| StatusOr<loop::LoopbackDeviceUniqueFd> ret = loop::createLoopDevice( |
| full_path, apex.GetImageOffset(), apex.GetImageSize()); |
| if (ret.Ok()) { |
| loopbackDevice = std::move(*ret); |
| break; |
| } |
| if (attempts >= kLoopDeviceSetupAttempts) { |
| return StatusM::Fail(StringLog() |
| << "Could not create loop device for " << full_path |
| << ": " << ret.ErrorMessage()); |
| } |
| } |
| LOG(VERBOSE) << "Loopback device created: " << loopbackDevice.name; |
| |
| auto verityData = apex.VerifyApexVerity(); |
| if (!verityData.Ok()) { |
| return StatusM::Fail(StringLog() |
| << "Failed to verify Apex Verity data for " |
| << full_path << ": " << verityData.ErrorMessage()); |
| } |
| std::string blockDevice = loopbackDevice.name; |
| MountedApexData apex_data(loopbackDevice.name, apex.GetPath(), mountPoint, |
| device_name); |
| |
| // 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 mountOnVerity = |
| gForceDmVerityOnSystem || !isPathForBuiltinApexes(full_path); |
| DmVerityDevice verityDev; |
| if (mountOnVerity) { |
| auto verityTable = |
| createVerityTable(*verityData, loopbackDevice.name, |
| /* restart_on_corruption = */ !verifyImage); |
| StatusOr<DmVerityDevice> verityDevRes = |
| createVerityDevice(device_name, *verityTable); |
| if (!verityDevRes.Ok()) { |
| return StatusM::Fail(StringLog() |
| << "Failed to create Apex Verity device " |
| << full_path << ": " << verityDevRes.ErrorMessage()); |
| } |
| verityDev = std::move(*verityDevRes); |
| blockDevice = verityDev.GetDevPath(); |
| |
| Status readAheadStatus = loop::configureReadAhead(verityDev.GetDevPath()); |
| if (!readAheadStatus.Ok()) { |
| return StatusM::MakeError(readAheadStatus); |
| } |
| } |
| |
| // TODO(b/122059364): Even though the kernel has created the verity |
| // device, we still depend on ueventd to run to actually create the |
| // device node in userspace. To solve this properly we should listen on |
| // the netlink socket for uevents, or use inotify. For now, this will |
| // have to do. |
| Status deviceStatus = waitForDevice(blockDevice, kWaitToBeCreated); |
| if (!deviceStatus.Ok()) { |
| return StatusM::MakeError(deviceStatus); |
| } |
| |
| // TODO: consider moving this inside RunVerifyFnInsideTempMount. |
| if (mountOnVerity && verifyImage) { |
| Status verityStatus = |
| readVerityDevice(blockDevice, (*verityData).desc->image_size); |
| if (!verityStatus.Ok()) { |
| return StatusM::MakeError(verityStatus); |
| } |
| } |
| |
| if (mount(blockDevice.c_str(), mountPoint.c_str(), "ext4", |
| MS_NOATIME | MS_NODEV | MS_DIRSYNC | MS_RDONLY, nullptr) == 0) { |
| LOG(INFO) << "Successfully mounted package " << full_path << " on " |
| << mountPoint; |
| auto status = VerifyMountedImage(apex, mountPoint); |
| if (!status.Ok()) { |
| umount2(mountPoint.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH); |
| return StatusM::Fail(StringLog() << "Failed to verify " << full_path |
| << ": " << status.ErrorMessage()); |
| } |
| // Time to accept the temporaries as good. |
| if (mountOnVerity) { |
| verityDev.Release(); |
| } |
| loopbackDevice.CloseGood(); |
| |
| return StatusM(std::move(apex_data)); |
| } else { |
| return StatusM::Fail(StringLog() << "Mounting failed for package " |
| << full_path << " : " << strerror(errno)); |
| } |
| } |
| |
| StatusOr<MountedApexData> mountFlattened(const ApexFile& apex, |
| const std::string& mountPoint) { |
| using StatusM = StatusOr<MountedApexData>; |
| if (!isPathForBuiltinApexes(apex.GetPath())) { |
| return StatusM::Fail(StringLog() << "Cannot activate flattened APEX " |
| << apex.GetPath()); |
| } |
| |
| if (mount(apex.GetPath().c_str(), mountPoint.c_str(), nullptr, MS_BIND, |
| nullptr) == 0) { |
| LOG(INFO) << "Successfully bind-mounted flattened package " |
| << apex.GetPath() << " on " << mountPoint; |
| |
| MountedApexData apex_data("" /* loop_name */, apex.GetPath(), mountPoint, |
| "" /* device_name */); |
| return StatusM(std::move(apex_data)); |
| } |
| return StatusM::Fail(PStringLog() << "Mounting failed for flattened package " |
| << apex.GetPath()); |
| } |
| |
| StatusOr<MountedApexData> MountPackageImpl(const ApexFile& apex, |
| const std::string& mountPoint, |
| const std::string& device_name, |
| bool verifyImage) { |
| using StatusM = StatusOr<MountedApexData>; |
| LOG(VERBOSE) << "Creating mount point: " << mountPoint; |
| // 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(mountPoint); |
| if (!exists.Ok()) { |
| return StatusM::MakeError(exists.ErrorStatus()); |
| } |
| if (!*exists && mkdir(mountPoint.c_str(), kMkdirMode) != 0) { |
| return StatusM::Fail(PStringLog() |
| << "Could not create mount point " << mountPoint); |
| } |
| auto deleter = [&mountPoint]() { |
| if (rmdir(mountPoint.c_str()) != 0) { |
| PLOG(WARNING) << "Could not rmdir " << mountPoint; |
| } |
| }; |
| auto scope_guard = android::base::make_scope_guard(deleter); |
| if (!IsEmptyDirectory(mountPoint)) { |
| return StatusM::Fail(PStringLog() << mountPoint << " is not empty"); |
| } |
| |
| StatusOr<MountedApexData> ret; |
| if (apex.IsFlattened()) { |
| ret = mountFlattened(apex, mountPoint); |
| } else { |
| ret = mountNonFlattened(apex, mountPoint, device_name, verifyImage); |
| } |
| if (ret.Ok()) { |
| scope_guard.Disable(); // Accept the mount. |
| } |
| return ret; |
| } |
| |
| StatusOr<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"; |
| return MountPackageImpl(apex, mount_point, temp_device_name, |
| /* verifyImage = */ true); |
| } |
| |
| Status Unmount(const MountedApexData& data) { |
| // Lazily try to umount whatever is mounted. |
| if (umount2(data.mount_point.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0 && |
| errno != EINVAL && errno != ENOENT) { |
| return Status::Fail(PStringLog() |
| << "Failed to unmount directory " << data.mount_point); |
| } |
| // Attempt to delete the folder. If the folder is retained, other |
| // data may be incorrect. |
| if (rmdir(data.mount_point.c_str()) != 0) { |
| PLOG(ERROR) << "Failed to rmdir directory " << data.mount_point; |
| } |
| |
| // Try to free up the device-mapper device. |
| if (!data.device_name.empty()) { |
| const auto& status = DeleteVerityDevice(data.device_name); |
| if (!status.Ok()) { |
| LOG(DEBUG) << "Failed to free device " << data.device_name << " : " |
| << status.ErrorMessage(); |
| } |
| } |
| |
| // Try to free up the loop device. |
| if (!data.loop_name.empty()) { |
| auto log_fn = [](const std::string& path, |
| const std::string& id ATTRIBUTE_UNUSED) { |
| LOG(VERBOSE) << "Freeing loop device " << path << "for unmount."; |
| }; |
| loop::DestroyLoopDevice(data.loop_name, log_fn); |
| } |
| |
| return Status::Success(); |
| } |
| |
| std::string GetPackageTempMountPoint(const ApexManifest& manifest) { |
| return StringPrintf("%s.tmp", |
| apexd_private::GetPackageMountPoint(manifest).c_str()); |
| } |
| |
| template <typename VerifyFn> |
| Status RunVerifyFnInsideTempMount(const ApexFile& apex, |
| const VerifyFn& verify_fn) { |
| // 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 = |
| GetPackageTempMountPoint(apex.GetManifest()); |
| |
| StatusOr<MountedApexData> mount_status = |
| VerifyAndTempMountPackage(apex, temp_mount_point); |
| if (!mount_status.Ok()) { |
| LOG(ERROR) << "Failed to temp mount to " << temp_mount_point << " : " |
| << mount_status.ErrorMessage(); |
| return mount_status.ErrorStatus(); |
| } |
| auto cleaner = [&]() { |
| LOG(DEBUG) << "Unmounting " << temp_mount_point; |
| Status status = Unmount(*mount_status); |
| if (!status.Ok()) { |
| LOG(WARNING) << "Failed to unmount " << temp_mount_point << " : " |
| << status.ErrorMessage(); |
| } |
| }; |
| auto scope_guard = android::base::make_scope_guard(cleaner); |
| return verify_fn(temp_mount_point); |
| } |
| |
| template <typename HookFn, typename HookCall> |
| Status PrePostinstallPackages(const std::vector<ApexFile>& apexes, HookFn fn, |
| HookCall call) { |
| if (apexes.empty()) { |
| return Status::Fail("Empty set of inputs"); |
| } |
| |
| // 1) Check whether the APEXes have hooks. |
| bool has_hooks = false; |
| for (const ApexFile& apex_file : apexes) { |
| if (!(apex_file.GetManifest().*fn)().empty()) { |
| has_hooks = true; |
| break; |
| } |
| } |
| |
| // 2) If we found hooks, run the pre/post-install. |
| if (has_hooks) { |
| Status install_status = (*call)(apexes); |
| if (!install_status.Ok()) { |
| return install_status; |
| } |
| } |
| |
| return Status::Success(); |
| } |
| |
| Status PreinstallPackages(const std::vector<ApexFile>& apexes) { |
| return PrePostinstallPackages(apexes, &ApexManifest::preinstallhook, |
| &StagePreInstall); |
| } |
| |
| Status PostinstallPackages(const std::vector<ApexFile>& apexes) { |
| return PrePostinstallPackages(apexes, &ApexManifest::postinstallhook, |
| &StagePostInstall); |
| } |
| |
| template <typename RetType, typename Fn> |
| RetType HandlePackages(const std::vector<std::string>& paths, Fn fn) { |
| // 1) Open all APEXes. |
| std::vector<ApexFile> apex_files; |
| for (const std::string& path : paths) { |
| StatusOr<ApexFile> apex_file = ApexFile::Open(path); |
| if (!apex_file.Ok()) { |
| return RetType::Fail(apex_file.ErrorMessage()); |
| } |
| apex_files.emplace_back(std::move(*apex_file)); |
| } |
| |
| // 2) Dispatch. |
| return fn(apex_files); |
| } |
| |
| Status 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.ErrorStatus(); |
| } |
| auto verify_fn = [&](const std::string& system_apex_path) { |
| return shim::ValidateUpdate(system_apex_path, to.GetPath()); |
| }; |
| return RunVerifyFnInsideTempMount(*system_shim, verify_fn); |
| } |
| |
| // 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. |
| Status VerifyPackageBoot(const ApexFile& apex_file) { |
| if (apex_file.IsFlattened()) { |
| return Status::Fail("Can't upgrade flattened apex"); |
| } |
| StatusOr<ApexVerityData> verity_or = apex_file.VerifyApexVerity(); |
| if (!verity_or.Ok()) { |
| return Status::Fail(verity_or.ErrorMessage()); |
| } |
| |
| 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& status = ValidateStagingShimApex(apex_file); |
| if (!status.Ok()) { |
| return status; |
| } |
| } |
| return Status::Success(); |
| } |
| |
| // 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. |
| Status VerifyPackageInstall(const ApexFile& apex_file) { |
| const auto& verify_package_boot_status = VerifyPackageBoot(apex_file); |
| if (!verify_package_boot_status.Ok()) { |
| return verify_package_boot_status; |
| } |
| if (!kUpdatable) { |
| return Status::Fail(StringLog() << "Attempted to upgrade apex package " |
| << apex_file.GetPath() |
| << " on a device that doesn't support it"); |
| } |
| StatusOr<ApexVerityData> verity_or = apex_file.VerifyApexVerity(); |
| |
| constexpr const auto kSuccessFn = [](const std::string& _) { |
| return Status::Success(); |
| }; |
| return RunVerifyFnInsideTempMount(apex_file, kSuccessFn); |
| } |
| |
| template <typename VerifyApexFn> |
| StatusOr<std::vector<ApexFile>> verifyPackages( |
| const std::vector<std::string>& paths, const VerifyApexFn& verify_apex_fn) { |
| if (paths.empty()) { |
| return StatusOr<std::vector<ApexFile>>::MakeError("Empty set of inputs"); |
| } |
| LOG(DEBUG) << "verifyPackages() for " << Join(paths, ','); |
| |
| using StatusT = StatusOr<std::vector<ApexFile>>; |
| auto verify_fn = [&](std::vector<ApexFile>& apexes) { |
| for (const ApexFile& apex_file : apexes) { |
| Status status = verify_apex_fn(apex_file); |
| if (!status.Ok()) { |
| return StatusT::MakeError(status); |
| } |
| } |
| return StatusT(std::move(apexes)); |
| }; |
| return HandlePackages<StatusT>(paths, verify_fn); |
| } |
| |
| StatusOr<ApexFile> verifySessionDir(const int session_id) { |
| std::string sessionDirPath = std::string(kStagedSessionsDir) + "/session_" + |
| std::to_string(session_id); |
| LOG(INFO) << "Scanning " << sessionDirPath |
| << " looking for packages to be validated"; |
| StatusOr<std::vector<std::string>> scan = |
| FindApexFilesByName(sessionDirPath, /* include_dirs=*/false); |
| if (!scan.Ok()) { |
| LOG(WARNING) << scan.ErrorMessage(); |
| return StatusOr<ApexFile>::MakeError(scan.ErrorMessage()); |
| } |
| |
| if (scan->size() > 1) { |
| return StatusOr<ApexFile>::MakeError( |
| "More than one APEX package found in the same session directory."); |
| } |
| |
| auto verified = verifyPackages(*scan, VerifyPackageInstall); |
| if (!verified.Ok()) { |
| return StatusOr<ApexFile>::MakeError(verified.ErrorStatus()); |
| } |
| return StatusOr<ApexFile>(std::move((*verified)[0])); |
| } |
| |
| Status ClearSessions() { |
| auto sessions = ApexSession::GetSessions(); |
| int cnt = 0; |
| for (ApexSession& session : sessions) { |
| Status status = session.DeleteSession(); |
| if (!status.Ok()) { |
| return status; |
| } |
| cnt++; |
| } |
| if (cnt > 0) { |
| LOG(DEBUG) << "Deleted " << cnt << " sessions"; |
| } |
| return Status::Success(); |
| } |
| |
| Status DeleteBackup() { |
| auto exists = PathExists(std::string(kApexBackupDir)); |
| if (!exists.Ok()) { |
| return Status::Fail(StringLog() << "Can't clean " << kApexBackupDir << " : " |
| << exists.ErrorMessage()); |
| } |
| if (!*exists) { |
| LOG(DEBUG) << kApexBackupDir << " does not exist. Nothing to clean"; |
| return Status::Success(); |
| } |
| return DeleteDirContent(std::string(kApexBackupDir)); |
| } |
| |
| Status BackupActivePackages() { |
| LOG(DEBUG) << "Initializing backup of " << kActiveApexPackagesDataDir; |
| |
| // Previous restore might've delete backups folder. |
| auto create_status = createDirIfNeeded(kApexBackupDir, 0700); |
| if (!create_status.Ok()) { |
| return Status::Fail(StringLog() |
| << "Backup failed : " << create_status.ErrorMessage()); |
| } |
| |
| auto apex_active_exists = PathExists(std::string(kActiveApexPackagesDataDir)); |
| if (!apex_active_exists.Ok()) { |
| return Status::Fail("Backup failed : " + apex_active_exists.ErrorMessage()); |
| } |
| if (!*apex_active_exists) { |
| LOG(DEBUG) << kActiveApexPackagesDataDir |
| << " does not exist. Nothing to backup"; |
| return Status::Success(); |
| } |
| |
| auto active_packages = |
| FindApexFilesByName(kActiveApexPackagesDataDir, false /* include_dirs */); |
| if (!active_packages.Ok()) { |
| return Status::Fail(StringLog() << "Backup failed : " |
| << active_packages.ErrorMessage()); |
| } |
| |
| auto cleanup_status = DeleteBackup(); |
| if (!cleanup_status.Ok()) { |
| return Status::Fail(StringLog() |
| << "Backup failed : " << cleanup_status.ErrorMessage()); |
| } |
| |
| auto backup_path_fn = [](const ApexFile& apex_file) { |
| return StringPrintf("%s/%s%s", kApexBackupDir, |
| GetPackageId(apex_file.GetManifest()).c_str(), |
| kApexPackageSuffix); |
| }; |
| |
| auto deleter = []() { |
| auto status = DeleteDirContent(std::string(kApexBackupDir)); |
| if (!status.Ok()) { |
| LOG(ERROR) << "Failed to cleanup " << kApexBackupDir << " : " |
| << status.ErrorMessage(); |
| } |
| }; |
| auto scope_guard = android::base::make_scope_guard(deleter); |
| |
| for (const std::string& path : *active_packages) { |
| StatusOr<ApexFile> apex_file = ApexFile::Open(path); |
| if (!apex_file.Ok()) { |
| return Status::Fail("Backup failed : " + apex_file.ErrorMessage()); |
| } |
| const auto& dest_path = backup_path_fn(*apex_file); |
| if (link(apex_file->GetPath().c_str(), dest_path.c_str()) != 0) { |
| return Status::Fail(PStringLog() |
| << "Failed to backup " << apex_file->GetPath()); |
| } |
| } |
| |
| scope_guard.Disable(); // Accept the backup. |
| return Status::Success(); |
| } |
| |
| Status DoRollback(ApexSession& session) { |
| if (gInFsCheckpointMode) { |
| // We will roll back automatically when we reboot |
| return Status::Success(); |
| } |
| auto scope_guard = android::base::make_scope_guard([&]() { |
| auto st = session.UpdateStateAndCommit(SessionState::ROLLBACK_FAILED); |
| LOG(DEBUG) << "Marking " << session << " as failed to rollback"; |
| if (!st.Ok()) { |
| LOG(WARNING) << "Failed to mark session " << session |
| << " as failed to rollback : " << st.ErrorMessage(); |
| } |
| }); |
| |
| auto backup_exists = PathExists(std::string(kApexBackupDir)); |
| if (!backup_exists.Ok()) { |
| return backup_exists.ErrorStatus(); |
| } |
| if (!*backup_exists) { |
| return Status::Fail(StringLog() << kApexBackupDir << " does not exist"); |
| } |
| |
| struct stat stat_data; |
| if (stat(kActiveApexPackagesDataDir, &stat_data) != 0) { |
| return Status::Fail(PStringLog() |
| << "Failed to access " << kActiveApexPackagesDataDir); |
| } |
| |
| LOG(DEBUG) << "Deleting existing packages in " << kActiveApexPackagesDataDir; |
| auto delete_status = |
| DeleteDirContent(std::string(kActiveApexPackagesDataDir)); |
| if (!delete_status.Ok()) { |
| return delete_status; |
| } |
| |
| LOG(DEBUG) << "Renaming " << kApexBackupDir << " to " |
| << kActiveApexPackagesDataDir; |
| if (rename(kApexBackupDir, kActiveApexPackagesDataDir) != 0) { |
| return Status::Fail(PStringLog() << "Failed to rename " << kApexBackupDir |
| << " to " << kActiveApexPackagesDataDir); |
| } |
| |
| LOG(DEBUG) << "Restoring original permissions for " |
| << kActiveApexPackagesDataDir; |
| if (chmod(kActiveApexPackagesDataDir, stat_data.st_mode & ALLPERMS) != 0) { |
| // TODO: should we wipe out /data/apex/active if chmod fails? |
| return Status::Fail(PStringLog() |
| << "Failed to restore original permissions for " |
| << kActiveApexPackagesDataDir); |
| } |
| |
| scope_guard.Disable(); // Rollback succeeded. Accept state. |
| return Status::Success(); |
| } |
| |
| Status RollbackStagedSession(ApexSession& session) { |
| // If the session is staged, it hasn't been activated yet, and we just need |
| // to update its state to prevent it from being activated later. |
| return session.UpdateStateAndCommit(SessionState::ROLLED_BACK); |
| } |
| |
| Status RollbackActivatedSession(ApexSession& session) { |
| if (gInFsCheckpointMode) { |
| LOG(DEBUG) << "Checkpoint mode is enabled"; |
| // On checkpointing devices, our modifications on /data will be |
| // automatically rolled back when we abort changes. Updating the session |
| // state is pointless here, as it will be rolled back as well. |
| return Status::Success(); |
| } |
| |
| auto status = |
| session.UpdateStateAndCommit(SessionState::ROLLBACK_IN_PROGRESS); |
| if (!status.Ok()) { |
| // TODO: should we continue with a rollback? |
| return Status::Fail(StringLog() << "Rollback of session " << session |
| << " failed : " << status.ErrorMessage()); |
| } |
| |
| status = DoRollback(session); |
| if (!status.Ok()) { |
| return Status::Fail(StringLog() << "Rollback of session " << session |
| << " failed : " << status.ErrorMessage()); |
| } |
| |
| status = session.UpdateStateAndCommit(SessionState::ROLLED_BACK); |
| if (!status.Ok()) { |
| LOG(WARNING) << "Failed to mark session " << session |
| << " as rolled back : " << status.ErrorMessage(); |
| } |
| |
| return Status::Success(); |
| } |
| |
| Status RollbackSession(ApexSession& session) { |
| LOG(DEBUG) << "Initializing rollback of " << session; |
| |
| switch (session.GetState()) { |
| case SessionState::ROLLBACK_IN_PROGRESS: |
| [[clang::fallthrough]]; |
| case SessionState::ROLLED_BACK: |
| return Status::Success(); |
| case SessionState::STAGED: |
| return RollbackStagedSession(session); |
| case SessionState::ACTIVATED: |
| return RollbackActivatedSession(session); |
| default: |
| return Status::Fail(StringLog() << "Can't restore session " << session |
| << " : session is in a wrong state"); |
| } |
| } |
| |
| Status ResumeRollback(ApexSession& session) { |
| auto backup_exists = PathExists(std::string(kApexBackupDir)); |
| if (!backup_exists.Ok()) { |
| return backup_exists.ErrorStatus(); |
| } |
| if (*backup_exists) { |
| auto rollback_status = DoRollback(session); |
| if (!rollback_status.Ok()) { |
| return rollback_status; |
| } |
| } |
| auto status = session.UpdateStateAndCommit(SessionState::ROLLED_BACK); |
| if (!status.Ok()) { |
| LOG(WARNING) << "Failed to mark session " << session |
| << " as rolled back : " << status.ErrorMessage(); |
| } |
| return Status::Success(); |
| } |
| |
| Status UnmountPackage(const ApexFile& apex, bool allow_latest) { |
| LOG(VERBOSE) << "Unmounting " << GetPackageId(apex.GetManifest()); |
| |
| 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.has_value()) { |
| return Status::Fail(StringLog() << "Did not find " << apex.GetPath()); |
| } |
| |
| if (latest) { |
| if (!allow_latest) { |
| return Status::Fail(StringLog() |
| << "Package " << apex.GetPath() << " is active"); |
| } |
| std::string mount_point = apexd_private::GetActiveMountPoint(manifest); |
| LOG(VERBOSE) << "Unmounting and deleting " << mount_point; |
| if (umount2(mount_point.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0) { |
| return Status::Fail(PStringLog() << "Failed to unmount " << mount_point); |
| } |
| if (rmdir(mount_point.c_str()) != 0) { |
| PLOG(ERROR) << "Could not rmdir " << mount_point; |
| // Continue here. |
| } |
| } |
| |
| // Clean up gMountedApexes now, even though we're not fully done. |
| gMountedApexes.RemoveMountedApex(manifest.name(), apex.GetPath()); |
| return Unmount(*data); |
| } |
| |
| } // namespace |
| |
| namespace apexd_private { |
| |
| Status MountPackage(const ApexFile& apex, const std::string& mountPoint) { |
| auto ret = |
| MountPackageImpl(apex, mountPoint, GetPackageId(apex.GetManifest()), |
| /* verifyImage = */ false); |
| if (!ret.Ok()) { |
| return ret.ErrorStatus(); |
| } |
| |
| gMountedApexes.AddMountedApex(apex.GetManifest().name(), false, |
| std::move(*ret)); |
| return Status::Success(); |
| } |
| |
| Status UnmountPackage(const ApexFile& apex) { |
| return android::apex::UnmountPackage(apex, /* allow_latest= */ false); |
| } |
| |
| bool IsMounted(const std::string& name, const std::string& full_path) { |
| bool found_mounted = false; |
| gMountedApexes.ForallMountedApexes( |
| name, [&](const MountedApexData& data, bool latest ATTRIBUTE_UNUSED) { |
| 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 GetActiveMountPoint(const ApexManifest& manifest) { |
| return StringPrintf("%s/%s", kApexRoot, manifest.name().c_str()); |
| } |
| |
| } // namespace apexd_private |
| |
| Status resumeRollbackIfNeeded() { |
| auto session = ApexSession::GetActiveSession(); |
| if (!session.Ok()) { |
| return session.ErrorStatus(); |
| } |
| if (!session->has_value()) { |
| return Status::Success(); |
| } |
| if ((**session).GetState() == SessionState::ROLLBACK_IN_PROGRESS) { |
| // This means that phone was rebooted during the rollback. Resuming it. |
| return ResumeRollback(**session); |
| } |
| return Status::Success(); |
| } |
| |
| Status activatePackageImpl(const ApexFile& apex_file) { |
| const ApexManifest& manifest = apex_file.GetManifest(); |
| |
| if (gBootstrap && !isBootstrapApex(apex_file)) { |
| LOG(INFO) << "Skipped when bootstrapping"; |
| return Status::Success(); |
| } else if (!kUpdatable && !gBootstrap && isBootstrapApex(apex_file)) { |
| LOG(INFO) << "Package already activated in bootstrap"; |
| return Status::Success(); |
| } |
| |
| // 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 is_newest_version = true; |
| bool found_other_version = false; |
| 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) { |
| StatusOr<ApexFile> otherApex = ApexFile::Open(data.full_path); |
| if (!otherApex.Ok()) { |
| return; |
| } |
| found_other_version = true; |
| if (static_cast<uint64_t>(otherApex->GetManifest().version()) == |
| new_version) { |
| version_found_mounted = true; |
| version_found_active = latest; |
| } |
| if (static_cast<uint64_t>(otherApex->GetManifest().version()) > |
| new_version) { |
| is_newest_version = false; |
| } |
| }); |
| if (version_found_active) { |
| LOG(DEBUG) << "Package " << manifest.name() << " with version " |
| << manifest.version() << " already active"; |
| return Status::Success(); |
| } |
| } |
| |
| const std::string& mountPoint = apexd_private::GetPackageMountPoint(manifest); |
| |
| if (!version_found_mounted) { |
| Status mountStatus = apexd_private::MountPackage(apex_file, mountPoint); |
| if (!mountStatus.Ok()) { |
| return mountStatus; |
| } |
| } |
| |
| bool mounted_latest = false; |
| if (is_newest_version) { |
| const Status& update_st = apexd_private::BindMount( |
| apexd_private::GetActiveMountPoint(manifest), mountPoint); |
| mounted_latest = update_st.Ok(); |
| if (!update_st.Ok()) { |
| return Status::Fail(StringLog() |
| << "Failed to update package " << manifest.name() |
| << " to version " << manifest.version() << " : " |
| << update_st.ErrorMessage()); |
| } |
| } |
| if (mounted_latest) { |
| gMountedApexes.SetLatest(manifest.name(), apex_file.GetPath()); |
| } |
| |
| LOG(DEBUG) << "Successfully activated " << apex_file.GetPath() |
| << " package_name: " << manifest.name() |
| << " version: " << manifest.version(); |
| return Status::Success(); |
| } |
| |
| Status activatePackage(const std::string& full_path) { |
| LOG(INFO) << "Trying to activate " << full_path; |
| |
| StatusOr<ApexFile> apex_file = ApexFile::Open(full_path); |
| if (!apex_file.Ok()) { |
| return apex_file.ErrorStatus(); |
| } |
| return activatePackageImpl(*apex_file); |
| } |
| |
| Status deactivatePackage(const std::string& full_path) { |
| LOG(INFO) << "Trying to deactivate " << full_path; |
| |
| StatusOr<ApexFile> apexFile = ApexFile::Open(full_path); |
| if (!apexFile.Ok()) { |
| return apexFile.ErrorStatus(); |
| } |
| |
| return UnmountPackage(*apexFile, /* allow_latest= */ true); |
| } |
| |
| std::vector<ApexFile> getActivePackages() { |
| std::vector<ApexFile> ret; |
| gMountedApexes.ForallMountedApexes( |
| [&](const std::string&, const MountedApexData& data, bool latest) { |
| if (!latest) { |
| return; |
| } |
| |
| StatusOr<ApexFile> apexFile = ApexFile::Open(data.full_path); |
| if (!apexFile.Ok()) { |
| // TODO: Fail? |
| return; |
| } |
| ret.emplace_back(std::move(*apexFile)); |
| }); |
| |
| return ret; |
| } |
| |
| 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; |
| for (const auto& dir : kApexPackageBuiltinDirs) { |
| auto apex_files = FindApexFilesByName(dir, /* include_dirs=*/false); |
| if (!apex_files.Ok()) { |
| LOG(ERROR) << apex_files.ErrorMessage(); |
| continue; |
| } |
| for (const std::string& path : *apex_files) { |
| StatusOr<ApexFile> apex_file = ApexFile::Open(path); |
| if (!apex_file.Ok()) { |
| LOG(ERROR) << apex_file.ErrorMessage(); |
| } else { |
| ret.emplace_back(std::move(*apex_file)); |
| } |
| } |
| } |
| return ret; |
| } |
| |
| StatusOr<ApexFile> getActivePackage(const std::string& packageName) { |
| std::vector<ApexFile> packages = getActivePackages(); |
| for (ApexFile& apex : packages) { |
| if (apex.GetManifest().name() == packageName) { |
| return StatusOr<ApexFile>(std::move(apex)); |
| } |
| } |
| |
| return StatusOr<ApexFile>::MakeError( |
| PStringLog() << "Cannot find matching package for: " << packageName); |
| } |
| |
| Status abortActiveSession() { |
| auto session_or_none = ApexSession::GetActiveSession(); |
| if (!session_or_none.Ok()) { |
| return session_or_none.ErrorStatus(); |
| } |
| if (session_or_none->has_value()) { |
| auto& session = session_or_none->value(); |
| LOG(DEBUG) << "Aborting active session " << session; |
| switch (session.GetState()) { |
| case SessionState::VERIFIED: |
| [[clang::fallthrough]]; |
| case SessionState::STAGED: |
| return session.DeleteSession(); |
| case SessionState::ACTIVATED: |
| return RollbackActivatedSession(session); |
| default: |
| return Status::Fail(StringLog() |
| << "Session " << session << " can't be aborted"); |
| } |
| } else { |
| LOG(DEBUG) << "There are no active sessions"; |
| return Status::Success(); |
| } |
| } |
| |
| Status scanPackagesDirAndActivate(const char* apex_package_dir) { |
| LOG(INFO) << "Scanning " << apex_package_dir << " looking for APEX packages."; |
| |
| const bool scanBuiltinApexes = isPathForBuiltinApexes(apex_package_dir); |
| StatusOr<std::vector<std::string>> scan = |
| FindApexFilesByName(apex_package_dir, scanBuiltinApexes); |
| if (!scan.Ok()) { |
| return Status::Fail(StringLog() << "Failed to scan " << apex_package_dir |
| << " : " << scan.ErrorMessage()); |
| } |
| |
| const auto& packages_with_code = GetActivePackagesMap(); |
| |
| std::vector<std::string> failed_pkgs; |
| size_t activated_cnt = 0; |
| size_t skipped_cnt = 0; |
| for (const std::string& name : *scan) { |
| LOG(INFO) << "Found " << name; |
| |
| StatusOr<ApexFile> apex_file = ApexFile::Open(name); |
| if (!apex_file.Ok()) { |
| LOG(ERROR) << "Failed to activate " << name << " : " |
| << apex_file.ErrorMessage(); |
| failed_pkgs.push_back(name); |
| continue; |
| } |
| |
| uint64_t new_version = |
| static_cast<uint64_t>(apex_file->GetManifest().version()); |
| const auto& it = packages_with_code.find(apex_file->GetManifest().name()); |
| if (it != packages_with_code.end() && it->second >= new_version) { |
| LOG(INFO) << "Skipping activation of " << name |
| << " same package with higher version " << it->second |
| << " is already active"; |
| skipped_cnt++; |
| continue; |
| } |
| |
| if (!kUpdatable && !apex_file->IsFlattened()) { |
| LOG(INFO) << "Skipping activation of non-flattened apex package " << name |
| << " because device doesn't support it"; |
| skipped_cnt++; |
| continue; |
| } |
| |
| Status res = activatePackageImpl(*apex_file); |
| if (!res.Ok()) { |
| LOG(ERROR) << "Failed to activate " << name << " : " |
| << res.ErrorMessage(); |
| failed_pkgs.push_back(name); |
| } else { |
| activated_cnt++; |
| } |
| } |
| |
| if (!failed_pkgs.empty()) { |
| return Status::Fail(StringLog() |
| << "Failed to activate following packages : " |
| << Join(failed_pkgs, ',')); |
| } |
| |
| LOG(INFO) << "Activated " << activated_cnt |
| << " packages. Skipped: " << skipped_cnt; |
| return Status::Success(); |
| } |
| |
| void scanStagedSessionsDirAndStage() { |
| LOG(INFO) << "Scanning " << kApexSessionsDir |
| << " looking for sessions to be activated."; |
| |
| auto stagedSessions = ApexSession::GetSessionsInState(SessionState::STAGED); |
| for (auto& session : stagedSessions) { |
| auto sessionId = session.GetId(); |
| |
| auto session_failed_fn = [&]() { |
| LOG(WARNING) << "Marking session " << sessionId << " as failed."; |
| auto st = session.UpdateStateAndCommit(SessionState::ACTIVATION_FAILED); |
| if (!st.Ok()) { |
| LOG(WARNING) << "Failed to mark session " << sessionId |
| << " as failed : " << st.ErrorMessage(); |
| } |
| }; |
| auto scope_guard = android::base::make_scope_guard(session_failed_fn); |
| |
| std::vector<std::string> dirsToScan; |
| if (session.GetChildSessionIds().empty()) { |
| dirsToScan.push_back(std::string(kStagedSessionsDir) + "/session_" + |
| std::to_string(sessionId)); |
| } else { |
| for (auto childSessionId : session.GetChildSessionIds()) { |
| dirsToScan.push_back(std::string(kStagedSessionsDir) + "/session_" + |
| std::to_string(childSessionId)); |
| } |
| } |
| |
| std::vector<std::string> apexes; |
| bool scanSuccessful = true; |
| for (const auto& dirToScan : dirsToScan) { |
| StatusOr<std::vector<std::string>> scan = |
| FindApexFilesByName(dirToScan, /* include_dirs=*/false); |
| if (!scan.Ok()) { |
| LOG(WARNING) << scan.ErrorMessage(); |
| scanSuccessful = false; |
| break; |
| } |
| |
| if (scan->size() > 1) { |
| LOG(WARNING) << "More than one APEX package found in the same session " |
| << "directory " << dirToScan << ", skipping activation."; |
| scanSuccessful = false; |
| break; |
| } |
| |
| if (scan->empty()) { |
| LOG(WARNING) << "No APEX packages found while scanning " << dirToScan |
| << " session id: " << sessionId << "."; |
| scanSuccessful = false; |
| break; |
| } |
| apexes.push_back(std::move((*scan)[0])); |
| } |
| |
| if (!scanSuccessful) { |
| continue; |
| } |
| |
| // Run postinstall, if necessary. |
| Status postinstall_status = postinstallPackages(apexes); |
| if (!postinstall_status.Ok()) { |
| LOG(ERROR) << "Postinstall failed for session " |
| << std::to_string(sessionId) << ": " |
| << postinstall_status.ErrorMessage(); |
| continue; |
| } |
| |
| const Status result = stagePackages(apexes); |
| if (!result.Ok()) { |
| LOG(ERROR) << "Activation failed for packages " << Join(apexes, ',') |
| << ": " << result.ErrorMessage(); |
| continue; |
| } |
| |
| // Session was OK, release scopeguard. |
| scope_guard.Disable(); |
| |
| auto st = session.UpdateStateAndCommit(SessionState::ACTIVATED); |
| if (!st.Ok()) { |
| LOG(ERROR) << "Failed to mark " << session |
| << " as activated : " << st.ErrorMessage(); |
| } |
| } |
| } |
| |
| Status preinstallPackages(const std::vector<std::string>& paths) { |
| if (paths.empty()) { |
| return Status::Fail("Empty set of inputs"); |
| } |
| LOG(DEBUG) << "preinstallPackages() for " << Join(paths, ','); |
| return HandlePackages<Status>(paths, PreinstallPackages); |
| } |
| |
| Status postinstallPackages(const std::vector<std::string>& paths) { |
| if (paths.empty()) { |
| return Status::Fail("Empty set of inputs"); |
| } |
| LOG(DEBUG) << "postinstallPackages() for " << Join(paths, ','); |
| return HandlePackages<Status>(paths, PostinstallPackages); |
| } |
| |
| namespace { |
| std::string StageDestPath(const ApexFile& apex_file) { |
| return StringPrintf("%s/%s%s", kActiveApexPackagesDataDir, |
| GetPackageId(apex_file.GetManifest()).c_str(), |
| kApexPackageSuffix); |
| } |
| |
| std::vector<std::string> FilterUnnecessaryStagingPaths( |
| const std::vector<std::string>& tmp_paths) { |
| const auto& packages_with_code = GetActivePackagesMap(); |
| |
| auto filter_fn = [&packages_with_code](const std::string& path) { |
| auto apex_file = ApexFile::Open(path); |
| if (!apex_file.Ok()) { |
| // Pretend that apex should be staged, so that stagePackages will fail |
| // trying to open it. |
| return true; |
| } |
| std::string dest_path = StageDestPath(*apex_file); |
| if (access(dest_path.c_str(), F_OK) == 0) { |
| LOG(DEBUG) << dest_path << " already exists. Skipping"; |
| return false; |
| } |
| const ApexManifest& manifest = apex_file->GetManifest(); |
| const auto& it = packages_with_code.find(manifest.name()); |
| uint64_t new_version = static_cast<uint64_t>(manifest.version()); |
| if (it != packages_with_code.end() && it->second == new_version) { |
| LOG(DEBUG) << GetPackageId(manifest) << " is already active. Skipping"; |
| return false; |
| } |
| return true; |
| }; |
| |
| std::vector<std::string> ret; |
| std::copy_if(tmp_paths.begin(), tmp_paths.end(), std::back_inserter(ret), |
| filter_fn); |
| return ret; |
| } |
| |
| } // namespace |
| |
| Status stagePackages(const std::vector<std::string>& tmpPaths) { |
| if (tmpPaths.empty()) { |
| return Status::Fail("Empty set of inputs"); |
| } |
| LOG(DEBUG) << "stagePackages() for " << Join(tmpPaths, ','); |
| |
| // Note: this function is temporary. As such the code is not optimized, e.g., |
| // it will open ApexFiles multiple times. |
| |
| // 1) Verify all packages. |
| auto verify_status = verifyPackages(tmpPaths, VerifyPackageBoot); |
| if (!verify_status.Ok()) { |
| return Status::Fail(verify_status.ErrorMessage()); |
| } |
| |
| // 2) Now stage all of them. |
| |
| // Make sure that kActiveApexPackagesDataDir exists. |
| auto create_dir_status = |
| createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0750); |
| if (!create_dir_status.Ok()) { |
| return Status::Fail(create_dir_status.ErrorMessage()); |
| } |
| |
| // 2) Filter out packages that do not require staging, e.g.: |
| // a) Their /data/apex/active/package.apex@version already exists. |
| // b) Such package is already active |
| std::vector<std::string> paths_to_stage = |
| FilterUnnecessaryStagingPaths(tmpPaths); |
| if (paths_to_stage.empty()) { |
| // Finish early if nothing to stage. Since stagePackages fails in case |
| // tmpPaths is empty, it's fine to return Success here. |
| return Status::Success(); |
| } |
| |
| // 3) Now stage all of them. |
| |
| // Ensure the APEX gets removed on failure. |
| std::unordered_set<std::string> staged_files; |
| auto deleter = [&staged_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; |
| } |
| } |
| }; |
| auto scope_guard = android::base::make_scope_guard(deleter); |
| |
| std::unordered_set<std::string> staged_packages; |
| for (const std::string& path : paths_to_stage) { |
| StatusOr<ApexFile> apex_file = ApexFile::Open(path); |
| if (!apex_file.Ok()) { |
| return apex_file.ErrorStatus(); |
| } |
| std::string dest_path = StageDestPath(*apex_file); |
| |
| if (link(apex_file->GetPath().c_str(), dest_path.c_str()) != 0) { |
| // TODO: Get correct binder error status. |
| return Status::Fail(PStringLog() |
| << "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); |
| } |
| |
| Status unstagePackages(const std::vector<std::string>& paths) { |
| if (paths.empty()) { |
| return Status::Fail("Empty set of inputs"); |
| } |
| LOG(DEBUG) << "unstagePackages() for " << Join(paths, ','); |
| |
| // TODO: to make unstage safer, we can copy to be unstaged packages to a |
| // temporary folder and restore state from it in case unstagePackages fails. |
| |
| for (const std::string& path : paths) { |
| if (access(path.c_str(), F_OK) != 0) { |
| return Status::Fail(PStringLog() << "Can't access " << path); |
| } |
| } |
| |
| for (const std::string& path : paths) { |
| if (unlink(path.c_str()) != 0) { |
| return Status::Fail(PStringLog() << "Can't unlink " << path); |
| } |
| } |
| |
| return Status::Success(); |
| } |
| |
| Status rollbackStagedSessionIfAny() { |
| auto session = ApexSession::GetActiveSession(); |
| if (!session.Ok()) { |
| return session.ErrorStatus(); |
| } |
| if (!session->has_value()) { |
| LOG(WARNING) << "No session to rollback"; |
| return Status::Success(); |
| } |
| if ((*session)->GetState() == SessionState::STAGED) { |
| LOG(INFO) << "Rolling back session " << **session; |
| return RollbackStagedSession(**session); |
| } |
| return Status::Fail(StringLog() << "Can't rollback " << **session |
| << " because it is not in STAGED state"); |
| } |
| |
| Status rollbackActiveSession() { |
| auto session = ApexSession::GetActiveSession(); |
| if (!session.Ok()) { |
| return Status::Fail(StringLog() << "Failed to get active session : " |
| << session.ErrorMessage()); |
| } else if (!session->has_value()) { |
| return Status::Fail( |
| "Rollback requested, when there are no active sessions."); |
| } else { |
| return RollbackSession(*(*session)); |
| } |
| } |
| |
| Status rollbackActiveSessionAndReboot() { |
| auto status = rollbackActiveSession(); |
| if (!status.Ok()) { |
| return status; |
| } |
| LOG(ERROR) << "Successfully rolled back. Time to reboot device."; |
| if (gInFsCheckpointMode) { |
| Status res = gVoldService->AbortChanges("apexd_initiated" /* message */, |
| false /* retry */); |
| if (!res.Ok()) { |
| LOG(ERROR) << res.ErrorMessage(); |
| } |
| } |
| Reboot(); |
| return Status::Success(); |
| } |
| |
| int onBootstrap() { |
| gBootstrap = true; |
| |
| Status preAllocate = preAllocateLoopDevices(); |
| if (!preAllocate.Ok()) { |
| LOG(ERROR) << "Failed to pre-allocate loop devices : " |
| << preAllocate.ErrorMessage(); |
| } |
| |
| Status status = collectApexKeys(); |
| if (!status.Ok()) { |
| LOG(ERROR) << "Failed to collect APEX keys : " << status.ErrorMessage(); |
| return 1; |
| } |
| |
| // Activate built-in APEXes for processes launched before /data is mounted. |
| status = scanPackagesDirAndActivate(kApexPackageSystemDir); |
| if (!status.Ok()) { |
| LOG(ERROR) << "Failed to activate APEX files in " << kApexPackageSystemDir |
| << " : " << status.ErrorMessage(); |
| return 1; |
| } |
| LOG(INFO) << "Bootstrapping done"; |
| return 0; |
| } |
| |
| void onStart(CheckpointInterface* checkpoint_service) { |
| LOG(INFO) << "Marking APEXd as starting"; |
| if (!android::base::SetProperty(kApexStatusSysprop, kApexStatusStarting)) { |
| PLOG(ERROR) << "Failed to set " << kApexStatusSysprop << " to " |
| << kApexStatusStarting; |
| } |
| |
| if (checkpoint_service != nullptr) { |
| gVoldService = checkpoint_service; |
| StatusOr<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.ErrorMessage(); |
| } |
| if (gSupportsFsCheckpoints) { |
| StatusOr<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.ErrorMessage(); |
| } |
| } |
| } |
| |
| // Ask whether we should roll back any staged sessions; this can happen if |
| // we've exceeded the retry count on a device that supports filesystem |
| // checkpointing. |
| if (gSupportsFsCheckpoints) { |
| StatusOr<bool> needs_rollback = gVoldService->NeedsRollback(); |
| if (!needs_rollback.Ok()) { |
| LOG(ERROR) << "Failed to check if we need a rollback: " |
| << needs_rollback.ErrorMessage(); |
| } else if (*needs_rollback) { |
| LOG(INFO) << "Exceeded number of session retries (" |
| << kNumRetriesWhenCheckpointingEnabled |
| << "). Starting a rollback"; |
| Status status = rollbackStagedSessionIfAny(); |
| if (!status.Ok()) { |
| LOG(ERROR) |
| << "Failed to roll back (as requested by fs checkpointing) : " |
| << status.ErrorMessage(); |
| } |
| } |
| } |
| |
| Status status = collectApexKeys(); |
| if (!status.Ok()) { |
| LOG(ERROR) << "Failed to collect APEX keys : " << status.ErrorMessage(); |
| return; |
| } |
| |
| gMountedApexes.PopulateFromMounts(); |
| |
| // Activate APEXes from /data/apex. If one in the directory is newer than the |
| // system one, the new one will eclipse the old one. |
| scanStagedSessionsDirAndStage(); |
| status = resumeRollbackIfNeeded(); |
| if (!status.Ok()) { |
| LOG(ERROR) << "Failed to resume rollback : " << status.ErrorMessage(); |
| } |
| |
| status = scanPackagesDirAndActivate(kActiveApexPackagesDataDir); |
| if (!status.Ok()) { |
| LOG(ERROR) << "Failed to activate packages from " |
| << kActiveApexPackagesDataDir << " : " << status.ErrorMessage(); |
| Status rollback_status = rollbackActiveSessionAndReboot(); |
| if (!rollback_status.Ok()) { |
| // TODO: should we kill apexd in this case? |
| LOG(ERROR) << "Failed to rollback : " << rollback_status.ErrorMessage(); |
| } |
| } |
| |
| for (const auto& dir : kApexPackageBuiltinDirs) { |
| // TODO(b/123622800): if activation failed, rollback and reboot. |
| status = scanPackagesDirAndActivate(dir.c_str()); |
| if (!status.Ok()) { |
| // This should never happen. Like **really** never. |
| // TODO: should we kill apexd in this case? |
| LOG(ERROR) << "Failed to activate packages from " << dir << " : " |
| << status.ErrorMessage(); |
| } |
| } |
| } |
| |
| void onAllPackagesReady() { |
| // Set a system property to let other components to know that APEXs are |
| // correctly mounted and ready to be used. Before using any file from APEXs, |
| // they can query this system property to ensure that they are okay to |
| // access. Or they may have a on-property trigger to delay a task until |
| // APEXs become ready. |
| LOG(INFO) << "Marking APEXd as ready"; |
| if (!android::base::SetProperty(kApexStatusSysprop, kApexStatusReady)) { |
| PLOG(ERROR) << "Failed to set " << kApexStatusSysprop << " to " |
| << kApexStatusReady; |
| } |
| } |
| |
| StatusOr<std::vector<ApexFile>> submitStagedSession( |
| const int session_id, const std::vector<int>& child_session_ids) { |
| bool needsBackup = true; |
| Status cleanup_status = ClearSessions(); |
| if (!cleanup_status.Ok()) { |
| return StatusOr<std::vector<ApexFile>>::MakeError(cleanup_status); |
| } |
| |
| if (gSupportsFsCheckpoints) { |
| Status checkpoint_status = |
| gVoldService->StartCheckpoint(kNumRetriesWhenCheckpointingEnabled); |
| if (!checkpoint_status.Ok()) { |
| // The device supports checkpointing, but we could not start it; |
| // log a warning, but do continue, since we can live without it. |
| LOG(WARNING) << "Failed to start filesystem checkpoint on device that " |
| "should support it: " |
| << checkpoint_status.ErrorMessage(); |
| } else { |
| needsBackup = false; |
| } |
| } |
| |
| if (needsBackup) { |
| Status backup_status = BackupActivePackages(); |
| if (!backup_status.Ok()) { |
| return StatusOr<std::vector<ApexFile>>::MakeError(backup_status); |
| } |
| } |
| |
| std::vector<int> ids_to_scan; |
| if (!child_session_ids.empty()) { |
| ids_to_scan = child_session_ids; |
| } else { |
| ids_to_scan = {session_id}; |
| } |
| |
| std::vector<ApexFile> ret; |
| for (int id_to_scan : ids_to_scan) { |
| auto verified = verifySessionDir(id_to_scan); |
| if (!verified.Ok()) { |
| return StatusOr<std::vector<ApexFile>>::MakeError(verified.ErrorStatus()); |
| } |
| ret.push_back(std::move(*verified)); |
| } |
| |
| // Run preinstall, if necessary. |
| Status preinstall_status = PreinstallPackages(ret); |
| if (!preinstall_status.Ok()) { |
| return StatusOr<std::vector<ApexFile>>::MakeError(preinstall_status); |
| } |
| |
| auto session = ApexSession::CreateSession(session_id); |
| if (!session.Ok()) { |
| return StatusOr<std::vector<ApexFile>>::MakeError(session.ErrorMessage()); |
| } |
| (*session).SetChildSessionIds(child_session_ids); |
| Status commit_status = |
| (*session).UpdateStateAndCommit(SessionState::VERIFIED); |
| if (!commit_status.Ok()) { |
| return StatusOr<std::vector<ApexFile>>::MakeError(commit_status); |
| } |
| |
| return StatusOr<std::vector<ApexFile>>(std::move(ret)); |
| } |
| |
| Status markStagedSessionReady(const int session_id) { |
| auto session = ApexSession::GetSession(session_id); |
| if (!session.Ok()) { |
| return session.ErrorStatus(); |
| } |
| // We should only accept sessions in SessionState::VERIFIED or |
| // SessionState::STAGED state. In the SessionState::STAGED case, this |
| // function is effectively a no-op. |
| auto session_state = (*session).GetState(); |
| if (session_state == SessionState::STAGED) { |
| return Status::Success(); |
| } |
| if (session_state == SessionState::VERIFIED) { |
| return (*session).UpdateStateAndCommit(SessionState::STAGED); |
| } |
| return Status::Fail(StringLog() << "Invalid state for session " << session_id |
| << ". Cannot mark it as ready."); |
| } |
| |
| Status markStagedSessionSuccessful(const int session_id) { |
| auto session = ApexSession::GetSession(session_id); |
| if (!session.Ok()) { |
| return session.ErrorStatus(); |
| } |
| // Only SessionState::ACTIVATED or SessionState::SUCCESS states are accepted. |
| // In the SessionState::SUCCESS state, this function is a no-op. |
| if (session->GetState() == SessionState::SUCCESS) { |
| return Status::Success(); |
| } else if (session->GetState() == SessionState::ACTIVATED) { |
| auto cleanup_status = DeleteBackup(); |
| if (!cleanup_status.Ok()) { |
| return Status::Fail(StringLog() << "Failed to mark session " << *session |
| << " as successful : " |
| << cleanup_status.ErrorMessage()); |
| } |
| return session->UpdateStateAndCommit(SessionState::SUCCESS); |
| } else { |
| return Status::Fail(StringLog() << "Session " << *session |
| << " can not be marked successful"); |
| } |
| } |
| |
| // Find dangling mounts and unmount them. |
| // If one is on /data/apex/active, remove it. |
| void unmountDanglingMounts() { |
| std::multimap<std::string, MountedApexData> danglings; |
| gMountedApexes.ForallMountedApexes([&](const std::string& package, |
| const MountedApexData& data, |
| bool latest) { |
| if (!latest) { |
| danglings.insert({package, data}); |
| } |
| }); |
| |
| for (const auto& [package, data] : danglings) { |
| const std::string& path = data.full_path; |
| LOG(VERBOSE) << "Unmounting " << data.mount_point; |
| gMountedApexes.RemoveMountedApex(package, path); |
| if (auto st = Unmount(data); !st.Ok()) { |
| LOG(ERROR) << st.ErrorMessage(); |
| } |
| if (StartsWith(path, kActiveApexPackagesDataDir)) { |
| LOG(VERBOSE) << "Deleting old APEX " << path; |
| if (unlink(path.c_str()) != 0) { |
| PLOG(ERROR) << "Failed to delete " << path; |
| } |
| } |
| } |
| } |
| |
| } // namespace apex |
| } // namespace android |