Add basic event listener plugin support 13/321413/8
authorMichal Bloch <m.bloch@samsung.com>
Wed, 19 Mar 2025 15:54:31 +0000 (16:54 +0100)
committerMichal Bloch <m.bloch@samsung.com>
Thu, 27 Mar 2025 12:39:13 +0000 (13:39 +0100)
Change-Id: I766a35b341c89d14eeae96f16d31ea65b91ac27e

packaging/sessiond.spec
src/service/CMakeLists.txt
src/service/src/main_context.hpp
src/service/src/plugin.cpp [new file with mode: 0644]
src/service/src/plugin.hpp [new file with mode: 0644]

index 4f9e842e6c7401096d91420a49d197d3bed5dfc5..6756610178be0cdb82df95efc4e6d12a5bc74328 100644 (file)
@@ -12,6 +12,7 @@ BuildRequires: pkgconfig(gio-2.0)
 BuildRequires: gtest-devel
 BuildRequires: pkgconfig(libsmack)
 BuildRequires: pkgconfig(dlog)
+BuildRequires: pkgconfig(libsyscommon-plugin-api-sessiond)
 %description
 
 %package -n libsessiond
index d5dced87b79ee26c4616ddf58d7cd66eba0bed74..5d24aa92821977eda9be314a854593c87a11590b 100644 (file)
@@ -3,6 +3,7 @@ pkg_check_modules(DEPS REQUIRED IMPORTED_TARGET
        gio-2.0
        libsmack
        dlog
+       libsyscommon-plugin-api-sessiond
 )
 
 set(
@@ -10,6 +11,7 @@ set(
        src/main.cpp
        src/fs_helpers.cpp
        src/os_ops.cpp
+       src/plugin.cpp
        src/dir_backend_regular_dir.cpp
        src/dir_backend_fixed_size.cpp
        src/tuple_g_variant_helpers.hpp
index 3ff3d287181decb6472a40a29e3dc18e24f85dd4..be8e1c7ea9c4ddcf0996efe57612396da362aafd 100644 (file)
@@ -38,6 +38,7 @@
 #include "tuple_g_variant_helpers.hpp"
 #include "tuple_hash.hpp"
 #include "wait_manager.hpp"
+#include "plugin.hpp"
 #include "dir_backend_regular_dir.hpp"
 #include "dir_backend_fixed_size.hpp"
 
@@ -121,7 +122,7 @@ struct main_loop {
 };
 
 struct sessiond_context {
-       sessiond_context() : data(xml), id(), loop()
+       sessiond_context() : data(xml), id(), loop(), plugin()
        {
                connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
                if (!connection)
@@ -185,6 +186,8 @@ struct sessiond_context {
 
                add_user_subsession(session_uid, subsession_id, backend);
 
+               plugin.OnSubsessionAdded(session_uid, subsession_id);
+
                wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
                wait_add.at(session_uid).on_start(subsession_id, { });
        }
@@ -198,6 +201,8 @@ struct sessiond_context {
 
                remove_user_subsession(session_uid, subsession_id);
 
+               plugin.OnSubsessionRemoved(session_uid, subsession_id);
+
                wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
                wait_remove.at(session_uid).on_start(subsession_id, { });
        }
@@ -218,6 +223,8 @@ struct sessiond_context {
                if (!switch_user_subsession(session_uid, prev_subsession_id, next_subsession_id))
                        return false;
 
+               plugin.OnSubsessionChanged(session_uid, prev_subsession_id, next_subsession_id);
+
                last_subsession_per_session[session_uid] = next_subsession_id;
 
                wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
@@ -612,6 +619,12 @@ struct sessiond_context {
        owner_id id;
        main_loop loop;
        GDBusConnection *connection = nullptr;
+
+       /* The plugin is always loaded alongside context (i.e. at start).
+        * Lazy-loading was considered but given that sessiond is activated
+        * via D-Bus and the plugin listens on common events we can assume
+        * that it will ~always be promptly needed anyway. */
+       Plugin plugin;
 };
 
 /* The struct is effectively a singleton, but all the usual code (getter etc)
diff --git a/src/service/src/plugin.cpp b/src/service/src/plugin.cpp
new file mode 100644 (file)
index 0000000..7ffbfc1
--- /dev/null
@@ -0,0 +1,130 @@
+/* MIT License
+ *
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE. */
+
+#include "plugin.hpp"
+
+#include <stdexcept>
+#include <string>
+
+#undef LOG_TAG
+#define LOG_TAG "SESSIOND"
+#include <dlog.h>
+
+#include <system/syscommon-plugin-sessiond.h>
+#include <system/syscommon-plugin-sessiond-interface.h>
+
+using namespace std::string_literals;
+
+static bool isPluginCallOK(int ret)
+{
+       if (ret == 0)
+               return true;
+
+       /* Returned when the plugin does not supply given
+        * function, which is fine - they're all optional. */
+       if (ret == -ENOTSUP)
+               return true;
+
+       return false;
+}
+
+Plugin::Plugin ()
+       : active (false)
+{
+       /* The libsyscommon plugin API is pretty terrible. There is
+        * no handle or anything, just global reference tracking that
+        * you are supposed to keep track of yourself (since there's
+        * no interface that observes it without modifying) and that
+        * may not necessarily activate when you ask it to (if the
+        * `plugin.so` file is missing, which there is no way to tell
+        * without also activating it if present). */
+
+       int plugin_exists = 0;
+       if (const auto r = syscommon_plugin_sessiond_get_backend(&plugin_exists); !isPluginCallOK(r))
+               throw std::runtime_error("sessiond plugin get_backend failed! error "s + std::to_string(r));
+
+       if (!plugin_exists) {
+               active = false;
+               return;
+       }
+
+       if (const auto r = syscommon_plugin_sessiond_initialize(); !isPluginCallOK(r)) {
+               const auto r2 = syscommon_plugin_sessiond_put_backend();
+               if (!isPluginCallOK(r2)) {
+                       LOGF("Plugin recursive failure: put_backend error (%d) within initialize error handling", r2);
+                       /* Continue, there's nothing to salvage and we're throwing an error anyway.
+                        * Tizen can force-crash a program on fatal-level logs which is also okay. */
+               }
+               throw std::runtime_error("sessiond plugin initialize failed! error "s + std::to_string(r));
+       }
+
+       active = true;
+}
+
+Plugin::~Plugin ()
+{
+       if (!active)
+               return;
+
+       if (const int r = syscommon_plugin_sessiond_deinitialize(); !isPluginCallOK(r)) {
+               LOGE("syscommon_plugin_sessiond_deinitialize failed (%d)!", r);
+               /* We can't really do anything and we have to release
+                * the backend anyway below, so ignore and continue.
+                * This is a destructor so we can't safely throw. */
+       }
+
+       if (const int r = syscommon_plugin_sessiond_put_backend(); !isPluginCallOK(r)) {
+               LOGE("syscommon_plugin_sessiond_put_backend failed (%d)!", r);
+               /* On some level this doesn't matter much either way (would
+                * only be called at program exit but sessiond is immortal). */
+       }
+}
+
+void Plugin::OnSubsessionChanged(int uid, const std::string& from, const std::string& to)
+{
+       if (!active)
+               return;
+
+       const int r = syscommon_plugin_sessiond_subsession_changed(uid, from.c_str(), to.c_str());
+       if (!isPluginCallOK(r))
+               throw std::runtime_error("sessiond plugin subsession_changed(" + std::to_string(uid) + ", " + from + " -> " + to +") callback failed! error "s + std::to_string(r));
+}
+
+void Plugin::OnSubsessionAdded(int uid, const std::string& subsession)
+{
+       if (!active)
+               return;
+
+       const int r = syscommon_plugin_sessiond_subsession_added(uid, subsession.c_str());
+       if (!isPluginCallOK(r))
+               throw std::runtime_error("sessiond plugin subsession_added(" + std::to_string(uid) + ", " + subsession +") callback failed! error "s + std::to_string(r));
+}
+
+void Plugin::OnSubsessionRemoved(int uid, const std::string& subsession)
+{
+       if (!active)
+               return;
+
+       const int r = syscommon_plugin_sessiond_subsession_removed(uid, subsession.c_str());
+       if (!isPluginCallOK(r))
+               throw std::runtime_error("sessiond plugin subsession_removed(" + std::to_string(uid) + ", " + subsession +") callback failed! error "s + std::to_string(r));
+}
diff --git a/src/service/src/plugin.hpp b/src/service/src/plugin.hpp
new file mode 100644 (file)
index 0000000..573e91e
--- /dev/null
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <memory>
+#include <string>
+
+/* Plugins let sessiond itself execute trivial events, which makes it
+ * possible to avoid the overhead of having a dedicated process and
+ * going through IPC.
+ *
+ * The design assumes only one platform-specific plugin, which helps
+ * mitigate security issues (since sessiond has very high privileges)
+ * which is why the constructor etc aren't parameterized. */
+
+class Plugin final
+{
+       /* The plugin is optional (and using `std::optional` would
+        * break RAII without some overengineered workarounds). */
+       bool active;
+
+       /* The type as we use it is a quasi-singleton, don't bother. */
+       Plugin             (Plugin &&)      = delete;
+       Plugin             (const Plugin &) = delete;
+       Plugin& operator = (Plugin &&)      = delete;
+       Plugin& operator = (const Plugin &) = delete;
+
+public:
+       Plugin ();
+       ~ Plugin ();
+
+       /* Sessiond is single-threaded so a plugin that takes too long can risk starving any other D-Bus calls
+        * waiting to be handled (this applies to anything sessiond does, but the plugin is external so there
+        * are no checks/limitations on what it is going to do). The current interface does not allow any sort
+        * of parallelisation because some events don't make sense without sequentiality (e.g. Remove before
+        * the Add completes) and there is no preliminary response mechanism (i.e. "sessiond received the call
+        * but is queuing it up for later because something is taking too long"). What this all means is that
+        * the plugin must work reasonably fast and we work off that assumption - the interface is just normal
+        * function calls with no extra checks or synchronisation. */
+       void OnSubsessionChanged(int uid, const std::string& from, const std::string& to);
+       void OnSubsessionAdded(int uid, const std::string& subsession);
+       void OnSubsessionRemoved(int uid, const std::string& subsession);
+};