blob: 8e725905a17ab1015c6c92f12f157aeed60d9000 [file] [log] [blame]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/daemon_process.h"
#include <stdint.h>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/notimplemented.h"
#include "base/path_service.h"
#include "base/process/process.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "base/values.h"
#include "mojo/core/embedder/scoped_ipc_support.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "remoting/base/auto_thread.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/branding.h"
#include "remoting/base/crash/crash_reporting_breakpad.h"
#include "remoting/base/logging.h"
#include "remoting/base/username.h"
#include "remoting/host/base/host_exit_codes.h"
#include "remoting/host/base/screen_resolution.h"
#include "remoting/host/base/switches.h"
#include "remoting/host/chromoting_host_services_server.h"
#include "remoting/host/host_config.h"
#include "remoting/host/host_main.h"
#include "remoting/host/ipc_constants.h"
#include "remoting/host/linux/desktop_session_factory_linux.h"
#include "remoting/host/linux/linux_process_launcher_delegate.h"
#include "remoting/host/linux/passwd_utils.h"
#include "remoting/host/mojom/chromoting_host_services.mojom.h"
#include "remoting/host/mojom/remoting_host.mojom.h"
#include "remoting/host/usage_stats_consent.h"
#include "remoting/host/worker_process_launcher.h"
namespace remoting {
class DaemonProcessLinux : public DaemonProcess {
public:
DaemonProcessLinux(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
scoped_refptr<AutoThreadTaskRunner> io_task_runner,
base::OnceClosure stopped_callback);
DaemonProcessLinux(const DaemonProcessLinux&) = delete;
DaemonProcessLinux& operator=(const DaemonProcessLinux&) = delete;
~DaemonProcessLinux() override;
// WorkerProcessIpcDelegate implementation.
void OnChannelConnected(int32_t peer_pid) override;
void OnWorkerProcessStopped() override;
// DaemonProcess overrides.
bool OnDesktopSessionAgentAttached(
int terminal_id,
int session_id,
mojo::ScopedMessagePipeHandle desktop_pipe) override;
void StartDesktopSessionFactory();
private:
// DaemonProcess implementation.
std::unique_ptr<DesktopSession> DoCreateDesktopSession(
int terminal_id,
const mojom::DesktopSessionOptions& options) override;
void DoCrashNetworkProcess(const base::Location& location) override;
void LaunchNetworkProcess() override;
void SendHostConfigToNetworkProcess(
const std::string& serialized_config) override;
void SendTerminalDisconnected(int terminal_id) override;
void StartChromotingHostServices() override;
void OnStartDesktopSessionFactoryResult(
base::expected<void, Loggable> result);
void BindChromotingHostServices(
mojo::PendingReceiver<mojom::ChromotingHostServices> receiver,
base::ProcessId peer_pid);
// Mojo keeps the task runner passed to it alive forever, so an
// AutoThreadTaskRunner should not be passed to it. Otherwise, the process may
// never shut down cleanly.
mojo::core::ScopedIPCSupport ipc_support_;
std::unique_ptr<WorkerProcessLauncher> network_launcher_;
std::unique_ptr<ChromotingHostServicesServer> ipc_server_;
DesktopSessionFactoryLinux desktop_session_factory_;
mojo::AssociatedRemote<mojom::DesktopSessionConnectionEvents>
desktop_session_connection_events_;
mojo::AssociatedRemote<mojom::RemotingHostControl> remoting_host_control_;
};
DaemonProcessLinux::DaemonProcessLinux(
scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
scoped_refptr<AutoThreadTaskRunner> io_task_runner,
base::OnceClosure stopped_callback)
: DaemonProcess(caller_task_runner,
io_task_runner,
std::move(stopped_callback)),
ipc_support_(io_task_runner->task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST),
desktop_session_factory_(io_task_runner) {}
DaemonProcessLinux::~DaemonProcessLinux() = default;
void DaemonProcessLinux::OnChannelConnected(int32_t peer_pid) {
// Typically the Daemon process is responsible for disconnecting the remote
// however in cases where the network process crashes, we want to ensure that
// |remoting_host_control_| is reset so it can be reused after the network
// process is relaunched.
remoting_host_control_.reset();
network_launcher_->GetRemoteAssociatedInterface(
remoting_host_control_.BindNewEndpointAndPassReceiver());
desktop_session_connection_events_.reset();
network_launcher_->GetRemoteAssociatedInterface(
desktop_session_connection_events_.BindNewEndpointAndPassReceiver());
DaemonProcess::OnChannelConnected(peer_pid);
}
void DaemonProcessLinux::OnWorkerProcessStopped() {
// Reset our IPC remote so it's ready to re-init if the network process is
// re-launched.
remoting_host_control_.reset();
desktop_session_connection_events_.reset();
DaemonProcess::OnWorkerProcessStopped();
}
bool DaemonProcessLinux::OnDesktopSessionAgentAttached(
int terminal_id,
int session_id,
mojo::ScopedMessagePipeHandle desktop_pipe) {
if (desktop_session_connection_events_) {
desktop_session_connection_events_->OnDesktopSessionAgentAttached(
terminal_id, session_id, std::move(desktop_pipe));
}
return true;
}
void DaemonProcessLinux::StartDesktopSessionFactory() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
desktop_session_factory_.Start(
base::BindOnce(&DaemonProcessLinux::OnStartDesktopSessionFactoryResult,
base::Unretained(this)));
}
std::unique_ptr<DesktopSession> DaemonProcessLinux::DoCreateDesktopSession(
int terminal_id,
const mojom::DesktopSessionOptions& options) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
return desktop_session_factory_.CreateDesktopSession(terminal_id, this,
options);
}
void DaemonProcessLinux::DoCrashNetworkProcess(const base::Location& location) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
NOTIMPLEMENTED();
}
void DaemonProcessLinux::LaunchNetworkProcess() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
// TODO: crbug.com/475611769 - See if we need a dedicated desktop process
// binary.
base::FilePath this_exe;
if (!base::PathService::Get(base::BasePathKey::FILE_EXE, &this_exe)) {
LOG(ERROR) << "Failed to get the current executable path.";
Stop();
return;
}
auto user_info = GetPasswdUserInfo(GetNetworkProcessUsername());
if (!user_info.has_value()) {
LOG(ERROR) << user_info.error();
Stop();
return;
}
base::CommandLine command_line(this_exe);
command_line.AppendSwitchASCII(kProcessTypeSwitchName, kProcessTypeNetwork);
LinuxWorkerProcessLauncherDelegate::LaunchOptions options(command_line);
options.new_session = true;
options.uid = user_info->uid;
options.gid = user_info->gid;
// The home directory of the network user is /nonexistent, so we just change
// the working directory to /tmp instead.
base::FilePath temp_dir;
if (!base::PathService::Get(base::DIR_TEMP, &temp_dir)) {
LOG(ERROR) << "Failed to get the temporary directory path.";
Stop();
return;
}
options.working_dir = temp_dir;
options.environment_variables = {
{"LOGNAME", GetNetworkProcessUsername().data()},
{"USER", GetNetworkProcessUsername().data()},
};
network_launcher_ = std::make_unique<WorkerProcessLauncher>(
std::make_unique<LinuxWorkerProcessLauncherDelegate>(std::move(options),
io_task_runner()),
this);
}
void DaemonProcessLinux::SendHostConfigToNetworkProcess(
const std::string& serialized_config) {
if (!remoting_host_control_) {
return;
}
LOG_IF(ERROR, !remoting_host_control_.is_connected())
<< "IPC channel not connected. HostConfig message will be dropped.";
std::optional<base::DictValue> config(HostConfigFromJson(serialized_config));
if (!config.has_value()) {
LOG(ERROR) << "Invalid host config, shutting down.";
OnPermanentError(kInvalidHostConfigurationExitCode);
return;
}
remoting_host_control_->ApplyHostConfig(std::move(config.value()));
}
void DaemonProcessLinux::SendTerminalDisconnected(int terminal_id) {
if (desktop_session_connection_events_) {
desktop_session_connection_events_->OnTerminalDisconnected(terminal_id);
}
}
void DaemonProcessLinux::StartChromotingHostServices() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
DCHECK(!ipc_server_);
ipc_server_ = std::make_unique<ChromotingHostServicesServer>(
base::BindRepeating(&DaemonProcessLinux::BindChromotingHostServices,
base::Unretained(this)));
ipc_server_->StartServer();
HOST_LOG << "ChromotingHostServices IPC server has been started.";
}
void DaemonProcessLinux::OnStartDesktopSessionFactoryResult(
base::expected<void, Loggable> result) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
if (!result.has_value()) {
LOG(ERROR) << result.error();
Stop();
}
}
std::unique_ptr<DaemonProcess> DaemonProcess::Create(
scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
scoped_refptr<AutoThreadTaskRunner> io_task_runner,
base::OnceClosure stopped_callback) {
auto daemon_process = std::make_unique<DaemonProcessLinux>(
caller_task_runner, io_task_runner, std::move(stopped_callback));
// TODO: crbug.com/475611769 - set ACL on the pairing registry directory for
// the network user.
daemon_process->StartDesktopSessionFactory();
// Finishes configuring the Daemon process and launches the network process.
daemon_process->Initialize();
return std::move(daemon_process);
}
void DaemonProcessLinux::BindChromotingHostServices(
mojo::PendingReceiver<mojom::ChromotingHostServices> receiver,
base::ProcessId peer_pid) {
if (!remoting_host_control_.is_bound()) {
LOG(ERROR) << "Binding rejected. Network process is not ready.";
return;
}
remoting_host_control_->BindChromotingHostServices(std::move(receiver),
peer_pid);
}
} // namespace remoting