#include "components/dbus/xdg/systemd.h"
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/version_info/nix/version_extra_utils.h"
#include "build/branding_buildflags.h"
#include "components/dbus/utils/name_has_owner.h"
#include "components/dbus/utils/variant.h"
#include "components/dbus/utils/write_value.h"
#include "components/dbus/xdg/systemd_constants.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/object_proxy.h"
#include "dbus/property.h"
namespace dbus_xdg {
template <typename T>
using Dict = std::vector<std::tuple<std::string, T>>;
using VarDict = Dict<dbus_utils::Variant>;
using internal::SystemdUnitCallback;
using internal::SystemdUnitStatus;
using SystemdUnitCallbacks = std::vector<SystemdUnitCallback>;
namespace {
constexpr char kUnitNameFormat[] = "app-$1-$2.scope";
constexpr char kModeReplace[] = "replace";
class SystemdUnitActiveStateWatcher : public dbus::PropertySet {
public:
SystemdUnitActiveStateWatcher(scoped_refptr<dbus::Bus> bus,
dbus::ObjectProxy* object_proxy,
SystemdUnitCallback callback)
: dbus::PropertySet(object_proxy,
kInterfaceSystemdUnit,
base::BindRepeating(
&SystemdUnitActiveStateWatcher::OnPropertyChanged,
base::Unretained(this))),
bus_(bus),
callback_(std::move(callback)) {
RegisterProperty(kSystemdActiveStateProp, &active_state_);
ConnectSignals();
GetAll();
}
~SystemdUnitActiveStateWatcher() override {
bus_->RemoveObjectProxy(kServiceNameSystemd, object_proxy()->object_path(),
base::DoNothing());
}
private:
void OnPropertyChanged(const std::string& property_name) {
DCHECK(active_state_.is_valid());
const std::string state_value = active_state_.value();
if (!callback_ || state_value == "activating" ||
state_value == "reloading") {
return;
}
std::move(callback_).Run(state_value == kSystemdStateActive
? SystemdUnitStatus::kUnitStarted
: SystemdUnitStatus::kFailedToStart);
MaybeDeleteSelf();
}
void OnGetAll(dbus::Response* response) override {
dbus::PropertySet::OnGetAll(response);
keep_alive_ = false;
MaybeDeleteSelf();
}
void MaybeDeleteSelf() {
if (!keep_alive_ && !callback_) {
delete this;
}
}
dbus::Property<std::string> active_state_;
bool keep_alive_ = true;
scoped_refptr<dbus::Bus> bus_;
SystemdUnitCallback callback_;
};
void OnGetPathResponse(scoped_refptr<dbus::Bus> bus,
SystemdUnitCallback callback,
dbus::Response* response) {
dbus::MessageReader reader(response);
dbus::ObjectPath obj_path;
if (!response || !reader.PopObjectPath(&obj_path) || !obj_path.IsValid()) {
std::move(callback).Run(SystemdUnitStatus::kFailedToStart);
return;
}
dbus::ObjectProxy* unit_proxy =
bus->GetObjectProxy(kServiceNameSystemd, obj_path);
std::unique_ptr<SystemdUnitActiveStateWatcher> active_state_watcher =
std::make_unique<SystemdUnitActiveStateWatcher>(bus, unit_proxy,
std::move(callback));
active_state_watcher.release();
}
void WaitUnitActivateAndRunCallbacks(scoped_refptr<dbus::Bus> bus,
std::string unit_name,
SystemdUnitCallback callback) {
dbus::ObjectProxy* systemd = bus->GetObjectProxy(
kServiceNameSystemd, dbus::ObjectPath(kObjectPathSystemd));
dbus::MethodCall method_call(kInterfaceSystemdManager, kMethodGetUnit);
dbus::MessageWriter writer(&method_call);
writer.AppendString(unit_name);
systemd->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&OnGetPathResponse, std::move(bus), std::move(callback)));
}
void OnStartTransientUnitResponse(scoped_refptr<dbus::Bus> bus,
std::string unit_name,
SystemdUnitCallback callback,
dbus::Response* response) {
SystemdUnitStatus result = response ? SystemdUnitStatus::kUnitStarted
: SystemdUnitStatus::kFailedToStart;
if (result == SystemdUnitStatus::kFailedToStart) {
std::move(callback).Run(result);
} else {
WaitUnitActivateAndRunCallbacks(std::move(bus), unit_name,
std::move(callback));
}
}
void OnNameHasOwnerResponse(scoped_refptr<dbus::Bus> bus,
SystemdUnitCallback callback,
std::optional<bool> name_has_owner) {
if (!name_has_owner.value_or(false)) {
std::move(callback).Run(SystemdUnitStatus::kNoSystemdService);
return;
}
pid_t pid = getpid();
if (pid <= 1) {
std::move(callback).Run(SystemdUnitStatus::kInvalidPid);
return;
}
std::string app_name =
version_info::nix::GetAppName(*base::Environment::Create());
auto unit_name = base::ReplaceStringPlaceholders(
kUnitNameFormat, {app_name, base::NumberToString(pid)}, nullptr);
auto* systemd = bus->GetObjectProxy(kServiceNameSystemd,
dbus::ObjectPath(kObjectPathSystemd));
dbus::MethodCall method_call(kInterfaceSystemdManager,
kMethodStartTransientUnit);
dbus::MessageWriter writer(&method_call);
writer.AppendString(unit_name);
writer.AppendString(kModeReplace);
std::vector<uint32_t> pids = {static_cast<uint32_t>(pid)};
VarDict properties;
properties.emplace_back("PIDs",
dbus_utils::Variant::Wrap<"au">(std::move(pids)));
dbus_utils::WriteValue(writer, properties);
dbus_utils::WriteValue(writer, Dict<VarDict>());
systemd->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&OnStartTransientUnitResponse, std::move(bus), unit_name,
std::move(callback)));
}
}
namespace internal {
void SetSystemdScopeUnitNameForXdgPortal(dbus::Bus* bus,
SystemdUnitCallback callback) {
#if DCHECK_IS_ON()
static base::SequenceChecker sequence_checker;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
#endif
auto env = base::Environment::Create();
if (env->HasVar("FLATPAK_SANDBOX_DIR") || env->HasVar("SNAP")) {
std::move(callback).Run(SystemdUnitStatus::kUnitNotNecessary);
return;
}
dbus_utils::NameHasOwner(
bus, kServiceNameSystemd,
base::BindOnce(&OnNameHasOwnerResponse, base::WrapRefCounted(bus),
std::move(callback)));
}
}
}