text: // Copyright 2021 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <string>
  #include <vector>
  
  #include <base/files/file.h>
  #include <base/files/file_enumerator.h>
  #include <base/files/file_path.h>
  #include <base/files/file_util.h>
  #include <base/logging.h>
  #include <base/posix/eintr_wrapper.h>
  #include <brillo/file_utils.h>
  #include <brillo/process/process.h>
  #include <brillo/userdb_utils.h>
  
  #include "tpm2-simulator/constants.h"
  #include "tpm2-simulator/tpm_nvchip_utils.h"
  
  namespace {
  
  constexpr char kNVChipPath[] = "NVChip";
  constexpr size_t kNVChipSize = 1024 * 1024;  // 1MB.
  constexpr char kWoringDirectory[] = ".";
  
  // The old NVChip size that need to migrate to new format.
  constexpr size_t kOldNVChipSize = 16384;
  
  struct NVChipMigrateData {
    std::string chip_data;
  };
  
  bool Tune2Fs(const base::FilePath& file, const std::vector<std::string>& opts) {
    brillo::ProcessImpl tune_process;
    tune_process.AddArg("/sbin/tune2fs");
    for (const auto& arg : opts)
      tune_process.AddArg(arg);
  
    tune_process.AddArg(file.value());
  
    // Close unused file descriptors in child process.
    tune_process.SetCloseUnusedFileDescriptors(true);
  
    // Avoid polluting the parent process' stdout.
    tune_process.RedirectOutput("/dev/null");
  
    int rc = tune_process.Run();
    if (rc != 0) {
      LOG(ERROR) << "Can't tune ext4: " << file.value() << ", error: " << rc;
      return false;
    }
    return true;
  }
  
  bool FormatExt4(const base::FilePath& file) {
    brillo::ProcessImpl format_process;
    format_process.AddArg("/sbin/mkfs.ext4");
  
    format_process.AddArg(file.value());
  
    // No need to emit output.
    format_process.AddArg("-q");
  
    // Close unused file descriptors in child process.
    format_process.SetCloseUnusedFileDescriptors(true);
  
    // Avoid polluting the parent process' stdout.
    format_process.RedirectOutput("/dev/null");
  
    int rc = format_process.Run();
    if (rc != 0) {
      LOG(ERROR) << "Can't format '" << file.value()
                 << "' as ext4, exit status: " << rc;
      return false;
    }
  
    // Tune the formatted filesystem:
    // -c 0: Disable max mount count checking.
    // -i 0: Disable filesystem checking.
    return Tune2Fs(file, {"-c", "0", "-i", "0"});
  }
  
  bool MountLoopbackFile(const base::FilePath& file,
                         const base::FilePath& mount_point) {
    brillo::ProcessImpl mount_process;
    mount_process.AddArg("/bin/mount");
  
    mount_process.AddArg("-o");
    mount_process.AddArg("loop");
  
    mount_process.AddArg(file.value());
    mount_process.AddArg(mount_point.value());
  
    // Close unused file descriptors in child process.
    mount_process.SetCloseUnusedFileDescriptors(true);
  
    // Avoid polluting the parent process' stdout.
    mount_process.RedirectOutput("/dev/null");
  
    int rc = mount_process.Run();
    if (rc != 0) {
      LOG(ERROR) << "Can't mount '" << file.value() << "' to '"
                 << mount_point.value() << "', exit status: " << rc;
      return false;
    }
    return true;
  }
  
  bool ChownDirectoryContents(const base::FilePath& dir, uid_t uid, gid_t gid) {
    base::FileEnumerator ent_enum(dir, false, base::FileEnumerator::FILES);
    for (base::FilePath path = ent_enum.Next(); !path.empty();
         path = ent_enum.Next()) {
      if (HANDLE_EINTR(chown(path.value().c_str(), uid, gid)) < 0) {
        PLOG(ERROR) << "Failed to chown " << path.value();
        return false;
      }
    }
    return true;
  }
  
  bool PrepareMigrateNVChip(const base::FilePath& chip_path,
                            int64_t chip_size,
                            NVChipMigrateData* migrate_data) {
    if (chip_size == kOldNVChipSize) {
      if (!base::ReadFileToString(chip_path, &migrate_data->chip_data)) {
        LOG(ERROR) << "Failed to read the NVChip.";
        return false;
      }
      if (migrate_data->chip_data.size() != kOldNVChipSize) {
        LOG(ERROR) << "Unknown NVChip size after read.";
        return false;
      }
      if (!brillo::WriteStringToFile(chip_path, std::string(kNVChipSize, '\0'))) {
        LOG(ERROR) << "Failed to create the NVChip.";
        return false;
      }
      if (!FormatExt4(chip_path)) {
        LOG(ERROR) << "Failed to format the NVChip to ext4.";
        return false;
      }
      return true;
    } else {
      LOG(ERROR) << "Unknown NVChip size.";
      return false;
    }
  }
  
  bool MigrateNVChip(const base::FilePath& chip_path,
                     const base::FilePath& mount_point,
                     const NVChipMigrateData& migrate_data) {
    base::FilePath new_chip_path = mount_point.Append(kNVChipPath);
    if (!brillo::WriteStringToFile(new_chip_path, migrate_data.chip_data)) {
      LOG(ERROR) << "Failed to create the NVChip.";
      return false;
    }
    return true;
  }
  
  }  // namespace
  
  namespace tpm2_simulator {
  
  bool MountAndEnterNVChip() {
    base::FilePath chip_path(kNVChipPath);
    base::FilePath mount_point(kNVChipMountPoint);
    if (!base::PathExists(chip_path)) {
      if (!brillo::WriteStringToFile(chip_path, std::string(kNVChipSize, '\0'))) {
        LOG(ERROR) << "Failed to create the NVChip.";
        return false;
      }
      if (!FormatExt4(chip_path)) {
        LOG(ERROR) << "Failed to format the NVChip to ext4.";
        return false;
      }
    }
  
    int64_t chip_size = 0;
    if (!base::GetFileSize(chip_path, &chip_size)) {
      LOG(ERROR) << "Failed to get NVChip size.";
      return false;
    }
  
    bool migrate_nvchip = chip_size != kNVChipSize;
    NVChipMigrateData migrate_data;
  
    if (migrate_nvchip) {
      if (!PrepareMigrateNVChip(chip_path, chip_size, &migrate_data)) {
        LOG(ERROR) << "Failed to prepare migrate NVChip.";
        return false;
      }
    }
  
    if (!base::PathExists(mount_point)) {
      if (!base::CreateDirectory(mount_point)) {
        LOG(ERROR) << "Failed to create the NVChip mount point.";
        return false;
      }
    }
  
    uid_t uid;
    gid_t gid;
    if (!brillo::userdb::GetUserInfo(kSimulatorUser, &uid, &gid)) {
      LOG(ERROR) << "Failed to lookup the user name.";
      return false;
    }
    if (HANDLE_EINTR(chown(chip_path.value().c_str(), uid, gid)) < 0) {
      PLOG(ERROR) << "Failed to chown the NVChip.";
      return false;
    }
    if (HANDLE_EINTR(chown(mount_point.value().c_str(), uid, gid)) < 0) {
      PLOG(ERROR) << "Failed to chown the NVChip mount point.";
      return false;
    }
  
    if (HANDLE_EINTR(unshare(CLONE_NEWNS)) < 0) {
      PLOG(ERROR) << "Failed to unshare.";
      return false;
    }
  
    if (!MountLoopbackFile(chip_path, mount_point)) {
      LOG(ERROR) << "Failed to mount the NVChip.";
      return false;
    }
  
    if (HANDLE_EINTR(chown(mount_point.value().c_str(), uid, gid)) < 0) {
      PLOG(ERROR) << "Failed to chown the NVChip mount point after mount.";
      return false;
    }
  
    if (migrate_nvchip) {
      if (!MigrateNVChip(chip_path, mount_point, migrate_data)) {
        LOG(ERROR) << "Failed to migrate NVChip.";
        return false;
      }
    }
  
    if (HANDLE_EINTR(chdir(mount_point.value().c_str())) < 0) {
      PLOG(ERROR) << "Failed to enter the mount point.";
      return false;
    }
  
    return true;
  }
  
  bool CorrectWorkingDirectoryFilesOwner() {
    uid_t uid;
    gid_t gid;
    if (!brillo::userdb::GetUserInfo(kSimulatorUser, &uid, &gid)) {
      LOG(ERROR) << "Failed to lookup the user name.";
      return false;
    }
  
    if (!ChownDirectoryContents(base::FilePath(kWoringDirectory), uid, gid)) {
      LOG(ERROR)
          << "Failed to chown the NVChip mount point contents after mount.";
      return false;
    }
  
    return true;
  }
  
  }  // namespace tpm2_simulator
  