Add dbus_utils::CheckForServiceAndStart
This is extracted from the following places:
* chrome/browser/notifications/notification_platform_bridge_linux.cc
* chrome/browser/platform_util_linux.cc
* chrome/browser/dbus_memory_pressure_evaluator_linux.cc
* ui/shell_dialogs/select_file_dialog_linux_portal.cc
Also, there are more places that should be auto-starting that were not.
Followup CLs will refactor existing usages to use NameHasOwner().
The motivation for this CL is to deduplicate logic from the above
locations and to autostart in places that have not yet implemented
autostarting. Also check_for_service_and_start_unittest.cc is added.
Change-Id: I949e7abbce0eed5d5eb24c4204d7e06c29df2c61
Bug: None
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5906079
Auto-Submit: Thomas Anderson <thomasanderson@chromium.org>
Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
Commit-Queue: Ryo Hashimoto <hashimoto@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1368599}
diff --git a/components/BUILD.gn b/components/BUILD.gn
index bb37b04..649e7ef 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -830,6 +830,7 @@
deps += [
"//components/dbus/menu:unit_tests",
"//components/dbus/properties:unit_tests",
+ "//components/dbus/utils:unit_tests",
]
}
diff --git a/components/dbus/utils/BUILD.gn b/components/dbus/utils/BUILD.gn
index 9cd8a0f..1ef0ae9 100644
--- a/components/dbus/utils/BUILD.gn
+++ b/components/dbus/utils/BUILD.gn
@@ -4,6 +4,8 @@
component("utils") {
sources = [
+ "check_for_service_and_start.cc",
+ "check_for_service_and_start.h",
"name_has_owner.cc",
"name_has_owner.h",
]
@@ -13,3 +15,17 @@
"//dbus",
]
}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "check_for_service_and_start_unittest.cc" ]
+ deps = [
+ ":utils",
+ "//base",
+ "//base/test:test_support",
+ "//dbus",
+ "//dbus:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/components/dbus/utils/check_for_service_and_start.cc b/components/dbus/utils/check_for_service_and_start.cc
new file mode 100644
index 0000000..eacd824
--- /dev/null
+++ b/components/dbus/utils/check_for_service_and_start.cc
@@ -0,0 +1,123 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/dbus/utils/check_for_service_and_start.h"
+
+#include <utility>
+
+#include "base/containers/contains.h"
+#include "base/functional/callback.h"
+#include "base/logging.h"
+#include "components/dbus/utils/name_has_owner.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_proxy.h"
+
+namespace dbus_utils {
+
+namespace {
+
+constexpr char kMethodListActivatableNames[] = "ListActivatableNames";
+constexpr char kMethodStartServiceByName[] = "StartServiceByName";
+
+void OnStartServiceByNameResponse(scoped_refptr<dbus::Bus> bus,
+ const std::string& name,
+ CheckForServiceAndStartCallback callback,
+ dbus::Response* response) {
+ if (!response) {
+ std::move(callback).Run(std::nullopt);
+ return;
+ }
+
+ dbus::MessageReader reader(response);
+ std::optional<bool> started;
+ uint32_t start_service_reply = 0;
+ if (reader.PopUint32(&start_service_reply)) {
+ started = start_service_reply == DBUS_START_REPLY_SUCCESS ||
+ start_service_reply == DBUS_START_REPLY_ALREADY_RUNNING;
+ } else {
+ LOG(ERROR) << "Failed to read " << kMethodStartServiceByName << " response";
+ }
+ std::move(callback).Run(started);
+}
+
+void StartServiceByName(scoped_refptr<dbus::Bus> bus,
+ const std::string& name,
+ CheckForServiceAndStartCallback callback) {
+ dbus::ObjectProxy* proxy =
+ bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS));
+ dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, kMethodStartServiceByName);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(name);
+ // No flags
+ writer.AppendUint32(0);
+ proxy->CallMethod(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::BindOnce(&OnStartServiceByNameResponse, bus, name,
+ std::move(callback)));
+}
+
+void OnListActivatableNamesResponse(scoped_refptr<dbus::Bus> bus,
+ const std::string& name,
+ CheckForServiceAndStartCallback callback,
+ dbus::Response* response) {
+ if (!response) {
+ std::move(callback).Run(std::nullopt);
+ return;
+ }
+
+ dbus::MessageReader reader(response);
+ std::vector<std::string> activatable_names;
+ if (!reader.PopArrayOfStrings(&activatable_names)) {
+ LOG(ERROR) << "Failed to read " << kMethodListActivatableNames
+ << " response";
+ std::move(callback).Run(std::nullopt);
+ return;
+ }
+
+ if (base::Contains(activatable_names, name)) {
+ StartServiceByName(bus, name, std::move(callback));
+ } else {
+ // The service is not activatable
+ std::move(callback).Run(false);
+ }
+}
+
+void ListActivatableNames(scoped_refptr<dbus::Bus> bus,
+ const std::string& name,
+ CheckForServiceAndStartCallback callback) {
+ dbus::ObjectProxy* proxy =
+ bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS));
+ dbus::MethodCall method_call(DBUS_INTERFACE_DBUS,
+ kMethodListActivatableNames);
+ proxy->CallMethod(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::BindOnce(&OnListActivatableNamesResponse, bus, name,
+ std::move(callback)));
+}
+
+void OnNameHasOwnerResponse(scoped_refptr<dbus::Bus> bus,
+ const std::string& name,
+ CheckForServiceAndStartCallback callback,
+ std::optional<bool> name_has_owner) {
+ if (name_has_owner.value_or(true)) {
+ // Error communicating with bus or service already running.
+ std::move(callback).Run(name_has_owner);
+ return;
+ }
+
+ // Try auto-starting the service if it is activatable.
+ ListActivatableNames(bus, name, std::move(callback));
+}
+
+} // namespace
+
+void CheckForServiceAndStart(scoped_refptr<dbus::Bus> bus,
+ const std::string& name,
+ CheckForServiceAndStartCallback callback) {
+ // Check if the service is already running.
+ NameHasOwner(
+ bus.get(), name,
+ base::BindOnce(&OnNameHasOwnerResponse, bus, name, std::move(callback)));
+}
+
+} // namespace dbus_utils
diff --git a/components/dbus/utils/check_for_service_and_start.h b/components/dbus/utils/check_for_service_and_start.h
new file mode 100644
index 0000000..4954c3ad
--- /dev/null
+++ b/components/dbus/utils/check_for_service_and_start.h
@@ -0,0 +1,37 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DBUS_UTILS_CHECK_FOR_SERVICE_AND_START_H_
+#define COMPONENTS_DBUS_UTILS_CHECK_FOR_SERVICE_AND_START_H_
+
+#include <optional>
+#include <string>
+
+#include "base/component_export.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/scoped_refptr.h"
+#include "dbus/object_path.h"
+
+namespace dbus {
+class Bus;
+}
+
+namespace dbus_utils {
+
+using CheckForServiceAndStartCallback =
+ base::OnceCallback<void(/*service_started=*/std::optional<bool>)>;
+
+// Checks whether the service `name` has an owner on the bus and runs `callback`
+// with this boolean value. Attempts to autostart the service if possible.
+// False is passed to the callback if the service is not activatable or failed to
+// start. True is passed if the service is already running or was auto-started.
+// Any DBus errors are indicated as nullopt.
+COMPONENT_EXPORT(DBUS)
+void CheckForServiceAndStart(scoped_refptr<dbus::Bus> bus,
+ const std::string& name,
+ CheckForServiceAndStartCallback callback);
+
+} // namespace dbus_utils
+
+#endif // COMPONENTS_DBUS_UTILS_CHECK_FOR_SERVICE_AND_START_H_
diff --git a/components/dbus/utils/check_for_service_and_start_unittest.cc b/components/dbus/utils/check_for_service_and_start_unittest.cc
new file mode 100644
index 0000000..94f161d
--- /dev/null
+++ b/components/dbus/utils/check_for_service_and_start_unittest.cc
@@ -0,0 +1,192 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/dbus/utils/check_for_service_and_start.h"
+
+#include "base/functional/bind.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/mock_bus.h"
+#include "dbus/mock_object_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+namespace dbus_utils {
+namespace {
+
+MATCHER_P2(MatchMethod, interface, member, "") {
+ return arg->GetInterface() == interface && arg->GetMember() == member;
+}
+
+class CheckForServiceAndStartTest : public testing::Test {
+ public:
+ void SetUp() override {
+ mock_bus_ = base::MakeRefCounted<dbus::MockBus>(dbus::Bus::Options());
+
+ mock_dbus_proxy_ = base::MakeRefCounted<dbus::MockObjectProxy>(
+ mock_bus_.get(), DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS));
+
+ EXPECT_CALL(*mock_bus_, GetObjectProxy(DBUS_SERVICE_DBUS,
+ dbus::ObjectPath(DBUS_PATH_DBUS)))
+ .WillRepeatedly(Return(mock_dbus_proxy_.get()));
+ }
+
+ void TearDown() override {
+ mock_bus_.reset();
+ mock_dbus_proxy_.reset();
+ }
+
+ protected:
+ void ExpectNameHasOwner(const std::string& service_name,
+ bool has_owner,
+ bool error = false) {
+ EXPECT_CALL(
+ *mock_dbus_proxy_,
+ DoCallMethod(MatchMethod(DBUS_INTERFACE_DBUS, "NameHasOwner"), _, _))
+ .WillOnce(Invoke([this, service_name, has_owner, error](
+ dbus::MethodCall* method_call, int timeout_ms,
+ dbus::ObjectProxy::ResponseCallback* callback) {
+ dbus::MessageReader reader(method_call);
+ std::string received_service_name;
+ EXPECT_TRUE(reader.PopString(&received_service_name));
+ EXPECT_EQ(received_service_name, service_name);
+
+ if (error) {
+ // Simulate an error by calling the callback with a null response
+ task_environment_.GetMainThreadTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(*callback), nullptr));
+ } else {
+ response_ = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response_.get());
+ writer.AppendBool(has_owner);
+ task_environment_.GetMainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(*callback), response_.get()));
+ }
+ }));
+ }
+
+ void ExpectListActivatableNames(
+ const std::vector<std::string>& activatable_names) {
+ EXPECT_CALL(
+ *mock_dbus_proxy_,
+ DoCallMethod(MatchMethod(DBUS_INTERFACE_DBUS, "ListActivatableNames"),
+ _, _))
+ .WillOnce(Invoke([this, activatable_names](
+ dbus::MethodCall* method_call, int timeout_ms,
+ dbus::ObjectProxy::ResponseCallback* callback) {
+ response_ = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response_.get());
+ writer.AppendArrayOfStrings(activatable_names);
+ task_environment_.GetMainThreadTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(*callback), response_.get()));
+ }));
+ }
+
+ void ExpectStartServiceByName(const std::string& service_name,
+ uint32_t reply_code) {
+ EXPECT_CALL(
+ *mock_dbus_proxy_,
+ DoCallMethod(MatchMethod(DBUS_INTERFACE_DBUS, "StartServiceByName"), _,
+ _))
+ .WillOnce(Invoke([this, service_name, reply_code](
+ dbus::MethodCall* method_call, int timeout_ms,
+ dbus::ObjectProxy::ResponseCallback* callback) {
+ dbus::MessageReader reader(method_call);
+ std::string received_service_name;
+ uint32_t flags;
+ EXPECT_TRUE(reader.PopString(&received_service_name));
+ EXPECT_TRUE(reader.PopUint32(&flags));
+ EXPECT_EQ(received_service_name, service_name);
+ EXPECT_EQ(flags, 0U);
+
+ response_ = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response_.get());
+ writer.AppendUint32(reply_code);
+ task_environment_.GetMainThreadTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(*callback), response_.get()));
+ }));
+ }
+
+ void RunCheckForServiceAndStart(const std::string& service_name,
+ std::optional<bool>* service_started_out) {
+ base::RunLoop run_loop;
+ CheckForServiceAndStartCallback callback = base::BindOnce(
+ [](std::optional<bool>* service_started_out,
+ base::OnceClosure quit_closure,
+ std::optional<bool> service_started_in) {
+ *service_started_out = service_started_in;
+ std::move(quit_closure).Run();
+ },
+ service_started_out, run_loop.QuitClosure());
+
+ CheckForServiceAndStart(mock_bus_, service_name, std::move(callback));
+
+ run_loop.Run();
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ std::unique_ptr<dbus::Response> response_;
+ scoped_refptr<dbus::MockBus> mock_bus_;
+ scoped_refptr<dbus::MockObjectProxy> mock_dbus_proxy_;
+};
+
+TEST_F(CheckForServiceAndStartTest, ServiceAlreadyRunning) {
+ const std::string kServiceName = "org.example.Service";
+
+ ExpectNameHasOwner(kServiceName, true);
+
+ std::optional<bool> service_started;
+ RunCheckForServiceAndStart(kServiceName, &service_started);
+
+ EXPECT_TRUE(service_started.has_value());
+ EXPECT_TRUE(service_started.value());
+}
+
+TEST_F(CheckForServiceAndStartTest, ServiceNotRunningButActivatable) {
+ const std::string kServiceName = "org.example.Service";
+
+ ExpectNameHasOwner(kServiceName, false);
+ ExpectListActivatableNames({kServiceName, "org.other.Service"});
+ ExpectStartServiceByName(kServiceName, DBUS_START_REPLY_SUCCESS);
+
+ std::optional<bool> service_started;
+ RunCheckForServiceAndStart(kServiceName, &service_started);
+
+ ASSERT_TRUE(service_started.has_value());
+ EXPECT_TRUE(service_started.value());
+}
+
+TEST_F(CheckForServiceAndStartTest, ServiceNotRunningAndNotActivatable) {
+ const std::string kServiceName = "org.example.Service";
+
+ ExpectNameHasOwner(kServiceName, false);
+ ExpectListActivatableNames({"org.other.Service"});
+
+ std::optional<bool> service_started;
+ RunCheckForServiceAndStart(kServiceName, &service_started);
+
+ ASSERT_TRUE(service_started.has_value());
+ EXPECT_FALSE(service_started.value());
+}
+
+TEST_F(CheckForServiceAndStartTest, NameHasOwnerError) {
+ const std::string kServiceName = "org.example.Service";
+
+ ExpectNameHasOwner(kServiceName, false, true); // Simulate an error
+
+ std::optional<bool> service_started;
+ RunCheckForServiceAndStart(kServiceName, &service_started);
+
+ EXPECT_FALSE(service_started.has_value());
+}
+
+} // namespace
+} // namespace dbus_utils