blob: c31e4d1e328cf5bc964e0c6f584629d9b63b64ad [file]
// 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/systemd_user_env_setter.h"
#include <systemd/sd-bus.h>
#include <unistd.h>
#include <cstdlib>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/posix/safe_strerror.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "remoting/base/logging.h"
namespace remoting {
namespace {
constexpr base::TimeDelta kTimeout = base::Seconds(10);
constexpr base::TimeDelta kPollInterval = base::Seconds(1);
using ScopedSdBus =
std::unique_ptr<sd_bus, decltype([](sd_bus* bus) { sd_bus_unref(bus); })>;
using ScopedSdBusMessage =
std::unique_ptr<sd_bus_message, decltype([](sd_bus_message* message) {
sd_bus_message_unref(message);
})>;
struct SdBusError {
SdBusError() = default;
SdBusError(const SdBusError&) = delete;
~SdBusError() {
// This only frees resources held by `error`, but not `error` itself.
sd_bus_error_free(&error);
}
sd_bus_error* get() { return &error; }
sd_bus_error* operator->() { return &error; }
private:
sd_bus_error error = SD_BUS_ERROR_NULL;
};
base::expected<base::EnvironmentMap, Loggable> ParseEnvironment(
sd_bus_message* message) {
int r = sd_bus_message_enter_container(message, 'a', "s");
if (r < 0) {
return base::unexpected(
Loggable(FROM_HERE,
base::StringPrintf("Failed to enter Environment container: %s",
strerror(-r))));
}
// `env_str` is never owned by us.
const char* env_str = nullptr;
base::EnvironmentMap env_map;
while (sd_bus_message_read(message, "s", &env_str) > 0) {
auto split_result = base::SplitStringOnce(env_str, '=');
if (split_result.has_value() && !split_result->first.empty()) {
env_map[std::string(split_result->first)] = split_result->second;
}
}
return env_map;
}
} // namespace
base::expected<void, Loggable> SetSystemdUserEnvironment() {
uid_t uid = getuid();
base::FilePath dbus_socket =
base::FilePath(base::StringPrintf("/run/user/%u/bus", uid));
base::TimeTicks deadline = base::TimeTicks::Now() + kTimeout;
bool is_initial_poll = true;
while (base::TimeTicks::Now() < deadline) {
if (is_initial_poll) {
is_initial_poll = false;
} else {
base::PlatformThread::Sleep(kPollInterval);
}
if (!base::PathExists(dbus_socket)) {
HOST_LOG << "D-Bus socket " << dbus_socket << " not ready. Retrying in "
<< kPollInterval;
continue;
}
std::string dbus_address = "unix:path=" + dbus_socket.value();
setenv("DBUS_SESSION_BUS_ADDRESS", dbus_address.c_str(),
/*replace=*/true);
sd_bus* bus_ptr = nullptr;
int r = sd_bus_open_user(&bus_ptr);
if (r < 0) {
LOG(WARNING) << "Failed to connect to user D-Bus: "
<< base::safe_strerror(-r) << ". Retrying in "
<< kPollInterval;
continue;
}
ScopedSdBus bus(bus_ptr);
SdBusError error;
sd_bus_message* message_ptr = nullptr;
r = sd_bus_get_property(bus.get(), "org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", "Environment",
error.get(), &message_ptr, "as");
if (r < 0) {
LOG(WARNING) << "Failed to get Environment property from systemd: "
<< (error->message ? error->message
: base::safe_strerror(-r))
<< ". Retrying in " << kPollInterval;
continue;
}
ScopedSdBusMessage message(message_ptr);
auto result = ParseEnvironment(message.get());
if (!result.has_value()) {
return base::unexpected(result.error());
}
auto& env_map = result.value();
// The compositor isn't immediately ready after the session is created, so
// we poll the environment until we see either WAYLAND_DISPLAY or DISPLAY.
auto session_type_it = env_map.find("XDG_SESSION_TYPE");
bool is_wayland = session_type_it != env_map.end() &&
session_type_it->second == "wayland";
if ((is_wayland && env_map.contains("WAYLAND_DISPLAY")) ||
(!is_wayland && env_map.contains("DISPLAY"))) {
for (const auto& [key, value] : env_map) {
setenv(key.c_str(), value.c_str(), /*replace=*/true);
}
return base::ok();
}
HOST_LOG << "DISPLAY or WAYLAND_DISPLAY not found in systemd environment. "
<< "Retrying in " << kPollInterval;
}
return base::unexpected(Loggable(FROM_HERE,
"Timeout waiting for DISPLAY or "
"WAYLAND_DISPLAY in systemd environment."));
}
} // namespace remoting