| // 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/linux/remote_display_session_manager.h" |
| |
| #include <pwd.h> |
| #include <unistd.h> |
| |
| #include <optional> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/barrier_callback.h" |
| #include "base/base_paths.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/json/json_reader.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/path_service.h" |
| #include "base/process/launch.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/thread_pool.h" |
| #include "base/types/expected.h" |
| #include "base/version.h" |
| #include "remoting/base/loggable.h" |
| #include "remoting/base/logging.h" |
| #include "remoting/base/passwd_utils.h" |
| #include "remoting/host/base/switches.h" |
| #include "remoting/host/linux/gvariant_ref.h" |
| #include "remoting/host/linux/login_session_manager.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| constexpr std::string_view kRemoteDisplayIdPrefix = |
| "/com/google/ChromeRemoteDesktop/RemoteDisplays/"; |
| |
| std::string GetRemoteDisplayName(const gvariant::ObjectPath& remote_id) { |
| if (!base::StartsWith(remote_id.value(), kRemoteDisplayIdPrefix)) { |
| return {}; |
| } |
| return remote_id.value().substr(kRemoteDisplayIdPrefix.size()); |
| } |
| |
| } // namespace |
| |
| // RemoteDisplaySession |
| |
| RemoteDisplaySessionManager::RemoteDisplaySession::RemoteDisplaySession() = |
| default; |
| RemoteDisplaySessionManager::RemoteDisplaySession::RemoteDisplaySession( |
| RemoteDisplaySession&&) = default; |
| RemoteDisplaySessionManager::RemoteDisplaySession::RemoteDisplaySession( |
| const RemoteDisplaySession&) = default; |
| RemoteDisplaySessionManager::RemoteDisplaySession::~RemoteDisplaySession() = |
| default; |
| |
| RemoteDisplaySessionManager::RemoteDisplaySession& |
| RemoteDisplaySessionManager::RemoteDisplaySession::operator=( |
| RemoteDisplaySession&&) = default; |
| RemoteDisplaySessionManager::RemoteDisplaySession& |
| RemoteDisplaySessionManager::RemoteDisplaySession::operator=( |
| const RemoteDisplaySession&) = default; |
| |
| // RemoteDisplayInfo |
| |
| RemoteDisplaySessionManager::RemoteDisplayInfo::RemoteDisplayInfo() = default; |
| RemoteDisplaySessionManager::RemoteDisplayInfo::RemoteDisplayInfo( |
| RemoteDisplayInfo&&) = default; |
| RemoteDisplaySessionManager::RemoteDisplayInfo::RemoteDisplayInfo( |
| const RemoteDisplayInfo&) = default; |
| RemoteDisplaySessionManager::RemoteDisplayInfo::~RemoteDisplayInfo() = default; |
| |
| RemoteDisplaySessionManager::RemoteDisplayInfo& |
| RemoteDisplaySessionManager::RemoteDisplayInfo::operator=(RemoteDisplayInfo&&) = |
| default; |
| RemoteDisplaySessionManager::RemoteDisplayInfo& |
| RemoteDisplaySessionManager::RemoteDisplayInfo::operator=( |
| const RemoteDisplayInfo&) = default; |
| |
| // RemoteDisplaySessionManager |
| |
| RemoteDisplaySessionManager::RemoteDisplaySessionManager() = default; |
| RemoteDisplaySessionManager::~RemoteDisplaySessionManager() = default; |
| |
| void RemoteDisplaySessionManager::Start(Delegate* delegate, Callback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(start_state_, StartState::NOT_STARTED); |
| DCHECK(delegate); |
| DCHECK(callback); |
| |
| start_state_ = StartState::STARTING; |
| delegate_ = delegate; |
| |
| init_callback_ = std::move(callback); |
| GDBusConnectionRef::CreateForSystemBus( |
| base::BindOnce(&RemoteDisplaySessionManager::OnCreateDbusConnectionResult, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void RemoteDisplaySessionManager::CreateRemoteDisplay( |
| std::string_view display_name, |
| Callback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(start_state_, StartState::STARTED); |
| |
| auto result = gvariant::ObjectPath::TryFrom( |
| std::string(kRemoteDisplayIdPrefix) + std::string(display_name)); |
| if (!result.has_value()) { |
| std::move(callback).Run(base::unexpected(std::move(result).error())); |
| return; |
| } |
| remote_display_manager_.CreateRemoteDisplay(*result, std::move(callback)); |
| } |
| |
| void RemoteDisplaySessionManager::TerminateRemoteDisplay( |
| std::string_view display_name, |
| Callback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(start_state_, StartState::STARTED); |
| |
| const auto it = remote_displays_.find(display_name); |
| if (it == remote_displays_.end()) { |
| std::move(callback).Run(base::unexpected(Loggable( |
| FROM_HERE, |
| "Remote display " + std::string(display_name) + " not found."))); |
| return; |
| } |
| |
| auto terminate_all_callback = |
| base::BarrierCallback<base::expected<void, Loggable>>( |
| it->second.sessions.size(), |
| base::BindOnce( |
| [](const std::string& display_name, Callback callback, |
| std::vector<base::expected<void, Loggable>> results) { |
| for (auto& result : results) { |
| if (!result.has_value()) { |
| auto loggable = std::move(result).error(); |
| loggable.AddContext( |
| FROM_HERE, |
| std::string( |
| "Failed to terminate a session for display") + |
| display_name); |
| std::move(callback).Run( |
| base::unexpected(std::move(loggable))); |
| return; |
| } |
| } |
| std::move(callback).Run(base::ok()); |
| }, |
| std::string(display_name), std::move(callback))); |
| |
| for (const auto& [_, session] : it->second.sessions) { |
| TerminateRemoteDisplaySession(session, terminate_all_callback); |
| } |
| } |
| |
| void RemoteDisplaySessionManager::TerminateRemoteDisplaySession( |
| const RemoteDisplaySession& session, |
| Callback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(start_state_, StartState::STARTED); |
| |
| if (!session.session_info.has_value()) { |
| std::move(callback).Run(base::unexpected( |
| Loggable(FROM_HERE, "Remote display session has no session info."))); |
| return; |
| } |
| |
| login_session_manager_->TerminateSession(session.session_info->object_path, |
| std::move(callback)); |
| } |
| |
| const RemoteDisplaySessionManager::RemoteDisplayInfo* |
| RemoteDisplaySessionManager::GetRemoteDisplayInfo( |
| std::string_view display_name) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const auto it = remote_displays_.find(display_name); |
| if (it == remote_displays_.end()) { |
| return nullptr; |
| } |
| return &it->second; |
| } |
| |
| void RemoteDisplaySessionManager::QuerySessionInfo( |
| const std::string& display_name, |
| const gvariant::ObjectPath& display_path, |
| const std::string& session_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| login_session_manager_->GetSessionInfo( |
| session_id, |
| base::BindOnce(&RemoteDisplaySessionManager::OnSessionInfoReady, |
| weak_ptr_factory_.GetWeakPtr(), std::string(display_name), |
| display_path)); |
| } |
| |
| void RemoteDisplaySessionManager::HandleSessionInfoQueriesBlockingStartup() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (start_state_ == StartState::STARTED) { |
| return; |
| } |
| |
| DCHECK_EQ(start_state_, StartState::STARTING); |
| if (session_info_queries_blocking_startup_.empty()) { |
| start_state_ = StartState::STARTED; |
| std::move(init_callback_).Run(base::ok()); |
| } |
| } |
| |
| void RemoteDisplaySessionManager::OnCreateDbusConnectionResult( |
| base::expected<GDBusConnectionRef, Loggable> result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!result.has_value()) { |
| start_state_ = StartState::NOT_STARTED; |
| std::move(init_callback_).Run(base::unexpected(std::move(result).error())); |
| return; |
| } |
| |
| connection_ = std::move(*result); |
| login_session_manager_ = std::make_unique<LoginSessionManager>(connection_); |
| remote_display_manager_.Init( |
| connection_, this, |
| base::BindOnce( |
| &RemoteDisplaySessionManager::OnGdmRemoteDisplayManagerStarted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void RemoteDisplaySessionManager::OnGdmRemoteDisplayManagerStarted( |
| base::expected<void, Loggable> result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!result.has_value()) { |
| start_state_ = StartState::NOT_STARTED; |
| std::move(init_callback_).Run(base::unexpected(std::move(result).error())); |
| return; |
| } |
| |
| login_session_server_.StartServer(); |
| for (const auto& [display_path, remote_display] : |
| remote_display_manager_.remote_displays()) { |
| std::string display_name = GetRemoteDisplayName(remote_display.remote_id); |
| if (display_name.empty()) { |
| VLOG(1) << "Ignoring unrelated remote display: " |
| << remote_display.remote_id.value(); |
| continue; |
| } |
| auto& display_info = remote_displays_[display_name]; |
| display_info.sessions[display_path] = RemoteDisplaySession(); |
| if (!remote_display.session_id.empty()) { |
| session_info_queries_blocking_startup_.insert(display_path); |
| QuerySessionInfo(display_name, display_path, remote_display.session_id); |
| } |
| } |
| HandleSessionInfoQueriesBlockingStartup(); |
| } |
| |
| void RemoteDisplaySessionManager::OnRemoteDisplayCreated( |
| const gvariant::ObjectPath& display_path, |
| const GdmRemoteDisplayManager::RemoteDisplay& display) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| std::string display_name = GetRemoteDisplayName(display.remote_id); |
| if (display_name.empty()) { |
| VLOG(1) << "Ignoring unrelated remote display: " |
| << display.remote_id.value(); |
| return; |
| } |
| auto& display_info = remote_displays_[display_name]; |
| display_info.sessions[display_path] = RemoteDisplaySession(); |
| if (!display.session_id.empty()) { |
| HOST_LOG << "Querying session info for remote display: " |
| << display.remote_id.value() |
| << ", session id: " << display.session_id; |
| QuerySessionInfo(display_name, display_path, display.session_id); |
| } |
| } |
| |
| void RemoteDisplaySessionManager::OnRemoteDisplayRemoved( |
| const gvariant::ObjectPath& display_path, |
| const gvariant::ObjectPath& remote_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| std::string display_name = GetRemoteDisplayName(remote_id); |
| if (display_name.empty()) { |
| VLOG(1) << "Ignoring unrelated remote display: " << remote_id.value(); |
| return; |
| } |
| HOST_LOG << "GDM remote display session removed: " << display_path.value() |
| << ", remote id: " << remote_id.value(); |
| auto it = remote_displays_.find(display_name); |
| if (it == remote_displays_.end()) { |
| LOG(WARNING) << "Cannot find remote display with name: " << display_name; |
| return; |
| } |
| RemoteDisplayInfo& display_info = it->second; |
| display_info.sessions.erase(display_path); |
| if (!display_info.sessions.empty()) { |
| delegate_->OnRemoteDisplayChanged(display_name, display_info); |
| return; |
| } |
| remote_displays_.erase(it); |
| delegate_->OnRemoteDisplayTerminated(display_name); |
| } |
| |
| void RemoteDisplaySessionManager::OnRemoteDisplayChanged( |
| const gvariant::ObjectPath& display_path, |
| const GdmRemoteDisplayManager::RemoteDisplay& display) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| std::string display_name = GetRemoteDisplayName(display.remote_id); |
| if (display_name.empty()) { |
| VLOG(1) << "Ignoring unrelated remote display: " |
| << display.remote_id.value(); |
| return; |
| } |
| HOST_LOG << "Remote display session changed: " << display.remote_id.value() |
| << ", session id: " << display.session_id |
| << ", display path: " << display_path.value(); |
| auto display_it = remote_displays_.find(display_name); |
| if (display_it == remote_displays_.end()) { |
| LOG(WARNING) << "Cannot find remote display with name: " << display_name; |
| return; |
| } |
| RemoteDisplayInfo& display_info = display_it->second; |
| // Find `display_path`. In case it is not found, create the session. |
| auto [session_it, inserted] = display_info.sessions.try_emplace(display_path); |
| if (inserted) { |
| LOG(WARNING) << "Cannot find remote display session with display path: " |
| << display_path.value(); |
| } |
| RemoteDisplaySession& session = session_it->second; |
| session.session_info = std::nullopt; |
| session.user_info = std::nullopt; |
| if (display.session_id.empty()) { |
| delegate_->OnRemoteDisplayChanged(display_name, display_info); |
| return; |
| } |
| |
| QuerySessionInfo(display_name, display_path, display.session_id); |
| } |
| |
| void RemoteDisplaySessionManager::IsRunningInCrdSession( |
| const std::string& session_id, |
| LoginSessionServer::Delegate::IsRunningInCrdSessionCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| IsRunningInCrdSessionInternal(session_id, std::move(callback), |
| /*can_wait=*/true); |
| } |
| |
| void RemoteDisplaySessionManager::IsRunningInCrdSessionInternal( |
| const std::string& session_id, |
| LoginSessionServer::Delegate::IsRunningInCrdSessionCallback callback, |
| bool can_wait) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (IsRunningInCrdSessionSynchronous(session_id)) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| if (!can_wait) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // See if there is any active greeter session. If we see an unrecognized |
| // session while there is an active greeter session, then it is possible that |
| // the new session is transitioned from the greeter session during user login |
| // but we haven't observed it yet. |
| // TODO: yuweih - See if it is possible to "tag" a login session as |
| // CRD-managed so that we don't need this somewhat fragile logic, but this may |
| // require some new API from GDM. |
| bool has_greeter_session = false; |
| for (auto& [_, d_info] : remote_displays_) { |
| for (auto& [_, s] : d_info.sessions) { |
| if (s.session_info.has_value() && |
| s.session_info->session_class == "greeter") { |
| has_greeter_session = true; |
| break; |
| } |
| } |
| if (has_greeter_session) { |
| break; |
| } |
| } |
| |
| if (!has_greeter_session) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // Query the session info to check if it's a remote user session. |
| login_session_manager_->GetSessionInfo( |
| session_id, |
| base::BindOnce(&RemoteDisplaySessionManager:: |
| OnIsRunningInCrdSessionGetSessionInfoResult, |
| weak_ptr_factory_.GetWeakPtr(), session_id, |
| std::move(callback))); |
| } |
| |
| void RemoteDisplaySessionManager::OnIsRunningInCrdSessionGetSessionInfoResult( |
| const std::string& session_id, |
| LoginSessionServer::Delegate::IsRunningInCrdSessionCallback callback, |
| base::expected<LoginSessionManager::SessionInfo, Loggable> result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!result.has_value()) { |
| LOG(ERROR) << result.error(); |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // Not a remote user session, so it's definitely not managed by CRD. |
| if (!result->is_remote || result->session_class != "user") { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // Try again in 500ms without waiting again. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce( |
| &RemoteDisplaySessionManager::IsRunningInCrdSessionInternal, |
| weak_ptr_factory_.GetWeakPtr(), session_id, std::move(callback), |
| /*can_wait=*/false), |
| base::Milliseconds(500)); |
| } |
| |
| bool RemoteDisplaySessionManager::IsRunningInCrdSessionSynchronous( |
| const std::string& session_id) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| for (auto& [_, d_info] : remote_displays_) { |
| for (auto& [_, s] : d_info.sessions) { |
| if (s.session_info.has_value() && |
| s.session_info->session_id == session_id) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| void RemoteDisplaySessionManager::OnSessionInfoReady( |
| const std::string& display_name, |
| const gvariant::ObjectPath& display_path, |
| base::expected<LoginSessionManager::SessionInfo, Loggable> result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!result.has_value()) { |
| LOG(ERROR) << "Failed to get session info for " << display_name << ": " |
| << result.error(); |
| session_info_queries_blocking_startup_.erase(display_path); |
| HandleSessionInfoQueriesBlockingStartup(); |
| return; |
| } |
| DCHECK(result->is_remote); |
| |
| auto remote_display_it = remote_displays_.find(display_name); |
| DCHECK(remote_display_it != remote_displays_.end()); |
| auto& remote_display_info = remote_display_it->second; |
| auto& session = remote_display_info.sessions[display_path]; |
| session.session_info = std::move(*result); |
| auto user_info_expected = GetPasswdUserInfo(session.session_info->username); |
| if (user_info_expected.has_value()) { |
| session.user_info = std::move(user_info_expected.value()); |
| } else { |
| LOG(ERROR) << user_info_expected.error(); |
| } |
| |
| if (start_state_ == StartState::STARTED) { |
| delegate_->OnRemoteDisplayChanged(display_name, remote_display_info); |
| } |
| |
| session_info_queries_blocking_startup_.erase(display_path); |
| HandleSessionInfoQueriesBlockingStartup(); |
| } |
| |
| } // namespace remoting |