Move struct defs out of main.cpp 01/321601/2
authorMichal Bloch <m.bloch@samsung.com>
Tue, 25 Mar 2025 20:11:10 +0000 (21:11 +0100)
committerMichal Bloch <m.bloch@samsung.com>
Wed, 26 Mar 2025 16:31:24 +0000 (17:31 +0100)
Plugin will want to know the context definition to call work funcs,
so the definitions need to be in a header.

Make some other header functions `inline` to allow multiple inclusion.

Change-Id: Iba0cf4c15f139a47d808ee1fdfaa38e4e64ec4e7

src/service/src/main.cpp
src/service/src/main_context.hpp [new file with mode: 0644]
src/service/src/tuple_g_variant_helpers.hpp

index d7281e42795fd138cf8f270c3873840ec3afed1d..6be66415a4d930c0ab913b0effdfb2dfa9e5831b 100644 (file)
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE. */
 
-#undef LOG_TAG
-#define LOG_TAG "SESSIOND"
-#include <dlog.h>
-
-#include <algorithm>
-#include <unordered_map>
-
-#include <gio/gio.h>
-
-#include "sessiond-internal.h"
-#include "globals.hpp"
-#include "fs_helpers.hpp"
-#include "tuple_g_variant_helpers.hpp"
-#include "tuple_hash.hpp"
-#include "wait_manager.hpp"
-#include "dir_backend_regular_dir.hpp"
-#include "dir_backend_fixed_size.hpp"
-
-static constexpr const char * get_dbus_error_mapping (subsession_error_e subsession_error)
-{
-       /* I want this function to be evaluated at compile time
-        * and `std::find_if` is not constexpr/consteval-friendly,
-        * so caveman style iteration it is. */
-
-       const char *ret = nullptr;
-       for (const auto & x : error_mappings)
-               if (x.second == subsession_error)
-                       ret = x.first;
-
-       return ret ?: throw std::logic_error("Compile time get_dbus_error_mapping error");
-}
-
-using namespace std::string_view_literals;
-
-static bool is_subsession_size_limit_valid (unsigned size_kB)
-{
-       static constexpr int MINIMUM_FOR_EXT2_OVERHEAD = 220; // determined empirically
-       if (size_kB < MINIMUM_FOR_EXT2_OVERHEAD)
-               return false;
-
-       return true;
-}
-
-struct introspection_data {
-       introspection_data(std::string_view xml)
-       {
-               inner = g_dbus_node_info_new_for_xml(xml.data(), nullptr);
-               if (inner == nullptr)
-                       throw std::runtime_error("Couldn't parse introspection data");
-       }
-
-       ~introspection_data()
-       {
-               if (inner)
-                       g_dbus_node_info_unref(inner);
-       }
-
-       // Because of the rule of three, we also have to delete these.
-       introspection_data(const introspection_data &) = delete;
-       introspection_data &operator=(const introspection_data &) = delete;
-
-       GDBusNodeInfo *inner;
-};
-
-struct owner_id {
-       owner_id() : inner(0) {}
-
-       void own_name_on_connection(GDBusConnection *connection, std::string_view name,
-               GBusNameAcquiredCallback on_name_acquired, GBusNameLostCallback on_name_lost, gpointer user_data)
-       {
-               inner = g_bus_own_name_on_connection(connection, name.data(), G_BUS_NAME_OWNER_FLAGS_NONE,
-                       on_name_acquired, on_name_lost, user_data, nullptr);
-               LOGD("Acquiring %s", name.data());
-       }
-
-       ~owner_id()
-       {
-               if (inner != 0)
-                       g_bus_unown_name(inner);
-       }
-
-       // Because of the rule of three, we also have to delete these.
-       owner_id(const owner_id &) = delete;
-       owner_id &operator=(const owner_id &) = delete;
-
-       guint inner;
-};
-
-struct main_loop {
-       main_loop()
-       {
-               inner = g_main_loop_new(nullptr, false);
-       }
-
-       ~main_loop()
-       {
-               g_main_loop_unref(inner);
-       }
-
-       // Because of the rule of three, we also have to delete these.
-       main_loop(const main_loop &) = delete;
-       main_loop &operator=(const main_loop &) = delete;
-
-       GMainLoop *inner;
-};
-
-struct sessiond_context {
-       sessiond_context() : data(xml), id(), loop()
-       {
-               connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
-               if (!connection)
-                       throw std::runtime_error("Cannot connect to the system bus");
-
-               guint registration_id = g_dbus_connection_register_object(
-                       connection, bus_object.data(), data.inner->interfaces[0], &table, this, nullptr, nullptr);
-               if (registration_id <= 0)
-                       throw std::runtime_error("Couldn't register bus ID");
-
-               id.own_name_on_connection(connection, bus_name, glib_name_acquired, glib_name_lost, this);
-       }
-
-       void run()
-       {
-               g_main_loop_run(loop.inner);
-       }
-
-       void on_name_acquired()
-       {
-               LOGD("Bus name acquired");
-       }
-
-       void on_name_lost()
-       {
-               throw std::runtime_error("Bus name lost");
-       }
-
-       bool check_parameters_invalid(GDBusMethodInvocation *invocation,
-               const int session_uid, const std::string_view subsession_id)
-       {
-               if (session_uid <= 0) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
-                       return true;
-               }
-
-               if (!check_subsession_id_valid(subsession_id)) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Incorrect subsession_id passed");
-                       return true;
-               }
-
-               return false;
-       }
-
-       void do_add_user(int session_uid, const std::string& subsession_id, const DirBackendAdd& backend)
-       {
-               GError *err = nullptr;
-               if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), "AddUserStarted",
-                               vals_to_g_variant(session_uid, subsession_id), &err))
-                       g_error_throw(err, "Failed to emit a signal: ");
-
-               add_user_subsession(session_uid, subsession_id, backend);
-
-               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
-               wait_add.at(session_uid).on_start(subsession_id, { });
-       }
-
-       void do_remove_user(int session_uid, const std::string& subsession_id)
-       {
-               GError *err = nullptr;
-               if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), "RemoveUserStarted",
-                               vals_to_g_variant(session_uid, subsession_id), &err))
-                       g_error_throw(err, "Failed to emit a signal: ");
-
-               remove_user_subsession(session_uid, subsession_id);
-
-               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
-               wait_remove.at(session_uid).on_start(subsession_id, { });
-       }
-
-       bool do_switch_user(int session_uid, const std::string& next_subsession_id)
-       {
-               switch_id += 1;
-
-               std::string prev_subsession_id = SUBSESSION_INITIAL_SID;
-               if (last_subsession_per_session.contains(session_uid))
-                       prev_subsession_id = last_subsession_per_session.at(session_uid);
-
-               GError *err = nullptr;
-               if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), "SwitchUserStarted",
-                               vals_to_g_variant(session_uid, switch_id, prev_subsession_id, next_subsession_id), &err))
-                       g_error_throw(err, "Failed to emit a signal: ");
-
-               if (!switch_user_subsession(session_uid, prev_subsession_id, next_subsession_id))
-                       return false;
-
-               last_subsession_per_session[session_uid] = next_subsession_id;
-
-               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
-               wait_switch.at(session_uid).on_start(switch_id, { std::string(prev_subsession_id), next_subsession_id });
-
-               return true;
-       }
-
-       void on_add_user(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
-
-               if (check_parameters_invalid(invocation, session_uid, subsession_id))
-                       return;
-
-               do_add_user(session_uid, subsession_id, DirBackendAddRegularDir {});
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_add_user_fixed_size(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid, subsession_id, size_kB ] = tuple_from_g_variant<int, std::string, unsigned>(parameters);
-
-               if (check_parameters_invalid(invocation, session_uid, subsession_id))
-                       return;
-               if (!is_subsession_size_limit_valid(size_kB)) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Size too small");
-                       return;
-               }
-
-               do_add_user(session_uid, subsession_id, DirBackendAddFixedSize {size_kB});
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_remove_user(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
-
-               if (check_parameters_invalid(invocation, session_uid, subsession_id))
-                       return;
-
-               std::string_view current_subsession_id = SUBSESSION_INITIAL_SID;
-               if (last_subsession_per_session.contains(session_uid))
-                       current_subsession_id = last_subsession_per_session.at(session_uid);
-               if (subsession_id == current_subsession_id) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_RESOURCE_BUSY), "Cannot remove currently active user");
-                       return;
-               }
-
-               do_remove_user(session_uid, subsession_id);
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_switch_user(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid, next_subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
-
-               if (session_uid <= 0) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
-                       return;
-               }
-
-               // N.B. Switch to user "" (empty string) is possible and it means no subsession is currently active
-               if (next_subsession_id != SUBSESSION_INITIAL_SID && !check_subsession_id_valid(next_subsession_id)) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Incorrect subsession_id passed");
-                       return;
-               }
-               if (next_subsession_id != SUBSESSION_INITIAL_SID && !subsession_exists(session_uid, next_subsession_id)) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_NOT_AVAILABLE), "Subsession does not exist");
-                       return;
-               }
-
-               try {
-                       if (do_switch_user(session_uid, next_subsession_id))
-                               g_dbus_method_invocation_return_value(invocation, nullptr);
-                       else
-                               g_dbus_method_invocation_return_dbus_error(invocation,
-                                       get_dbus_error_mapping(SUBSESSION_ERROR_IO_ERROR), "Failed to switch away from previous subsession");
-               } catch (...) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_IO_ERROR), "Failed to switch away from previous subsession");
-                       throw;
-               }
-       }
-
-       void on_add_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
-
-               if (session_uid <= 0) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
-                       return;
-               }
-
-               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
-               wait_add.at(session_uid).on_client_register(std::string(sender));
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_remove_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
-
-               if (session_uid <= 0) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
-                       return;
-               }
-
-               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
-               wait_remove.at(session_uid).on_client_register(std::string(sender));
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_switch_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
-
-               if (session_uid <= 0) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
-                       return;
-               }
-
-               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
-               wait_switch.at(session_uid).on_client_register(std::string(sender));
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_add_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
-
-               if (check_parameters_invalid(invocation, session_uid, subsession_id))
-                       return;
-
-               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
-               wait_add.at(session_uid).on_client_done(subsession_id, std::string(sender));
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_remove_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
-
-               if (check_parameters_invalid(invocation, session_uid, subsession_id))
-                       return;
-
-               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
-               wait_remove.at(session_uid).on_client_done(subsession_id, std::string(sender));
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_switch_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid, switch_id ] = tuple_from_g_variant<int, uint64_t>(parameters);
-
-               if (session_uid <= 0) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
-                       return;
-               }
-
-               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
-               wait_switch.at(session_uid).on_client_done(switch_id, std::string(sender));
-
-               g_dbus_method_invocation_return_value(invocation, nullptr);
-       }
-
-       void on_get_user_list(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
-
-               if (session_uid <= 0) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
-                       return;
-               }
-
-               auto users = get_user_list(session_uid);
-
-               // TODO: It would be cool to be able to use vals_to_g_variant here.
-               g_autoptr(GVariantBuilder) builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
-               for (const auto &user : users)
-                       g_variant_builder_add(builder, "s", user.c_str());
-               auto ret = g_variant_new("(as)", builder);
-
-               g_dbus_method_invocation_return_value(invocation, ret);
-       }
-
-       void on_get_current_user(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
-       {
-               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
-
-               if (session_uid <= 0) {
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
-                       return;
-               }
-
-               std::string subsession_id = SUBSESSION_INITIAL_SID;
-               if (last_subsession_per_session.contains(session_uid))
-                       subsession_id = last_subsession_per_session.at(session_uid);
-
-               auto ret = vals_to_g_variant(subsession_id);
-               g_dbus_method_invocation_return_value(invocation, ret);
-       }
-
-       static void glib_name_acquired(GDBusConnection *conn, const gchar *name, gpointer user_data)
-       {
-               auto self = static_cast<sessiond_context *>(user_data);
-               self->on_name_acquired();
-       }
-
-       static void glib_name_lost(GDBusConnection *conn, const gchar *name, gpointer user_data)
-       {
-               auto self = static_cast<sessiond_context *>(user_data);
-               self->on_name_lost();
-       }
-       static void glib_method_call(GDBusConnection *conn, const gchar *sender, const gchar *object_path,
-                                    const gchar *interface_name, const gchar *method_name, GVariant *parameters,
-                                    GDBusMethodInvocation *invocation, gpointer user_data)
-       try { // TODO: Also dispatch on interface_name (not needed at the moment)
-               auto self = static_cast<sessiond_context *>(user_data);
-               auto to_call = std::find_if(methods.begin(), methods.end(), [&](auto pair) {
-                       return pair.first == method_name;
-               });
-               if (to_call == methods.end())
-                       throw std::runtime_error(std::string("Unknown method ") + method_name + " called");
-               LOGD("Handling %s call from %s", method_name, sender);
-               (self->*(to_call->second))(invocation, std::string_view(sender), parameters);
-       } catch (const std::invalid_argument &ex) {
-               g_dbus_method_invocation_return_dbus_error(invocation,
-                       get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), ex.what());
-               log_exception(ex, sender, method_name);
-       } catch (const std::system_error &ex) {
-               switch (ex.code().value()) {
-               case EEXIST:
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_ALREADY_EXISTS), ex.what());
-                       break;
-               case ENOENT:
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_NOT_AVAILABLE), ex.what());
-                       break;
-               default:
-                       g_dbus_method_invocation_return_dbus_error(invocation,
-                               get_dbus_error_mapping(SUBSESSION_ERROR_IO_ERROR),
-                               (std::string("Unable to complete requested operation: ") + ex.what()).c_str());
-                       break;
-               }
-               log_exception(ex, sender, method_name);
-       } catch (const std::runtime_error &ex) {
-               g_dbus_method_invocation_return_dbus_error(invocation,
-                       get_dbus_error_mapping(SUBSESSION_ERROR_IO_ERROR),
-                       (std::string("Unable to complete requested operation: ") + ex.what()).c_str());
-               log_exception(ex, sender, method_name);
-               // Swallow the exception; the show must go on
-       }
-       static void log_exception(const std::exception &ex, std::string_view sender, std::string_view method_name)
-       {
-               LOGE("Exception %s\nwhile handling %s call from %s", ex.what(), method_name.data(), sender.data());
-       }
-
-       constexpr static GDBusInterfaceVTable table = {
-               .method_call = sessiond_context::glib_method_call,
-               .get_property = NULL,
-               .set_property = NULL,
-               .padding = {0,},
-       };
-
-       constexpr static std::string_view xml =
-               "<node name=\"/org/tizen/sessiond\">"
-                       "<interface name=\"org.tizen.sessiond.subsession.Manager\">"
-                               "<method name=\"AddUser\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"AddUserFixedSize\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
-                                       "<arg name=\"size_kB\"            type=\"u\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"RemoveUser\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"SwitchUser\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"next_subsession_id\" type=\"s\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"AddUserWait\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"RemoveUserWait\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"SwitchUserWait\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"AddUserDone\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"RemoveUserDone\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"SwitchUserDone\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"switch_id\"          type=\"x\" direction=\"in\"/>"
-                               "</method>"
-                               "<method name=\"GetUserList\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"subsession_id_list\" type=\"as\" direction=\"out\"/>"
-                               "</method>"
-                               "<method name=\"GetCurrentUser\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
-                               "</method>"
-                               "<signal name=\"AddUserStarted\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
-                               "</signal>"
-                               "<signal name=\"RemoveUserStarted\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
-                               "</signal>"
-                               "<signal name=\"SwitchUserStarted\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
-                                       "<arg name=\"switch_id\"          type=\"x\" direction=\"out\"/>"
-                                       "<arg name=\"prev_subsession_id\" type=\"s\" direction=\"out\"/>"
-                                       "<arg name=\"next_subsession_id\" type=\"s\" direction=\"out\"/>"
-                               "</signal>"
-                               "<signal name=\"AddUserCompleted\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
-                               "</signal>"
-                               "<signal name=\"RemoveUserCompleted\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
-                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
-                               "</signal>"
-                               "<signal name=\"SwitchUserCompleted\">"
-                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
-                                       "<arg name=\"switch_id\"          type=\"x\" direction=\"out\"/>"
-                                       "<arg name=\"prev_subsession_id\" type=\"s\" direction=\"out\"/>"
-                                       "<arg name=\"next_subsession_id\" type=\"s\" direction=\"out\"/>"
-                               "</signal>"
-                       "</interface>"
-               "</node>";
-
-       constexpr static std::array methods = {
-               std::make_pair(         "AddUser"sv, &sessiond_context::on_add_user           ),
-               std::make_pair("AddUserFixedSize"sv, &sessiond_context::on_add_user_fixed_size),
-               std::make_pair(      "RemoveUser"sv, &sessiond_context::on_remove_user        ),
-               std::make_pair(      "SwitchUser"sv, &sessiond_context::on_switch_user        ),
-               std::make_pair(     "AddUserWait"sv, &sessiond_context::on_add_user_wait      ),
-               std::make_pair(  "RemoveUserWait"sv, &sessiond_context::on_remove_user_wait   ),
-               std::make_pair(  "SwitchUserWait"sv, &sessiond_context::on_switch_user_wait   ),
-               std::make_pair(     "AddUserDone"sv, &sessiond_context::on_add_user_done      ),
-               std::make_pair(  "RemoveUserDone"sv, &sessiond_context::on_remove_user_done   ),
-               std::make_pair(  "SwitchUserDone"sv, &sessiond_context::on_switch_user_done   ),
-               std::make_pair(     "GetUserList"sv, &sessiond_context::on_get_user_list      ),
-               std::make_pair(  "GetCurrentUser"sv, &sessiond_context::on_get_current_user   ),
-       };
-
-       // TODO: Currently, the first parameter is always a single-element tuple.
-       // Consider simplifying wait_manager.
-       // N.B. Although GLib is multi-threaded, the following data structures do not need
-       // to be protected by mutexes as all of them are accessed from DBus handlers only.
-       std::unordered_map<int, wait_manager<std::tuple<std::string>, std::tuple<>>> wait_add;
-       std::unordered_map<int, wait_manager<std::tuple<std::string>, std::tuple<>>> wait_remove;
-       std::unordered_map<int, wait_manager<std::tuple<uint64_t>, std::tuple<std::string, std::string>>> wait_switch;
-
-       // used for tracking subsession switches for each `session_uid`
-       std::unordered_map<int, std::string> last_subsession_per_session;
-
-       uint64_t switch_id = 0;
-
-       introspection_data data;
-       owner_id id;
-       main_loop loop;
-       GDBusConnection *connection = nullptr;
-};
+#include "main_context.hpp"
+#include <stdexcept>
 
 int main() try {
        sessiond_context().run();
diff --git a/src/service/src/main_context.hpp b/src/service/src/main_context.hpp
new file mode 100644 (file)
index 0000000..7ab23d6
--- /dev/null
@@ -0,0 +1,618 @@
+/* MIT License
+ *
+ * Copyright (c) 2022 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. */
+
+#pragma once
+
+#undef LOG_TAG
+#define LOG_TAG "SESSIOND"
+#include <dlog.h>
+
+#include <algorithm>
+#include <unordered_map>
+
+#include <gio/gio.h>
+
+#include "sessiond-internal.h"
+#include "globals.hpp"
+#include "fs_helpers.hpp"
+#include "tuple_g_variant_helpers.hpp"
+#include "tuple_hash.hpp"
+#include "wait_manager.hpp"
+#include "dir_backend_regular_dir.hpp"
+#include "dir_backend_fixed_size.hpp"
+
+static constexpr const char * get_dbus_error_mapping (subsession_error_e subsession_error)
+{
+       /* I want this function to be evaluated at compile time
+        * and `std::find_if` is not constexpr/consteval-friendly,
+        * so caveman style iteration it is. */
+
+       const char *ret = nullptr;
+       for (const auto & x : error_mappings)
+               if (x.second == subsession_error)
+                       ret = x.first;
+
+       return ret ?: throw std::logic_error("Compile time get_dbus_error_mapping error");
+}
+
+using namespace std::string_view_literals;
+
+static bool is_subsession_size_limit_valid (unsigned size_kB)
+{
+       static constexpr int MINIMUM_FOR_EXT2_OVERHEAD = 220; // determined empirically
+       if (size_kB < MINIMUM_FOR_EXT2_OVERHEAD)
+               return false;
+
+       return true;
+}
+
+struct introspection_data {
+       introspection_data(std::string_view xml)
+       {
+               inner = g_dbus_node_info_new_for_xml(xml.data(), nullptr);
+               if (inner == nullptr)
+                       throw std::runtime_error("Couldn't parse introspection data");
+       }
+
+       ~introspection_data()
+       {
+               if (inner)
+                       g_dbus_node_info_unref(inner);
+       }
+
+       // Because of the rule of three, we also have to delete these.
+       introspection_data(const introspection_data &) = delete;
+       introspection_data &operator=(const introspection_data &) = delete;
+
+       GDBusNodeInfo *inner;
+};
+
+struct owner_id {
+       owner_id() : inner(0) {}
+
+       void own_name_on_connection(GDBusConnection *connection, std::string_view name,
+               GBusNameAcquiredCallback on_name_acquired, GBusNameLostCallback on_name_lost, gpointer user_data)
+       {
+               inner = g_bus_own_name_on_connection(connection, name.data(), G_BUS_NAME_OWNER_FLAGS_NONE,
+                       on_name_acquired, on_name_lost, user_data, nullptr);
+               LOGD("Acquiring %s", name.data());
+       }
+
+       ~owner_id()
+       {
+               if (inner != 0)
+                       g_bus_unown_name(inner);
+       }
+
+       // Because of the rule of three, we also have to delete these.
+       owner_id(const owner_id &) = delete;
+       owner_id &operator=(const owner_id &) = delete;
+
+       guint inner;
+};
+
+struct main_loop {
+       main_loop()
+       {
+               inner = g_main_loop_new(nullptr, false);
+       }
+
+       ~main_loop()
+       {
+               g_main_loop_unref(inner);
+       }
+
+       // Because of the rule of three, we also have to delete these.
+       main_loop(const main_loop &) = delete;
+       main_loop &operator=(const main_loop &) = delete;
+
+       GMainLoop *inner;
+};
+
+struct sessiond_context {
+       sessiond_context() : data(xml), id(), loop()
+       {
+               connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
+               if (!connection)
+                       throw std::runtime_error("Cannot connect to the system bus");
+
+               guint registration_id = g_dbus_connection_register_object(
+                       connection, bus_object.data(), data.inner->interfaces[0], &table, this, nullptr, nullptr);
+               if (registration_id <= 0)
+                       throw std::runtime_error("Couldn't register bus ID");
+
+               id.own_name_on_connection(connection, bus_name, glib_name_acquired, glib_name_lost, this);
+       }
+
+       void run()
+       {
+               g_main_loop_run(loop.inner);
+       }
+
+       void on_name_acquired()
+       {
+               LOGD("Bus name acquired");
+       }
+
+       void on_name_lost()
+       {
+               throw std::runtime_error("Bus name lost");
+       }
+
+       bool check_parameters_invalid(GDBusMethodInvocation *invocation,
+               const int session_uid, const std::string_view subsession_id)
+       {
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
+                       return true;
+               }
+
+               if (!check_subsession_id_valid(subsession_id)) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Incorrect subsession_id passed");
+                       return true;
+               }
+
+               return false;
+       }
+
+       void do_add_user(int session_uid, const std::string& subsession_id, const DirBackendAdd& backend)
+       {
+               GError *err = nullptr;
+               if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), "AddUserStarted",
+                               vals_to_g_variant(session_uid, subsession_id), &err))
+                       g_error_throw(err, "Failed to emit a signal: ");
+
+               add_user_subsession(session_uid, subsession_id, backend);
+
+               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+               wait_add.at(session_uid).on_start(subsession_id, { });
+       }
+
+       void do_remove_user(int session_uid, const std::string& subsession_id)
+       {
+               GError *err = nullptr;
+               if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), "RemoveUserStarted",
+                               vals_to_g_variant(session_uid, subsession_id), &err))
+                       g_error_throw(err, "Failed to emit a signal: ");
+
+               remove_user_subsession(session_uid, subsession_id);
+
+               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+               wait_remove.at(session_uid).on_start(subsession_id, { });
+       }
+
+       bool do_switch_user(int session_uid, const std::string& next_subsession_id)
+       {
+               switch_id += 1;
+
+               std::string prev_subsession_id = SUBSESSION_INITIAL_SID;
+               if (last_subsession_per_session.contains(session_uid))
+                       prev_subsession_id = last_subsession_per_session.at(session_uid);
+
+               GError *err = nullptr;
+               if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), "SwitchUserStarted",
+                               vals_to_g_variant(session_uid, switch_id, prev_subsession_id, next_subsession_id), &err))
+                       g_error_throw(err, "Failed to emit a signal: ");
+
+               if (!switch_user_subsession(session_uid, prev_subsession_id, next_subsession_id))
+                       return false;
+
+               last_subsession_per_session[session_uid] = next_subsession_id;
+
+               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+               wait_switch.at(session_uid).on_start(switch_id, { std::string(prev_subsession_id), next_subsession_id });
+
+               return true;
+       }
+
+       void on_add_user(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
+
+               if (check_parameters_invalid(invocation, session_uid, subsession_id))
+                       return;
+
+               do_add_user(session_uid, subsession_id, DirBackendAddRegularDir {});
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_add_user_fixed_size(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, subsession_id, size_kB ] = tuple_from_g_variant<int, std::string, unsigned>(parameters);
+
+               if (check_parameters_invalid(invocation, session_uid, subsession_id))
+                       return;
+               if (!is_subsession_size_limit_valid(size_kB)) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Size too small");
+                       return;
+               }
+
+               do_add_user(session_uid, subsession_id, DirBackendAddFixedSize {size_kB});
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_remove_user(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
+
+               if (check_parameters_invalid(invocation, session_uid, subsession_id))
+                       return;
+
+               std::string_view current_subsession_id = SUBSESSION_INITIAL_SID;
+               if (last_subsession_per_session.contains(session_uid))
+                       current_subsession_id = last_subsession_per_session.at(session_uid);
+               if (subsession_id == current_subsession_id) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_RESOURCE_BUSY), "Cannot remove currently active user");
+                       return;
+               }
+
+               do_remove_user(session_uid, subsession_id);
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_switch_user(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, next_subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
+                       return;
+               }
+
+               // N.B. Switch to user "" (empty string) is possible and it means no subsession is currently active
+               if (next_subsession_id != SUBSESSION_INITIAL_SID && !check_subsession_id_valid(next_subsession_id)) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Incorrect subsession_id passed");
+                       return;
+               }
+               if (next_subsession_id != SUBSESSION_INITIAL_SID && !subsession_exists(session_uid, next_subsession_id)) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_NOT_AVAILABLE), "Subsession does not exist");
+                       return;
+               }
+
+               try {
+                       if (do_switch_user(session_uid, next_subsession_id))
+                               g_dbus_method_invocation_return_value(invocation, nullptr);
+                       else
+                               g_dbus_method_invocation_return_dbus_error(invocation,
+                                       get_dbus_error_mapping(SUBSESSION_ERROR_IO_ERROR), "Failed to switch away from previous subsession");
+               } catch (...) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_IO_ERROR), "Failed to switch away from previous subsession");
+                       throw;
+               }
+       }
+
+       void on_add_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
+                       return;
+               }
+
+               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+               wait_add.at(session_uid).on_client_register(std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_remove_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
+                       return;
+               }
+
+               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+               wait_remove.at(session_uid).on_client_register(std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_switch_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
+                       return;
+               }
+
+               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+               wait_switch.at(session_uid).on_client_register(std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_add_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
+
+               if (check_parameters_invalid(invocation, session_uid, subsession_id))
+                       return;
+
+               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+               wait_add.at(session_uid).on_client_done(subsession_id, std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_remove_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, std::string>(parameters);
+
+               if (check_parameters_invalid(invocation, session_uid, subsession_id))
+                       return;
+
+               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+               wait_remove.at(session_uid).on_client_done(subsession_id, std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_switch_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, switch_id ] = tuple_from_g_variant<int, uint64_t>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
+                       return;
+               }
+
+               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+               wait_switch.at(session_uid).on_client_done(switch_id, std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_get_user_list(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
+                       return;
+               }
+
+               auto users = get_user_list(session_uid);
+
+               // TODO: It would be cool to be able to use vals_to_g_variant here.
+               g_autoptr(GVariantBuilder) builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
+               for (const auto &user : users)
+                       g_variant_builder_add(builder, "s", user.c_str());
+               auto ret = g_variant_new("(as)", builder);
+
+               g_dbus_method_invocation_return_value(invocation, ret);
+       }
+
+       void on_get_current_user(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), "Negative UID passed");
+                       return;
+               }
+
+               std::string subsession_id = SUBSESSION_INITIAL_SID;
+               if (last_subsession_per_session.contains(session_uid))
+                       subsession_id = last_subsession_per_session.at(session_uid);
+
+               auto ret = vals_to_g_variant(subsession_id);
+               g_dbus_method_invocation_return_value(invocation, ret);
+       }
+
+       static void glib_name_acquired(GDBusConnection *conn, const gchar *name, gpointer user_data)
+       {
+               auto self = static_cast<sessiond_context *>(user_data);
+               self->on_name_acquired();
+       }
+
+       static void glib_name_lost(GDBusConnection *conn, const gchar *name, gpointer user_data)
+       {
+               auto self = static_cast<sessiond_context *>(user_data);
+               self->on_name_lost();
+       }
+       static void glib_method_call(GDBusConnection *conn, const gchar *sender, const gchar *object_path,
+                                    const gchar *interface_name, const gchar *method_name, GVariant *parameters,
+                                    GDBusMethodInvocation *invocation, gpointer user_data)
+       try { // TODO: Also dispatch on interface_name (not needed at the moment)
+               auto self = static_cast<sessiond_context *>(user_data);
+               auto to_call = std::find_if(methods.begin(), methods.end(), [&](auto pair) {
+                       return pair.first == method_name;
+               });
+               if (to_call == methods.end())
+                       throw std::runtime_error(std::string("Unknown method ") + method_name + " called");
+               LOGD("Handling %s call from %s", method_name, sender);
+               (self->*(to_call->second))(invocation, std::string_view(sender), parameters);
+       } catch (const std::invalid_argument &ex) {
+               g_dbus_method_invocation_return_dbus_error(invocation,
+                       get_dbus_error_mapping(SUBSESSION_ERROR_INVALID_PARAMETER), ex.what());
+               log_exception(ex, sender, method_name);
+       } catch (const std::system_error &ex) {
+               switch (ex.code().value()) {
+               case EEXIST:
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_ALREADY_EXISTS), ex.what());
+                       break;
+               case ENOENT:
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_NOT_AVAILABLE), ex.what());
+                       break;
+               default:
+                       g_dbus_method_invocation_return_dbus_error(invocation,
+                               get_dbus_error_mapping(SUBSESSION_ERROR_IO_ERROR),
+                               (std::string("Unable to complete requested operation: ") + ex.what()).c_str());
+                       break;
+               }
+               log_exception(ex, sender, method_name);
+       } catch (const std::runtime_error &ex) {
+               g_dbus_method_invocation_return_dbus_error(invocation,
+                       get_dbus_error_mapping(SUBSESSION_ERROR_IO_ERROR),
+                       (std::string("Unable to complete requested operation: ") + ex.what()).c_str());
+               log_exception(ex, sender, method_name);
+               // Swallow the exception; the show must go on
+       }
+       static void log_exception(const std::exception &ex, std::string_view sender, std::string_view method_name)
+       {
+               LOGE("Exception %s\nwhile handling %s call from %s", ex.what(), method_name.data(), sender.data());
+       }
+
+       constexpr static GDBusInterfaceVTable table = {
+               .method_call = sessiond_context::glib_method_call,
+               .get_property = NULL,
+               .set_property = NULL,
+               .padding = {0,},
+       };
+
+       constexpr static std::string_view xml =
+               "<node name=\"/org/tizen/sessiond\">"
+                       "<interface name=\"org.tizen.sessiond.subsession.Manager\">"
+                               "<method name=\"AddUser\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"AddUserFixedSize\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
+                                       "<arg name=\"size_kB\"            type=\"u\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"RemoveUser\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"SwitchUser\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"next_subsession_id\" type=\"s\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"AddUserWait\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"RemoveUserWait\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"SwitchUserWait\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"AddUserDone\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"RemoveUserDone\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"SwitchUserDone\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"switch_id\"          type=\"x\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"GetUserList\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id_list\" type=\"as\" direction=\"out\"/>"
+                               "</method>"
+                               "<method name=\"GetCurrentUser\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
+                               "</method>"
+                               "<signal name=\"AddUserStarted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
+                               "</signal>"
+                               "<signal name=\"RemoveUserStarted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
+                               "</signal>"
+                               "<signal name=\"SwitchUserStarted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"switch_id\"          type=\"x\" direction=\"out\"/>"
+                                       "<arg name=\"prev_subsession_id\" type=\"s\" direction=\"out\"/>"
+                                       "<arg name=\"next_subsession_id\" type=\"s\" direction=\"out\"/>"
+                               "</signal>"
+                               "<signal name=\"AddUserCompleted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
+                               "</signal>"
+                               "<signal name=\"RemoveUserCompleted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"s\" direction=\"out\"/>"
+                               "</signal>"
+                               "<signal name=\"SwitchUserCompleted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"switch_id\"          type=\"x\" direction=\"out\"/>"
+                                       "<arg name=\"prev_subsession_id\" type=\"s\" direction=\"out\"/>"
+                                       "<arg name=\"next_subsession_id\" type=\"s\" direction=\"out\"/>"
+                               "</signal>"
+                       "</interface>"
+               "</node>";
+
+       constexpr static std::array methods = {
+               std::make_pair(         "AddUser"sv, &sessiond_context::on_add_user           ),
+               std::make_pair("AddUserFixedSize"sv, &sessiond_context::on_add_user_fixed_size),
+               std::make_pair(      "RemoveUser"sv, &sessiond_context::on_remove_user        ),
+               std::make_pair(      "SwitchUser"sv, &sessiond_context::on_switch_user        ),
+               std::make_pair(     "AddUserWait"sv, &sessiond_context::on_add_user_wait      ),
+               std::make_pair(  "RemoveUserWait"sv, &sessiond_context::on_remove_user_wait   ),
+               std::make_pair(  "SwitchUserWait"sv, &sessiond_context::on_switch_user_wait   ),
+               std::make_pair(     "AddUserDone"sv, &sessiond_context::on_add_user_done      ),
+               std::make_pair(  "RemoveUserDone"sv, &sessiond_context::on_remove_user_done   ),
+               std::make_pair(  "SwitchUserDone"sv, &sessiond_context::on_switch_user_done   ),
+               std::make_pair(     "GetUserList"sv, &sessiond_context::on_get_user_list      ),
+               std::make_pair(  "GetCurrentUser"sv, &sessiond_context::on_get_current_user   ),
+       };
+
+       // TODO: Currently, the first parameter is always a single-element tuple.
+       // Consider simplifying wait_manager.
+       // N.B. Although GLib is multi-threaded, the following data structures do not need
+       // to be protected by mutexes as all of them are accessed from DBus handlers only.
+       std::unordered_map<int, wait_manager<std::tuple<std::string>, std::tuple<>>> wait_add;
+       std::unordered_map<int, wait_manager<std::tuple<std::string>, std::tuple<>>> wait_remove;
+       std::unordered_map<int, wait_manager<std::tuple<uint64_t>, std::tuple<std::string, std::string>>> wait_switch;
+
+       // used for tracking subsession switches for each `session_uid`
+       std::unordered_map<int, std::string> last_subsession_per_session;
+
+       uint64_t switch_id = 0;
+
+       introspection_data data;
+       owner_id id;
+       main_loop loop;
+       GDBusConnection *connection = nullptr;
+};
index eb4c52478e38fd0b3894f933326e1ff68975ad8b..aaa35c40124033bce45d92cad851aa03ed5173db 100644 (file)
@@ -84,7 +84,7 @@ constexpr std::string type_of_g_variant()
 }
 
 template <typename T>
-T value_from_g_variant_one_inner(GVariant *variant)
+inline T value_from_g_variant_one_inner(GVariant *variant)
 {
        // This case is disallowed due to lifetime mess potential.
        // Implament at your own responsibility!
@@ -100,13 +100,13 @@ T value_from_g_variant_one_inner(GVariant *variant)
 }
 
 template<>
-std::string value_from_g_variant_one_inner<std::string>(GVariant *variant)
+inline std::string value_from_g_variant_one_inner<std::string>(GVariant *variant)
 {
        return std::string(g_variant_get_string(variant, NULL));
 }
 
 template <size_t I, typename T>
-std::tuple<T> tuple_from_g_variant_inner(GVariant *variant)
+inline std::tuple<T> tuple_from_g_variant_inner(GVariant *variant)
 {
        GVariant *i = g_variant_get_child_value(variant, I);
        T output = value_from_g_variant_one_inner<T>(i);
@@ -114,7 +114,7 @@ std::tuple<T> tuple_from_g_variant_inner(GVariant *variant)
 }
 
 template <size_t I, typename T, typename T1, typename... Ts>
-std::tuple<T, T1, Ts...> tuple_from_g_variant_inner(GVariant *variant)
+inline std::tuple<T, T1, Ts...> tuple_from_g_variant_inner(GVariant *variant)
 {
        GVariant *i = g_variant_get_child_value(variant, I);
        T output = value_from_g_variant_one_inner<T>(i);
@@ -123,7 +123,7 @@ std::tuple<T, T1, Ts...> tuple_from_g_variant_inner(GVariant *variant)
 }
 
 template <typename... Ts>
-std::tuple<Ts...> tuple_from_g_variant(GVariant *variant)
+inline std::tuple<Ts...> tuple_from_g_variant(GVariant *variant)
 {
        GVariantType *type = g_variant_type_new(type_of_g_variant<Ts...>().c_str());
        if (!g_variant_is_of_type(variant, type)) {
@@ -136,7 +136,7 @@ std::tuple<Ts...> tuple_from_g_variant(GVariant *variant)
 }
 
 template <typename T>
-GVariant *value_to_g_variant_one_inner(const T &value)
+inline GVariant *value_to_g_variant_one_inner(const T &value)
 {
        // Out of completeness:
        static_assert(!std::is_same<T, std::string>::value, "Implemented elsewhere");
@@ -147,32 +147,32 @@ GVariant *value_to_g_variant_one_inner(const T &value)
 }
 
 template <>
-GVariant *value_to_g_variant_one_inner<std::string>(const std::string &value)
+inline GVariant *value_to_g_variant_one_inner<std::string>(const std::string &value)
 {
        return g_variant_new_string(value.c_str());
 }
 
 template <>
-GVariant *value_to_g_variant_one_inner<std::string_view>(const std::string_view &value)
+inline GVariant *value_to_g_variant_one_inner<std::string_view>(const std::string_view &value)
 {
        return g_variant_new_string(value.data());
 }
 
 template <size_t I, typename T>
-void fill_g_variant_one_table_inner(GVariant **table, const T &value)
+inline void fill_g_variant_one_table_inner(GVariant **table, const T &value)
 {
        table[I] = value_to_g_variant_one_inner<T>(value);
 }
 
 template <size_t I, typename T, typename T1, typename... Ts>
-void fill_g_variant_one_table_inner(GVariant **table, const T &value, const T1 &next_value, const Ts &...vals)
+inline void fill_g_variant_one_table_inner(GVariant **table, const T &value, const T1 &next_value, const Ts &...vals)
 {
        table[I] = value_to_g_variant_one_inner<T>(value);
        fill_g_variant_one_table_inner<I + 1, T1, Ts...>(table, next_value, vals...);
 }
 
 template <typename... Ts>
-GVariant *vals_to_g_variant(const Ts &...vals)
+inline GVariant *vals_to_g_variant(const Ts &...vals)
 {
        GVariant *table[sizeof...(vals)];
        fill_g_variant_one_table_inner<0, Ts...>(table, vals...);
@@ -182,7 +182,7 @@ GVariant *vals_to_g_variant(const Ts &...vals)
 }
 
 template <typename... Ts>
-GVariant *tuple_to_g_variant(const std::tuple<Ts...> &tuple)
+inline GVariant *tuple_to_g_variant(const std::tuple<Ts...> &tuple)
 {
        return std::apply(vals_to_g_variant<Ts...>, tuple);
 }