/*
 * 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