Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  dexopt_chroot_setup.cc

  Sprache: C
 

/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include "dexopt_chroot_setup.h"

#include <sched.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <algorithm>
#include <cerrno>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <iterator>
#include <mutex>
#include <optional>
#include <regex>
#include <string>
#include <string_view>
#include <system_error>
#include <tuple>
#include <vector>

#include "aidl/com/android/server/art/BnDexoptChrootSetup.h"
#include "android-base/errors.h"
#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/no_destructor.h"
#include "android-base/properties.h"
#include "android-base/result.h"
#include "android-base/scopeguard.h"
#include "android-base/strings.h"
#include "android-base/unique_fd.h"
#include "android/binder_auto_utils.h"
#include "android/binder_manager.h"
#include "android/binder_process.h"
#include "base/file_utils.h"
#include "base/macros.h"
#include "base/os.h"
#include "base/stl_util.h"
#include "base/utils.h"
#include "exec_utils.h"
#include "fstab/fstab.h"
#include "tools/binder_utils.h"
#include "tools/cmdline_builder.h"
#include "tools/tools.h"

namespace art {
namespace dexopt_chroot_setup {

namespace {

using ::android::base::ConsumePrefix;
using ::android::base::Error;
using ::android::base::GetProperty;
using ::android::base::Join;
using ::android::base::make_scope_guard;
using ::android::base::NoDestructor;
using ::android::base::ReadFileToString;
using ::android::base::Readlink;
using ::android::base::Result;
using ::android::base::SetProperty;
using ::android::base::Split;
using ::android::base::Tokenize;
using ::android::base::unique_fd;
using ::android::base::WaitForProperty;
using ::android::base::WriteStringToFile;
using ::android::fs_mgr::FstabEntry;
using ::art::tools::CmdlineBuilder;
using ::art::tools::Fatal;
using ::art::tools::GetProcMountsDescendantsOfPath;
using ::art::tools::NonFatal;
using ::art::tools::PathStartsWith;
using ::ndk::ScopedAStatus;

constexpr const char* kServiceName = "dexopt_chroot_setup";
const NoDestructor<std::string> kBindMountTmpDir(
    std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) + "/mount_tmp");
const NoDestructor<std::string> kOtaSlotFile(std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) +
                                             "/ota_slot");
const NoDestructor<std::string> kSnapshotMappedFile(
    std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) + "/snapshot_mapped");
constexpr mode_t kChrootDefaultMode = 0755;
constexpr std::chrono::milliseconds kSnapshotCtlTimeout = std::chrono::seconds(60);
constexpr std::array<const char*, 4> kExternalLibDirs = {
    "/system/lib""/system/lib64""/system_ext/lib""/system_ext/lib64"};

bool IsOtaUpdate(const std::optional<std::string>& ota_slot) { return ota_slot.has_value(); }

Result<void> Run(std::string_view log_name, const std::vector<std::string>& args) {
  LOG(INFO) << "Running " << log_name << ": " << Join(args, /*separator=*/" ");

  std::string error_msg;
  if (!Exec(args, &error_msg)) {
    return Errorf("Failed to run {}: {}", log_name, error_msg);
  }

  LOG(INFO) << log_name << " returned code 0";
  return {};
}

Result<CmdlineBuilder> GetArtExecCmdlineBuilder() {
  std::string error_msg;
  std::string art_root = GetArtRootSafe(&error_msg);
  if (!error_msg.empty()) {
    return Error() << error_msg;
  }
  CmdlineBuilder args;
  args.Add(art_root + "/bin/art_exec")
      .Add("--chroot=%s", DexoptChrootSetup::CHROOT_DIR)
      .Add("--process-name-suffix=Pre-reboot Dexopt chroot");
  return args;
}

Result<void> CreateDir(const std::string& path) {
  std::error_code ec;
  std::filesystem::create_directory(path, ec);
  if (ec) {
    return Errorf("Failed to create dir '{}': {}", path, ec.message());
  }
  return {};
}

Result<bool> IsSymlink(const std::string& path) {
  std::error_code ec;
  bool res = std::filesystem::is_symlink(path, ec);
  if (ec) {
    return Errorf("Failed to create dir '{}': {}", path, ec.message());
  }
  return res;
}

Result<bool> IsSelfOrParentSymlink(const std::string& path) {
  // We don't use `Realpath` because it does a `stat(2)` call which requires the SELinux "getattr"
  // permission. which we don't have on all mount points.
  unique_fd fd(open(path.c_str(), O_PATH | O_CLOEXEC));
  if (fd.get() < 0) {
    return ErrnoErrorf("Failed to open '{}' to resolve real path", path);
  }
  std::string real_path;
  if (!Readlink(ART_FORMAT("/proc/self/fd/{}", fd.get()), &real_path)) {
    return ErrnoErrorf("Failed to resolve real path for '{}'", path);
  }
  return path != real_path;
}

Result<void> Unmount(const std::string& target, bool logging = true) {
  if (umount2(target.c_str(), UMOUNT_NOFOLLOW) == 0) {
    LOG_IF(INFO, logging) << ART_FORMAT("Unmounted '{}'", target);
    return {};
  }
  LOG(WARNING) << ART_FORMAT(
      "Failed to umount2 '{}': {}. Retrying with MNT_DETACH", target, strerror(errno));
  if (umount2(target.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) == 0) {
    LOG_IF(INFO, logging) << ART_FORMAT("Unmounted '{}' with MNT_DETACH", target);
    return {};
  }
  return ErrnoErrorf("Failed to umount2 '{}'", target);
}

// Bind-mounts `source` at `target` with the mount propagation type being "shared". You generally
// want to use `BindMount` instead.
//
// `BindMountDirect` is safe to use only if there is no child mount points under `target`. DO NOT
// mount or unmount under `target` because mount events propagate to `source`.
Result<void> BindMountDirect(const std::string& source, const std::string& target) {
  // Don't follow symlinks.
  CHECK(!OR_RETURN(IsSelfOrParentSymlink(target))) << target;
  if (mount(source.c_str(),
            target.c_str(),
            /*fs_type=*/nullptr,
            MS_BIND,
            /*data=*/nullptr) != 0) {
    return ErrnoErrorf("Failed to bind-mount '{}' at '{}'", source, target);
  }
  LOG(INFO) << ART_FORMAT("Bind-mounted '{}' at '{}'", source, target);
  return {};
}

// Bind-mounts `source` at `target` with the mount propagation type being "slave+shared".
// By default, this function rejects `source` in chroot, to avoid accidental repeated bind-mounting.
// If you intentionally want `source` to be in chroot, set `check_source_is_not_in_chroot` to false.
Result<void> BindMount(const std::string& source,
                       const std::string& target,
                       bool check_source_is_not_in_chroot = true) {
  // Don't bind-mount repeatedly.
  if (check_source_is_not_in_chroot) {
    CHECK(!PathStartsWith(source, DexoptChrootSetup::CHROOT_DIR));
  }
  // Don't follow symlinks.
  CHECK(!OR_RETURN(IsSelfOrParentSymlink(target))) << target;
  // system_server has a different mount namespace from init, and it uses slave mounts. E.g:
  //
  //    a: init mount ns: shared(1):          /foo
  //    b: init mount ns: shared(2):          /mnt
  //    c: SS mount ns:   slave(1):           /foo
  //    d: SS mount ns:   slave(2):           /mnt
  //
  // We create our chroot setup in the init namespace but also want it to appear inside the
  // system_server one, since we need to access some files in it from system_server (in particular
  // service-art.jar).
  //
  // Hence we want the mount propagation type to be "slave+shared": Slave of the init namespace so
  // that unmounts in the chroot doesn't affect the rest of the system, while at the same time
  // shared with the system_server namespace so that it gets the same mounts recursively in the
  // chroot tree. This can be achieved in 4 steps:
  //
  // 1. Bind-mount /foo at a temp mount point /mnt/pre_reboot_dexopt/mount_tmp.
  //    a: init mount ns: shared(1):          /foo
  //    b: init mount ns: shared(2):          /mnt
  //    e: init mount ns: shared(1):          /mnt/pre_reboot_dexopt/mount_tmp
  //    c: SS mount ns:   slave(1):           /foo
  //    d: SS mount ns:   slave(2):           /mnt
  //    f: SS mount ns:   slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
  //
  // 2. Make the temp mount point slave.
  //    a: init mount ns: shared(1):          /foo
  //    b: init mount ns: shared(2):          /mnt
  //    e: init mount ns: slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
  //    c: SS mount ns:   slave(1):           /foo
  //    d: SS mount ns:   slave(2):           /mnt
  //    f: SS mount ns:   slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
  //
  // 3. Bind-mount the temp mount point at /mnt/pre_reboot_dexopt/chroot/foo. (The new mount point
  //    gets "slave+shared". It gets "slave" because the source (`e`) is "slave", and it gets
  //    "shared" because the dest (`b`) is "shared".)
  //    a: init mount ns: shared(1):          /foo
  //    b: init mount ns: shared(2):          /mnt
  //    e: init mount ns: slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
  //    g: init mount ns: slave(1),shared(3): /mnt/pre_reboot_dexopt/chroot/foo
  //    b: SS mount ns:   slave(1):           /foo
  //    d: SS mount ns:   slave(2):           /mnt
  //    f: SS mount ns:   slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
  //    h: SS mount ns:   slave(3):           /mnt/pre_reboot_dexopt/chroot/foo
  //
  // 4. Unmount the temp mount point.
  //    a: init mount ns: shared(1):          /foo
  //    b: init mount ns: shared(2):          /mnt
  //    g: init mount ns: slave(1),shared(3): /mnt/pre_reboot_dexopt/chroot/foo
  //    b: SS mount ns:   slave(1):           /foo
  //    d: SS mount ns:   slave(2):           /mnt
  //    h: SS mount ns:   slave(3):           /mnt/pre_reboot_dexopt/chroot/foo
  //
  // At this point, we have achieved what we want. `g` is a slave of `a` so that unmounts in `g`
  // doesn't affect `a`, and `g` is shared with `h` so that mounts in `g` are propagated to `h`.
  OR_RETURN(CreateDir(*kBindMountTmpDir));
  if (mount(source.c_str(),
            kBindMountTmpDir->c_str(),
            /*fs_type=*/nullptr,
            MS_BIND,
            /*data=*/nullptr) != 0) {
    return ErrnoErrorf("Failed to bind-mount '{}' at '{}' ('{}' -> '{}')",
                       source,
                       *kBindMountTmpDir,
                       source,
                       target);
  }
  auto cleanup = make_scope_guard([&]() {
    Result<void> result = Unmount(*kBindMountTmpDir, /*logging=*/false);
    if (!result.ok()) {
      LOG(ERROR) << result.error().message();
    }
  });
  if (mount(/*source=*/nullptr,
            kBindMountTmpDir->c_str(),
            /*fs_type=*/nullptr,
            MS_SLAVE,
            /*data=*/nullptr) != 0) {
    return ErrnoErrorf(
        "Failed to make mount slave for '{}' ('{}' -> '{}')", *kBindMountTmpDir, source, target);
  }
  if (mount(kBindMountTmpDir->c_str(),
            target.c_str(),
            /*fs_type=*/nullptr,
            MS_BIND,
            /*data=*/nullptr) != 0) {
    return ErrnoErrorf("Failed to bind-mount '{}' at '{}' ('{}' -> '{}')",
                       *kBindMountTmpDir,
                       target,
                       source,
                       target);
  }
  LOG(INFO) << ART_FORMAT("Bind-mounted '{}' at '{}'", source, target);
  return {};
}

Result<void> BindMountRecursive(const std::string& source, const std::string& target) {
  CHECK(!source.ends_with('/'));
  OR_RETURN(BindMount(source, target));

  // Mount and make slave one by one. Do not use MS_REC because we don't want to mount a child if
  // the parent cannot be slave (i.e., is shared). Otherwise, unmount events will be undesirably
  // propagated to the source. For example, if "/dev" and "/dev/pts" are mounted at "/chroot/dev"
  // and "/chroot/dev/pts" respectively, and "/chroot/dev" is shared, then unmounting
  // "/chroot/dev/pts" will also unmount "/dev/pts".
  //
  // The list is in mount order.
  std::vector<FstabEntry> entries = OR_RETURN(GetProcMountsDescendantsOfPath(source));
  for (const FstabEntry& entry : entries) {
    CHECK(!entry.mount_point.ends_with('/'));
    std::string_view sub_dir = entry.mount_point;
    CHECK(ConsumePrefix(&sub_dir, source));
    if (sub_dir.empty()) {
      // `source` itself. Already mounted.
      continue;
    }
    if (Result<void> result = BindMount(entry.mount_point, std::string(target).append(sub_dir));
        !result.ok()) {
      // Match paths for the "u:object_r:apk_tmp_file:s0" file context in
      // system/sepolicy/private/file_contexts.
      std::regex apk_tmp_file_re(R"re((/data|/mnt/expand/[^/]+)/app/vmdl[^/]+\.tmp(/.*)?)re");
      if (std::regex_match(entry.mount_point, apk_tmp_file_re)) {
        // Don't bother. The mount point is a temporary directory created by Package Manager during
        // app install. We won't be able to dexopt the app there anyway because it's not in the
        // Package Manager's snapshot.
        LOG(INFO) << ART_FORMAT("Skipped temporary mount point '{}'", entry.mount_point);
        continue;
      }

      std::regex vendor_file_re(R"re(/data/vendor(/.*)?)re");
      if (std::regex_match(entry.mount_point, vendor_file_re)) {
        // We can't reliably bind-mount vendor-specific files because those files can have
        // vendor-specific SELinux file contexts, which by design cannot be referenced by
        // `dexopt_chroot_setup.te`. In practice, we don't need to bind-mount those files because
        // they are unlikely to contain things useful to us.
        LOG(INFO) << ART_FORMAT("Skipped vendor mount point '{}'", entry.mount_point);
        continue;
      }

      return result;
    }
  }
  return {};
}

std::string GetBlockDeviceName(const std::string& partition, const std::string&&nbsp;slot) {
  return ART_FORMAT("/dev/block/mapper/{}{}", partition, slot);
}

Result<std::vector<std::string>> GetSupportedFilesystems() {
  std::string content;
  if (!ReadFileToString("/proc/filesystems", &content)) {
    return ErrnoErrorf("Failed to read '/proc/filesystems'");
  }
  std::vector<std::string> filesystems;
  for (const std::string& line : Split(content, "\n")) {
    std::vector<std::string> tokens = Tokenize(line, " \t");
    // If there are two tokens, the first token is a "nodev" mark, meaning it's not for a block
    // device, so we skip it.
    if (tokens.size() == 1) {
      filesystems.push_back(tokens[0]);
    }
  }
  // Prioritize the filesystems that are known to behave correctly, just in case some bad
  // filesystems are unexpectedly happy to mount volumes that aren't of their types. We have never
  // seen this case in practice though.
  constexpr const char* kWellKnownFilesystems[] = {"erofs""ext4"};
  for (const char* well_known_fs : kWellKnownFilesystems) {
    auto it = std::find(filesystems.begin(), filesystems.end(), well_known_fs);
    if (it != filesystems.end()) {
      filesystems.erase(it);
      filesystems.insert(filesystems.begin(), well_known_fs);
    }
  }
  return filesystems;
}

Result<void> Mount(const std::string& block_device, const std::string& target, bool is_optional) {
  static const NoDestructor<Result<std::vector<std::string>>> supported_filesystems(
      GetSupportedFilesystems());
  if (!supported_filesystems->ok()) {
    return supported_filesystems->error();
  }
  std::vector<std::string> error_msgs;
  for (const std::string& filesystem : supported_filesystems->value()) {
    if (mount(block_device.c_str(),
              target.c_str(),
              filesystem.c_str(),
              MS_RDONLY,
              /*data=*/nullptr) == 0) {
      // Success.
      LOG(INFO) << ART_FORMAT(
          "Mounted '{}' at '{}' with type '{}'", block_device, target, filesystem);
      return {};
    } else {
      if (errno == ENOENT && is_optional) {
        LOG(INFO) << ART_FORMAT("Skipped non-existing block device '{}'", block_device);
        return {};
      }
      error_msgs.push_back(ART_FORMAT("Tried '{}': {}", filesystem, strerror(errno)));
      if (errno != EINVAL && errno != EBUSY) {
        // If the filesystem type is wrong, `errno` must be either `EINVAL` or `EBUSY`. For example,
        // we've seen that trying to mount a device with a wrong filesystem type yields `EBUSY` if
        // the device is also mounted elsewhere, though we can't find any document about this
        // behavior.
        break;
      }
    }
  }
  return Errorf("Failed to mount '{}' at '{}':\n{}", block_device, target, Join(error_msgs, '\n'));
}

Result<void> MountTmpfs(const std::string& target, std::string_view se_context) {
  if (mount(/*source=*/"tmpfs",
            target.c_str(),
            /*fs_type=*/"tmpfs",
            MS_NODEV | MS_NOEXEC | MS_NOSUID,
            ART_FORMAT("mode={:#o},rootcontext={}", kChrootDefaultMode, se_context).c_str()) != 0) {
    return ErrnoErrorf("Failed to mount tmpfs at '{}'", target);
  }
  return {};
}

Result<std::optional<std::string>> LoadOtaSlotFile() {
  std::string content;
  if (!ReadFileToString(*kOtaSlotFile, &content)) {
    return ErrnoErrorf("Failed to read '{}'", *kOtaSlotFile);
  }
  if (content == "_a" || content == "_b") {
    return content;
  }
  if (content.empty()) {
    return std::nullopt;
  }
  return Errorf("Invalid content of '{}': '{}'", *kOtaSlotFile, content);
}

Result<void> PatchLinkerConfigForCompatEnv() {
  std::string art_linker_config_content;
  if (!ReadFileToString(PathInChroot("/linkerconfig/com.android.art/ld.config.txt"),
                        &art_linker_config_content)) {
    return ErrnoErrorf("Failed to read ART linker config");
  }

  std::string compat_section =
      OR_RETURN(ConstructLinkerConfigCompatEnvSection(art_linker_config_content));

  // Append the patched section to the global linker config. Because the compat env path doesn't
  // start with "/apex", the global linker config is the one that takes effect.
  std::string global_linker_config_path = PathInChroot("/linkerconfig/ld.config.txt");
  std::string global_linker_config_content;
  if (!ReadFileToString(global_linker_config_path, &global_linker_config_content)) {
    return ErrnoErrorf("Failed to read global linker config");
  }

  if (!WriteStringToFile("dir.com.android.art.compat = /mnt/compat_env/apex/com.android.art/bin\n" +
                             global_linker_config_content + compat_section,
                         global_linker_config_path)) {
    return ErrnoErrorf("Failed to write global linker config");
  }

  LOG(INFO) << "Patched " << global_linker_config_path;
  return {};
}

// Platform libraries communicate with things outside of chroot through unstable APIs. Examples are
// `libbinder_ndk.so` talking to `servicemanager` and `libcgrouprc.so` reading
// `/dev/cgroup_info/cgroup.rc`. To work around incompatibility issues, we bind-mount the old
// platform library directories into chroot so that both sides of a communication are old and
// therefore align with each other.
// After bind-mounting old platform libraries, the chroot environment has a combination of new
// modules and old platform libraries. We currently use the new linker config in such an
// environment, which is potentially problematic. If we start to see problems, we should consider
// generating a more correct linker config in a more complex way.
Result<void> PrepareExternalLibDirs() {
  std::vector<const char*> existing_lib_dirs;
  std::copy_if(kExternalLibDirs.begin(),
               kExternalLibDirs.end(),
               std::back_inserter(existing_lib_dirs),
               OS::DirectoryExists);
  if (existing_lib_dirs.empty()) {
    return Errorf("Unexpectedly missing platform library directories. Tried '{}'",
                  android::base::Join(kExternalLibDirs, "', '"));
  }

  // We should bind-mount all existing lib dirs or none of them. Try the first one to decide what
  // to do next.
  Result<void> result = BindMount(existing_lib_dirs[0], PathInChroot(existing_lib_dirs[0]));
  if (result.ok()) {
    for (size_t i = 1; i < existing_lib_dirs.size(); ++i) {
      OR_RETURN(BindMount(existing_lib_dirs[i], PathInChroot(existing_lib_dirs[i])));
    }
  } else if (result.error().code() == EACCES) {
    // We don't have the permission to do so on V. Fall back to bind-mounting elsewhere.
    LOG(WARNING) << result.error().message();

    OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env")));
    OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/system")));
    OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/system_ext")));
    OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex")));
    OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex/com.android.art")));
    OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex/com.android.art/bin")));
    OR_RETURN(BindMountDirect(PathInChroot("/apex/com.android.art/bin"),
                              PathInChroot("/mnt/compat_env/apex/com.android.art/bin")));
    for (const char* lib_dir : existing_lib_dirs) {
      OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env") + lib_dir));
      OR_RETURN(BindMountDirect(lib_dir, PathInChroot("/mnt/compat_env") + lib_dir));
    }

    OR_RETURN(PatchLinkerConfigForCompatEnv());
  } else {
    return result;
  }

  // Back up the new classpaths dir before bind-mounting etc dirs. We need the new classpaths dir
  // for derive_classpath.
  std::string classpaths_tmp_dir = PathInChroot("/mnt/classpaths");
  OR_RETURN(CreateDir(classpaths_tmp_dir));
  OR_RETURN(BindMount(PathInChroot("/system/etc/classpaths"),
                      classpaths_tmp_dir,
                      /*check_source_is_not_in_chroot=*/false));

  // Old platform libraries expect old etc dirs, so we should bind-mount them as well.
  OR_RETURN(BindMount("/system/etc", PathInChroot("/system/etc")));
  OR_RETURN(BindMount("/system_ext/etc", PathInChroot("/system_ext/etc")));
  OR_RETURN(BindMount("/product/etc", PathInChroot("/product/etc")));
  result = BindMount("/vendor/etc", PathInChroot("/vendor/etc"));
  if (!result.ok()) {
    if (result.error().code() == EACCES) {
      // We don't have the permission to do so on V. That's fine because the V version of the
      // platform libraries are fine with the B version of /vendor/etc at the time of writing. Even
      // if it's not fine, there is nothing we can do.
      LOG(WARNING) << result.error().message();
    } else {
      return result;
    }
  }

  // Restore the classpaths dir.
  OR_RETURN(BindMount(classpaths_tmp_dir,
                      PathInChroot("/system/etc/classpaths"),
                      /*check_source_is_not_in_chroot=*/false));
  OR_RETURN(Unmount(classpaths_tmp_dir));

  // Prepare a clean view of the new system partition for odrefresh, disregarding the /system/etc
  // overrides set up above, to make sure it gets the new boot image profile and other files in
  // /system/etc.
  OR_RETURN(CreateDir(PathInChroot("/mnt/new_system")));
  OR_RETURN(BindMount(PathInChroot("/system"),
                      PathInChroot("/mnt/new_system"),
                      /*check_source_is_not_in_chroot=*/false));

  return {};
}

}  // namespace

ScopedAStatus DexoptChrootSetup::setUp(const std::optional<std::string>& in_otaSlot,
                                       bool in_mapSnapshotsForOta) {
  if (!mu_.try_lock()) {
    return Fatal("Unexpected concurrent calls");
  }
  std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);

  if (in_otaSlot.has_value() && (in_otaSlot.value() != "_a" && in_otaSlot.value() != "_b")) {
    return Fatal(ART_FORMAT("Invalid OTA slot '{}'", in_otaSlot.value()));
  }
  OR_RETURN_NON_FATAL(SetUpChroot(in_otaSlot, in_mapSnapshotsForOta));
  return ScopedAStatus::ok();
}

ScopedAStatus DexoptChrootSetup::init() {
  if (!mu_.try_lock()) {
    return Fatal("Unexpected concurrent calls");
  }
  std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);

  if (OS::FileExists(PathInChroot("/linkerconfig/ld.config.txt").c_str())) {
    return Fatal("init must not be repeatedly called");
  }

  OR_RETURN_NON_FATAL(InitChroot());
  return ScopedAStatus::ok();
}

ScopedAStatus DexoptChrootSetup::tearDown(bool in_allowConcurrent) {
  if (in_allowConcurrent) {
    // Normally, we don't expect concurrent calls, but this method may be called upon system server
    // restart when another call initiated by the previous system_server instance is still being
    // processed.
    mu_.lock();
  } else {
    if (!mu_.try_lock()) {
      return Fatal("Unexpected concurrent calls");
    }
  }
  std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);

  OR_RETURN_NON_FATAL(TearDownChroot());
  return ScopedAStatus::ok();
}

Result<void> DexoptChrootSetup::Start() {
  ScopedAStatus status = ScopedAStatus::fromStatus(
      AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
  if (!status.isOk()) {
    return Error() << status.getDescription();
  }

  ABinderProcess_startThreadPool();

  return {};
}

Result<void> DexoptChrootSetup::SetUpChroot(const std::optional<std::string>& ota_slot,
                                            bool map_snapshots_for_ota) const {
  // Set the default permission mode for new files and dirs to be `kChrootDefaultMode`.
  umask(~kChrootDefaultMode & 0777);

  // In case there is some leftover.
  OR_RETURN(TearDownChroot());

  // Prepare the root dir of chroot. The parent directory has been created by init (see `init.rc`).
  OR_RETURN(CreateDir(CHROOT_DIR));
  LOG(INFO) << ART_FORMAT("Created '{}'", CHROOT_DIR);

  std::vector<std::tuple<std::string, std::string>> additional_system_partitions = {
      {"system_ext""/system_ext"},
      {"vendor""/vendor"},
      {"product""/product"},
      {"odm""/odm"},
  };

  std::string partitions_from_sysprop =
      GetProperty(kAdditionalPartitionsSysprop, /*default_value=*/"");
  std::vector<std::string_view> partitions_from_sysprop_entries;
  art::Split(partitions_from_sysprop, ',', &partitions_from_sysprop_entries);
  for (std::string_view entry : partitions_from_sysprop_entries) {
    std::vector<std::string_view> pair;
    art::Split(entry, ':', &pair);
    if (pair.size() != 2 || pair[0].empty() || pair[1].empty() || !pair[1].starts_with('/')) {
      return Errorf("Malformed entry in '{}': '{}'", kAdditionalPartitionsSysprop, entry);
    }
    additional_system_partitions.emplace_back(std::string(pair[0]), std::string(pair[1]));
  }

  if (!IsOtaUpdate(ota_slot)) {  // Mainline update
    OR_RETURN(BindMount("/", CHROOT_DIR));
    // Normally, we don't need to bind-mount "/system" because it's a part of the image mounted at
    // "/". However, when readonly partitions are remounted read-write, an overlay is created at
    // "/system", so we need to bind-mount "/system" to handle this case. On devices where readonly
    // partitions are not remounted, bind-mounting "/system" doesn't hurt.
    OR_RETURN(BindMount("/system", PathInChroot("/system")));
    for (const auto& [partition, mount_point] : additional_system_partitions) {
      // Some additional partitions are optional. On a device where an additional partition doesn't
      // exist, the mount point of the partition is a symlink to a directory inside /system.
      if (!OR_RETURN(IsSymlink(mount_point))) {
        OR_RETURN(BindMount(mount_point, PathInChroot(mount_point)));
      }
    }
  } else {
    CHECK(ota_slot.value() == "_a" || ota_slot.value() == "_b");

    if (map_snapshots_for_ota) {
      // Write the file early in case `snapshotctl map` fails in the middle, leaving some devices
      // mapped. We don't assume that `snapshotctl map` is transactional.
      if (!WriteStringToFile("", *kSnapshotMappedFile)) {
        return ErrnoErrorf("Failed to write '{}'", *kSnapshotMappedFile);
      }

      // Run `snapshotctl map` through init to map block devices. We can't run it ourselves because
      // it requires the UID to be 0. See `sys.snapshotctl.map` in `init.rc`.
      if (!SetProperty("sys.snapshotctl.map""requested")) {
        return Errorf("Failed to request snapshotctl map");
      }
      if (!WaitForProperty("sys.snapshotctl.map""finished", kSnapshotCtlTimeout)) {
        return Errorf("snapshotctl timed out");
      }

      // We don't know whether snapshotctl succeeded or not, but if it failed, the mount operation
      // below will fail with `ENOENT`.
      OR_RETURN(
          Mount(GetBlockDeviceName("system", ota_slot.value()), CHROOT_DIR, /*is_optional=*/false));
    } else {
      // update_engine has mounted `system` at `/postinstall` for us.
      OR_RETURN(BindMount("/postinstall", CHROOT_DIR));
    }

    for (const auto& [partition, mount_point] : additional_system_partitions) {
      OR_RETURN(Mount(GetBlockDeviceName(partition, ota_slot.value()),
                      PathInChroot(mount_point),
                      /*is_optional=*/true));
    }
  }

  OR_RETURN(MountTmpfs(PathInChroot("/apex"), "u:object_r:apex_mnt_dir:s0"));
  OR_RETURN(MountTmpfs(PathInChroot("/linkerconfig"), "u:object_r:linkerconfig_file:s0"));
  OR_RETURN(MountTmpfs(PathInChroot("/mnt"), "u:object_r:pre_reboot_dexopt_file:s0"));
  OR_RETURN(CreateDir(PathInChroot("/mnt/artd_tmp")));
  OR_RETURN(MountTmpfs(PathInChroot("/mnt/artd_tmp"), "u:object_r:pre_reboot_dexopt_artd_file:s0"));
  OR_RETURN(CreateDir(PathInChroot("/mnt/expand")));

  std::vector<std::string> bind_mount_srcs = {
      // Data partitions.
      "/data",
      "/mnt/expand",
      // Linux API filesystems.
      "/dev",
      "/proc",
      "/sys",
      // For apexd to query staged APEX sessions.
      "/metadata",
  };

  for (const std::string& src : bind_mount_srcs) {
    OR_RETURN(BindMountRecursive(src, PathInChroot(src)));
  }

  if (!WriteStringToFile(ota_slot.value_or(""), *kOtaSlotFile)) {
    return ErrnoErrorf("Failed to write '{}'", *kOtaSlotFile);
  }

  return {};
}

Result<void> DexoptChrootSetup::InitChroot() const {
  std::optional<std::string> ota_slot = OR_RETURN(LoadOtaSlotFile());

  // Generate empty linker config to suppress warnings.
  if (!android::base::WriteStringToFile("", PathInChroot("/linkerconfig/ld.config.txt"))) {
    PLOG(WARNING) << "Failed to generate empty linker config to suppress warnings";
  }

  CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder());
  args.Add("--")
      .Add("/system/bin/apexd")
      .Add("--otachroot-bootstrap")
      .AddIf(!IsOtaUpdate(ota_slot), "--also-include-staged-apexes");
  OR_RETURN(Run("apexd", args.Get()));

  args = OR_RETURN(GetArtExecCmdlineBuilder());
  args.Add("--drop-capabilities")
      .Add("--")
      .Add("/apex/com.android.runtime/bin/linkerconfig")
      .Add("--target")
      .Add("/linkerconfig");
  OR_RETURN(Run("linkerconfig", args.Get()));

  if (IsOtaUpdate(ota_slot)) {
    OR_RETURN(PrepareExternalLibDirs());
  }

  return {};
}

Result<void> DexoptChrootSetup::TearDownChroot() const {
  // For platform library dirs and etc dirs, make sure we have unmounted them before running apexd,
  // as apexd expects new libraries (and probably new etc dirs).
  // For mount points under "/mnt/compat_env", make sure we have unmounted them before running
  // apexd, as apexd doesn't expect apexes to be in-use.
  // The list is in mount order.
  std::vector<FstabEntry> entries = OR_RETURN(GetProcMountsDescendantsOfPath(CHROOT_DIR));
  for (auto it = entries.rbegin(); it != entries.rend(); ++it) {
    const FstabEntry& entry = *it;
    std::string_view mount_point_in_chroot = entry.mount_point;
    CHECK(ConsumePrefix(&mount_point_in_chroot, CHROOT_DIR));
    if (mount_point_in_chroot.empty()) {
      continue;  // The root mount.
    }
    if (ContainsElement(kExternalLibDirs, mount_point_in_chroot) ||
        PathStartsWith(mount_point_in_chroot, "/mnt/compat_env") ||
        ContainsElement({"/system/etc",
                         "/system_ext/etc",
                         "/product/etc",
                         "/vendor/etc",
                         "/system/etc/classpaths",
                         "/mnt/classpaths",
                         "/mnt/new_system"},
                        mount_point_in_chroot)) {
      OR_RETURN(Unmount(entry.mount_point));
    }
  }

  std::vector<FstabEntry> apex_entries =
      OR_RETURN(GetProcMountsDescendantsOfPath(PathInChroot("/apex")));
  // If there is only one entry, it's /apex itself.
  bool has_apex = apex_entries.size() > 1;

  if (has_apex && OS::FileExists(PathInChroot("/system/bin/apexd").c_str())) {
    // Delegate to apexd to unmount all APEXes. It also cleans up loop devices.
    CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder());
    args.Add("--")
        .Add("/system/bin/apexd")
        .Add("--unmount-all")
        .Add("--also-include-staged-apexes");
    OR_RETURN(Run("apexd", args.Get()));
  }

  // Double check to make sure all APEXes are unmounted, just in case apexd incorrectly reported
  // success.
  apex_entries = OR_RETURN(GetProcMountsDescendantsOfPath(PathInChroot("/apex")));
  for (const FstabEntry& entry : apex_entries) {
    if (entry.mount_point != PathInChroot("/apex")) {
      return Errorf("apexd didn't unmount '{}'. See logs for details", entry.mount_point);
    }
  }

  // The list is in mount order.
  entries = OR_RETURN(GetProcMountsDescendantsOfPath(CHROOT_DIR));
  for (auto it = entries.rbegin(); it != entries.rend(); ++it) {
    OR_RETURN(Unmount(it->mount_point));
  }

  std::error_code ec;
  std::uintmax_t removed = std::filesystem::remove_all(CHROOT_DIR, ec);
  if (ec) {
    return Errorf("Failed to remove dir '{}': {}", CHROOT_DIR, ec.message());
  }
  if (removed > 0) {
    LOG(INFO) << ART_FORMAT("Removed '{}'", CHROOT_DIR);
  }

  if (!OR_RETURN(GetProcMountsDescendantsOfPath(*kBindMountTmpDir)).empty()) {
    OR_RETURN(Unmount(*kBindMountTmpDir));
  }

  std::filesystem::remove_all(*kBindMountTmpDir, ec);
  if (ec) {
    return Errorf("Failed to remove dir '{}': {}", *kBindMountTmpDir, ec.message());
  }

  std::filesystem::remove(*kOtaSlotFile, ec);
  if (ec) {
    return Errorf("Failed to remove file '{}': {}", *kOtaSlotFile, ec.message());
  }

  if (OS::FileExists(kSnapshotMappedFile->c_str())) {
    if (!SetProperty("sys.snapshotctl.unmap""requested")) {
      return Errorf("Failed to request snapshotctl unmap");
    }
    if (!WaitForProperty("sys.snapshotctl.unmap""finished", kSnapshotCtlTimeout)) {
      return Errorf("snapshotctl timed out");
    }
    std::filesystem::remove(*kSnapshotMappedFile, ec);
    if (ec) {
      return Errorf("Failed to remove file '{}': {}", *kSnapshotMappedFile, ec.message());
    }
  }

  return {};
}

std::string PathInChroot(std::string_view path) {
  return std::string(DexoptChrootSetup::CHROOT_DIR).append(path);
}

Result<std::string> ConstructLinkerConfigCompatEnvSection(
    const std::string& art_linker_config_content) {
  std::regex system_lib_re(R"re((=\s*|:)/(system(?:_ext)?/\$\{LIB\}))re");
  constexpr const char* kSystemLibFmt = "$1/mnt/compat_env/$2";

  // Make a copy of the [com.android.art] section and patch particular lines.
  std::string compat_section;
  bool is_in_art_section = false;
  bool replaced = false;
  std::vector<std::string_view> art_linker_config_lines;
  art::Split(art_linker_config_content, '\n', &art_linker_config_lines);
  for (std::string_view line : art_linker_config_lines) {
    if (!is_in_art_section && line == "[com.android.art]") {
      is_in_art_section = true;
    } else if (is_in_art_section && line.starts_with('[')) {
      is_in_art_section = false;
    }

    if (is_in_art_section) {
      if (line == "[com.android.art]") {
        compat_section += "[com.android.art.compat]\n";
      } else {
        std::string patched_line =
            std::regex_replace(std::string(line), system_lib_re, kSystemLibFmt);
        if (line != patched_line) {
          LOG(DEBUG) << ART_FORMAT("Replacing '{}' with '{}'", line, patched_line);
          replaced = true;
        }
        compat_section += patched_line;
        compat_section += '\n';
      }
    }
  }
  if (!replaced) {
    return Errorf("No matching lines to patch in ART linker config");
  }
  return compat_section;
}

}  // namespace dexopt_chroot_setup
}  // namespace art

Messung V0.5 in Prozent
C=89 H=96 G=92

¤ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet am  2026-06-29) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik