text: // Copyright 2020 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 "cros-disks/archive_mounter.h"
  
  #include <utility>
  
  #include <base/strings/stringprintf.h>
  #include <base/strings/string_util.h>
  #include <brillo/scoped_mount_namespace.h>
  
  #include "cros-disks/quote.h"
  
  namespace cros_disks {
  
  namespace {
  constexpr char kOptionPassword[] = "password";
  }  // namespace
  
  ArchiveMounter::ArchiveMounter(
      const Platform* platform,
      brillo::ProcessReaper* process_reaper,
      std::string archive_type,
      Metrics* metrics,
      std::string metrics_name,
      std::vector<int> password_needed_exit_codes,
      std::unique_ptr<SandboxedProcessFactory> sandbox_factory)
      : FUSEMounter(
            platform, process_reaper, archive_type + "fs", {.read_only = true}),
        archive_type_(archive_type),
        extension_("." + archive_type),
        metrics_(metrics),
        metrics_name_(std::move(metrics_name)),
        password_needed_exit_codes_(std::move(password_needed_exit_codes)),
        sandbox_factory_(std::move(sandbox_factory)) {}
  
  ArchiveMounter::~ArchiveMounter() = default;
  
  bool ArchiveMounter::CanMount(const std::string& source,
                                const std::vector<std::string>& /*params*/,
                                base::FilePath* suggested_dir_name) const {
    base::FilePath path(source);
    if (path.IsAbsolute() &&
        base::CompareCaseInsensitiveASCII(path.Extension(), extension_) == 0) {
      *suggested_dir_name = path.BaseName();
      return true;
    }
    return false;
  }
  
  MountErrorType ArchiveMounter::InterpretReturnCode(int return_code) const {
    if (metrics_ && !metrics_name_.empty())
      metrics_->RecordFuseMounterErrorCode(metrics_name_, return_code);
  
    if (base::Contains(password_needed_exit_codes_, return_code))
      return MOUNT_ERROR_NEED_PASSWORD;
    return FUSEMounter::InterpretReturnCode(return_code);
  }
  
  std::unique_ptr<SandboxedProcess> ArchiveMounter::PrepareSandbox(
      const std::string& source,
      const base::FilePath& /*target_path*/,
      std::vector<std::string> params,
      MountErrorType* error) const {
    metrics_->RecordArchiveType(archive_type_);
  
    base::FilePath path(source);
    if (!path.IsAbsolute() || path.ReferencesParent()) {
      LOG(ERROR) << "Invalid archive path " << quote(path);
      *error = MOUNT_ERROR_INVALID_ARGUMENT;
      return nullptr;
    }
  
    auto sandbox = sandbox_factory_->CreateSandboxedProcess();
  
    std::unique_ptr<brillo::ScopedMountNamespace> mount_ns;
    if (!platform()->PathExists(path.value())) {
      // Try to locate the file in Chrome's mount namespace.
      mount_ns = brillo::ScopedMountNamespace::CreateFromPath(
          base::FilePath(kChromeNamespace));
      if (!mount_ns) {
        PLOG(ERROR) << "Could not look for archive " << quote(path)
                    << " in the Chrome's namespace";
        // TODO(dats): These probably should be MOUNT_ERROR_INVALID_DEVICE_PATH or
        //             something like that, but tast tests expect
        //             MOUNT_ERROR_MOUNT_PROGRAM_FAILED.
        *error = MOUNT_ERROR_MOUNT_PROGRAM_FAILED;
        return nullptr;
      }
      if (!platform()->PathExists(path.value())) {
        PLOG(ERROR) << "Could not find archive " << quote(path);
        *error = MOUNT_ERROR_MOUNT_PROGRAM_FAILED;
        return nullptr;
      }
    }
  
    // Archives are typically under /home, /media or /run. To bind-mount the
    // source those directories must be writable, but by default only /run is.
    for (const char* const dir : {"/home", "/media"}) {
      if (!sandbox->Mount("tmpfs", dir, "tmpfs", "mode=0755,size=1M")) {
        LOG(ERROR) << "Cannot mount " << quote(dir);
        *error = MOUNT_ERROR_INTERNAL;
        return nullptr;
      }
    }
  
    // Is the process "password-aware"?
    if (!password_needed_exit_codes_.empty()) {
      std::string password;
      if (GetParamValue(params, kOptionPassword, &password)) {
        sandbox->SetStdIn(password);
      }
    }
  
    *error = FormatInvocationCommand(path, std::move(params), sandbox.get());
    if (*error != MOUNT_ERROR_NONE) {
      return nullptr;
    }
  
    if (mount_ns) {
      // Sandbox will need to enter Chrome's namespace too to access files.
      mount_ns.reset();
      sandbox->EnterExistingMountNamespace(kChromeNamespace);
    }
  
    return sandbox;
  }
  
  MountErrorType ArchiveMounter::FormatInvocationCommand(
      const base::FilePath& archive,
      std::vector<std::string> /*params*/,
      SandboxedProcess* sandbox) const {
    // Make the source available in the sandbox.
    if (!sandbox->BindMount(archive.value(), archive.value(),
                            /* writeable= */ false,
                            /* recursive= */ false)) {
      LOG(ERROR) << "Cannot bind the source archive " << quote(archive);
      return MOUNT_ERROR_INTERNAL;
    }
  
    std::vector<std::string> opts = {
        MountOptions::kOptionReadOnly, "umask=0222",
        base::StringPrintf("uid=%d", kChronosUID),
        base::StringPrintf("gid=%d", kChronosAccessGID)};
  
    sandbox->AddArgument("-o");
    sandbox->AddArgument(base::JoinString(opts, ","));
    sandbox->AddArgument(archive.value());
  
    return MOUNT_ERROR_NONE;
  }
  
  }  // namespace cros_disks
  