910e62b5创建于 1月15日历史提交
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/updater/linux/systemd_util.h"

#include <fcntl.h>
#include <sys/un.h>
#include <systemd/sd-daemon.h>
#include <unistd.h>

#include <optional>
#include <utility>

#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/linux/ipc_constants.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/util/posix_util.h"

namespace updater {
// Allows the utility functions below to join processes. To avoid overzealously
// granting access to |base::ScopedAllowBaseSyncPrimitives|, this class must
// continue to live in a `.cc`.
class [[maybe_unused, nodiscard]] SystemctlLauncherScopedAllowBaseSyncPrimitives
    : public base::ScopedAllowBaseSyncPrimitives {};

namespace {
// Location of system-scoped unit files.
const base::FilePath kSystemUnitDirectory("/etc/systemd/system");
// Location of user-scoped unit files relative to the user's home directory.
const base::FilePath kUserUnitRelativeDirectory(".local/share/systemd/user");
// Systemd unit names.
constexpr char kUpdaterServiceName[] = PRODUCT_FULLNAME_STRING ".service";
constexpr char kUpdaterSocketName[] = PRODUCT_FULLNAME_STRING ".socket";
// Systemd unit definition templates.
constexpr char kUpdaterServiceDefinitionTemplate[] =
    "[Service]\n"
    "ExecStart=%s\n"
    "KillMode=process";  // Ensure systemd does not kill child processes when
                         // the main process exits.
constexpr char kUpdaterSocketDefinitionTemplate[] =
    "[Socket]\n"
    "ListenStream=%s\n"
    "\n"
    "[Install]\n"
    "WantedBy=sockets.target";

// Returns the path to the systemd unit directory for the given scope.
std::optional<base::FilePath> GetUnitDirectory(UpdaterScope scope) {
  base::FilePath unit_dir;
  switch (scope) {
    case UpdaterScope::kUser:
      if (!base::PathService::Get(base::DIR_HOME, &unit_dir)) {
        return std::nullopt;
      }
      unit_dir = unit_dir.Append(kUserUnitRelativeDirectory);

      if (!base::CreateDirectory(unit_dir)) {
        return std::nullopt;
      }
      break;
    case UpdaterScope::kSystem:
      unit_dir = base::FilePath(kSystemUnitDirectory);
  }
  return unit_dir;
}

// Returns the command `systemctl` with or without the `--user` flag.
base::CommandLine GetBaseSystemctlCommand(UpdaterScope scope) {
  base::CommandLine command({"systemctl"});
  if (scope == UpdaterScope::kUser) {
    command.AppendSwitch("user");
  }
  return command;
}

// Launch the given command line with stdin, stdout, and stderr remapped to
// /dev/null.
void LaunchWithRemaps(base::CommandLine command) {
  base::LaunchOptions options;

  base::ScopedFD null_fd(HANDLE_EINTR(open("/dev/null", O_RDWR)));
  if (null_fd.is_valid()) {
    options.fds_to_remap.emplace_back(null_fd.get(), STDIN_FILENO);
  }

  base::Process proc = base::LaunchProcess(command, options);
  if (!proc.IsValid()) {
    VLOG(1) << "Could not launch " << command.GetCommandLineString();
  } else {
    SystemctlLauncherScopedAllowBaseSyncPrimitives allow_wait;
    proc.WaitForExit(nullptr);
  }
}

// Runs the daemon-reload command to have systemd reload unit files.
void ReloadUnitFiles(UpdaterScope scope) {
  base::CommandLine command = GetBaseSystemctlCommand(scope);
  command.AppendArg("daemon-reload");
  LaunchWithRemaps(command);
}

// Enables the socket unit to be created and managed by systemd at boot.
void EnableSocketUnit(UpdaterScope scope) {
  base::CommandLine command = GetBaseSystemctlCommand(scope);
  command.AppendArg("enable");
  command.AppendSwitch("now");
  command.AppendArg(kUpdaterSocketName);

  LaunchWithRemaps(command);
}

// Ensures that the update service and socket are stopped.
void StopService(UpdaterScope scope) {
  base::CommandLine command = GetBaseSystemctlCommand(scope);
  command.AppendArg("stop");
  command.AppendArg(kUpdaterServiceName);
  command.AppendArg(kUpdaterSocketName);
  LaunchWithRemaps(command);
}

// Writes the contents of |unit_definition| to |unit_path|. Returns true on
// success.
[[nodiscard]] bool InstallSystemdUnit(base::FilePath unit_path,
                                      std::string unit_definition) {
  base::File unit_file(unit_path,
                       base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS);
  return unit_file.IsValid() &&
         unit_file.WriteAndCheck(0, base::as_byte_span(unit_definition));
}

// Returns the command line which should be used to start the updater service.
std::string GetLauncherCommandLine(UpdaterScope scope,
                                   base::FilePath launcher) {
  base::CommandLine command(launcher);
  command.AppendSwitch(kServerSwitch);
  command.AppendSwitchUTF8(kServerServiceSwitch,
                           kServerUpdateServiceSwitchValue);
  if (scope == UpdaterScope::kSystem) {
    command.AppendSwitch(kSystemSwitch);
  }
  return command.GetCommandLineString();
}

}  // namespace

SystemdService::SystemdService() {
  if (sd_listen_fds(0) == 1) {
    server_socket_ = base::ScopedFD(SD_LISTEN_FDS_START + 0);
    read_watcher_controller_ = base::FileDescriptorWatcher::WatchReadable(
        server_socket_.get(),
        base::BindRepeating(&SystemdService::OnSocketReadable,
                            weak_factory_.GetWeakPtr()));
  }
}

SystemdService::~SystemdService() = default;

void SystemdService::OnSocketReadable() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(server_socket_.is_valid());

  base::ScopedFD remote_fd(accept(server_socket_.get(), nullptr, nullptr));
  if (!remote_fd.is_valid()) {
    VPLOG(1) << "Failed to accept connection on activation socket.";
  }
}

bool InstallSystemdUnits(UpdaterScope scope) {
  std::optional<base::FilePath> launcher_path =
      GetUpdateServiceLauncherPath(scope);
  std::optional<base::FilePath> unit_dir = GetUnitDirectory(scope);
  if (!launcher_path || !unit_dir) {
    return false;
  }

  // Uninstall existing units if they exist.
  UninstallSystemdUnits(scope);

  if (!InstallSystemdUnit(
          unit_dir->Append(kUpdaterServiceName),
          base::StringPrintf(
              kUpdaterServiceDefinitionTemplate,
              GetLauncherCommandLine(scope, *launcher_path).c_str())) ||
      !InstallSystemdUnit(
          unit_dir->Append(kUpdaterSocketName),
          base::StringPrintf(
              kUpdaterSocketDefinitionTemplate,
              GetActivationSocketPath(scope).AsUTF8Unsafe().c_str()))) {
    // Avoid a partial installation.
    UninstallSystemdUnits(scope);
    return false;
  }

  ReloadUnitFiles(scope);
  EnableSocketUnit(scope);
  LOG_IF(ERROR, !base::PathExists(GetActivationSocketPath(scope)))
      << "Activation socket file is missing post-install.";
  return true;
}

bool UninstallSystemdUnits(UpdaterScope scope) {
  std::optional<base::FilePath> unit_dir = GetUnitDirectory(scope);
  if (!unit_dir) {
    return false;
  }

  StopService(scope);

  // Note: It is considered successful to attempt to delete units that do not
  // exist.
  bool success = base::DeleteFile(unit_dir->Append(kUpdaterServiceName)) &&
                 base::DeleteFile(unit_dir->Append(kUpdaterSocketName));
  ReloadUnitFiles(scope);
  return success;
}

bool SystemdUnitsInstalled(UpdaterScope scope) {
  std::optional<base::FilePath> unit_dir = GetUnitDirectory(scope);
  if (!unit_dir) {
    return false;
  }

  return base::PathExists(unit_dir->Append(kUpdaterServiceName)) ||
         base::PathExists(unit_dir->Append(kUpdaterSocketName));
}

}  // namespace updater