#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"
};
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)
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, { });
}
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, { });
}
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");
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)
--- /dev/null
+/* 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));
+}
--- /dev/null
+#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);
+};