--- /dev/null
+#pragma once
+
+/* 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. */
+
+#include <chrono>
+#include <exception>
+#include <stdexcept>
+#include <string>
+
+#include <gio/gio.h>
+
+using namespace std::chrono_literals;
+
+constexpr inline std::string_view bus_name = "org.tizen.sessiond";
+constexpr inline std::string_view bus_object = "/org/tizen/sessiond";
+constexpr inline std::string_view bus_iface = "org.tizen.sessiond.subsession.Manager";
+
+std::chrono::seconds timeout = 10s;
+
+[[noreturn]] inline void g_error_throw(GError *err, std::string message) {
+ message += err->message;
+ g_error_free(err);
+ throw std::runtime_error(std::move(message));
+}
+
#include <algorithm>
#include <iostream>
-#include <memory>
#include <unordered_map>
-#include <unordered_set>
-#include <vector>
#include <gio/gio.h>
+#include "globals.hpp"
+#include "fs_helpers.h"
#include "tuple_g_variant_helpers.hpp"
#include "tuple_hash.hpp"
-#include "fs_helpers.h"
-
-constexpr static std::string_view bus_name = "org.tizen.sessiond";
-constexpr static std::string_view bus_object = "/org/tizen/sessiond";
-constexpr static std::string_view bus_iface = "org.tizen.sessiond.subsession.Manager";
-
-constexpr static int timeout = 10;
-
-[[noreturn]] void g_error_throw(GError *err, std::string message) {
- message += err->message;
- g_error_free(err);
- throw std::runtime_error(std::move(message));
-}
+#include "wait_manager.hpp"
struct introspection_data {
introspection_data(std::string_view xml)
GMainLoop *inner;
};
-template<typename ...Vs>
-struct wait_manager {
- wait_manager(int session_uid, GDBusConnection *connection, std::string_view completion_signal)
- : session_uid(session_uid), connection(connection), completion_signal(completion_signal) {}
- ~wait_manager()
- {
- for (auto ®istration : registered)
- g_bus_unwatch_name(registration.second);
-
- // We ought to swallow exceptions in the destructor.
- // (std::uncaught_exception exists, but there's no need for it IMHO.)
- try {
- for (auto &waiting_for : waiting) {
- waiting_for.second.clear();
- finalize_if_empty(waiting_for.first);
- }
- } catch (const std::exception &ex) {
- std::cerr << "Exception " << ex.what() << "\n" <<
- "while finalizing the wait manager\n";
- }
-
- for (auto &timeout : timeouts)
- g_source_remove(timeout->timeout_id);
- }
-
- // Because of the rule of three, we also have to delete these.
- wait_manager(const wait_manager &) = delete;
- wait_manager &operator=(const wait_manager &) = delete;
-
- void on_client_register(const std::string &name)
- {
- if (registered.contains(name))
- throw std::invalid_argument("Client already registered for waiting");
- auto match = g_bus_watch_name(G_BUS_TYPE_SYSTEM, name.data(), G_BUS_NAME_WATCHER_FLAGS_NONE,
- nullptr, glib_client_disappeared, this, nullptr);
- registered.emplace(name, std::move(match));
- }
-
- void on_start(std::tuple<Vs...> v)
- {
- // See the comment in on_timeout.
- timeouts.erase(std::remove_if(timeouts.begin(), timeouts.end(),
- [](const auto &td) { return !td->active; }),
- timeouts.end());
-
- // See the comment in finalize.
- std::erase_if(waiting, [](const auto &waits) { return waits.second.empty(); });
-
- if (waiting.contains(v))
- return; // TODO: any error here?
- // This shouldn't happen, but it's unclear how to punish this correctly.
- // In particular, this will not happen by construction in case of switch_user,
- // since v contains the switch_id.
-
- std::unordered_set<std::string> waits = {};
- for (const auto &going_to_wait : registered)
- waits.emplace(going_to_wait.first);
- if (waits.empty())
- signal(v);
- else {
- auto td = std::make_unique<timeout_data>();
- td->self = this;
- td->v = v;
- td->active = true;
- td->timeout_id = g_timeout_add_seconds(timeout, glib_timeout, td.get());
-
- waiting.emplace(v, std::move(waits));
- timeouts.emplace_back(std::move(td));
- }
- }
-
- void on_client_done(std::tuple<Vs...> v, const std::string &name)
- {
- if (!waiting.contains(v))
- return; // This could be an error, but then we would have a race condition with the timeout.
- drop_name(v, waiting[v], name);
- }
-
- void on_client_disappeared(const std::string &name)
- {
- registered.erase(name);
- for (auto &waiting_for : waiting) {
- drop_name(waiting_for.first, waiting_for.second, name);
- }
- }
-
- void drop_name(std::tuple<Vs...> v, std::unordered_set<std::string> &waits, const std::string &name)
- {
- waits.erase(name);
- finalize_if_empty(v);
- }
-
- void on_timeout(std::tuple<Vs...> v)
- {
- if (!waiting.contains(v))
- return;
- waiting[v].clear();
-
- finalize_if_empty(v);
-
- // One thing that we'd like to do here is removing the timeout from the array.
- // However doing so is a bit sus, since glib_timeout still has pointer.
- // What we do instead, is clearing the array up a bit when adding a new timeout in on_start.
- }
-
- void finalize_if_empty(std::tuple<Vs...> v)
- {
- // TODO: I don't like this, somehow.
- // Refactor suggestions are more than welcome.
- if (!waiting.contains(v) && !waiting[v].empty())
- return;
-
- signal(v);
-
- // Again, we'd like to remove the now empty vector.
- // However, doing so is again sus, since other functions (for example on_client_disappeared)
- // might actually iterate on waiting, and this invalidates the vector.
- // Again, let's clean up in on_start instead.
- }
-
- void signal(std::tuple<Vs...> v)
- {
- GError *err = nullptr;
- if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), completion_signal.data(),
- tuple_to_g_variant(std::tuple_cat(std::make_tuple(session_uid), v)), &err))
- g_error_throw(err, "Failed to emit a signal: ");
- }
-
- static void glib_client_disappeared(GDBusConnection *conn, const gchar *name, gpointer user_data)
- // As this is called directly by GLib, we need to swallow exceptions here.
- try {
- auto self = static_cast<wait_manager *>(user_data);
- self->on_client_disappeared(std::string(name));
- } catch (const std::exception &ex) {
- std::cerr << "Exception " << ex.what() << "\n" <<
- "while handling " << name << " disappearing\n";
- }
-
- struct timeout_data {
- guint timeout_id;
- wait_manager *self;
- std::tuple<Vs...> v;
- bool active;
- };
- static gboolean glib_timeout(gpointer user_data)
- // As this is called directly by GLib, we need to swallow exceptions here.
- try {
- auto data = static_cast<timeout_data *>(user_data);
- data->self->on_timeout(data->v);
- data->active = false;
- return G_SOURCE_REMOVE;
- } catch (const std::exception &ex) {
- std::cerr << "Exception " << ex.what() << "\n" <<
- "while timeout\n";
- return G_SOURCE_REMOVE;
- }
-
- int session_uid;
- GDBusConnection *connection;
- std::string_view completion_signal;
-
- std::unordered_map<std::string, guint> registered = {};
- std::unordered_map<std::tuple<Vs...>, std::unordered_set<std::string>, tuple_hash<std::tuple<Vs...>>> waiting = {};
-
- // We hold all the timeout_data, because I couldn't get memory management to work otherwise.
- std::vector<std::unique_ptr<timeout_data>> timeouts = {};
-};
-
struct sessiond_context {
sessiond_context() : data(xml), id(bus_name, glib_bus_connected, glib_name_lost, this), loop()
{
--- /dev/null
+#pragma once
+
+/* 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. */
+
+#include <algorithm>
+#include <exception>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <gio/gio.h>
+
+#include "globals.hpp"
+#include "tuple_g_variant_helpers.hpp"
+#include "tuple_hash.hpp"
+
+template<typename ...Vs>
+struct wait_manager {
+ wait_manager(int session_uid, GDBusConnection *connection, std::string_view completion_signal)
+ : session_uid(session_uid), connection(connection), completion_signal(completion_signal) {}
+ ~wait_manager()
+ {
+ for (auto ®istration : registered)
+ g_bus_unwatch_name(registration.second);
+
+ // We ought to swallow exceptions in the destructor.
+ // (std::uncaught_exception exists, but there's no need for it IMHO.)
+ try {
+ for (auto &waiting_for : waiting) {
+ waiting_for.second.clear();
+ finalize_if_empty(waiting_for.first);
+ }
+ } catch (const std::exception &ex) {
+ std::cerr << "Exception " << ex.what() << "\n" <<
+ "while finalizing the wait manager\n";
+ }
+
+ for (auto &timeout : timeouts)
+ g_source_remove(timeout->timeout_id);
+ }
+
+ // Because of the rule of three, we also have to delete these.
+ wait_manager(const wait_manager &) = delete;
+ wait_manager &operator=(const wait_manager &) = delete;
+
+ void on_client_register(const std::string &name)
+ {
+ if (registered.contains(name))
+ throw std::invalid_argument("Client already registered for waiting");
+ auto match = g_bus_watch_name(G_BUS_TYPE_SYSTEM, name.data(), G_BUS_NAME_WATCHER_FLAGS_NONE,
+ nullptr, glib_client_disappeared, this, nullptr);
+ registered.emplace(name, std::move(match));
+ }
+
+ void on_start(std::tuple<Vs...> v)
+ {
+ // See the comment in on_timeout.
+ timeouts.erase(std::remove_if(timeouts.begin(), timeouts.end(),
+ [](const auto &td) { return !td->active; }),
+ timeouts.end());
+
+ // See the comment in finalize.
+ std::erase_if(waiting, [](const auto &waits) { return waits.second.empty(); });
+
+ if (waiting.contains(v))
+ return; // TODO: any error here?
+ // This shouldn't happen, but it's unclear how to punish this correctly.
+ // In particular, this will not happen by construction in case of switch_user,
+ // since v contains the switch_id.
+
+ std::unordered_set<std::string> waits = {};
+ for (const auto &going_to_wait : registered)
+ waits.emplace(going_to_wait.first);
+ if (waits.empty())
+ signal(v);
+ else {
+ auto td = std::make_unique<timeout_data>();
+ td->self = this;
+ td->v = v;
+ td->active = true;
+ td->timeout_id = g_timeout_add_seconds(timeout.count(), glib_timeout, td.get());
+
+ waiting.emplace(v, std::move(waits));
+ timeouts.emplace_back(std::move(td));
+ }
+ }
+
+ void on_client_done(std::tuple<Vs...> v, const std::string &name)
+ {
+ if (!waiting.contains(v))
+ return; // This could be an error, but then we would have a race condition with the timeout.
+ drop_name(v, waiting[v], name);
+ }
+
+ void on_client_disappeared(const std::string &name)
+ {
+ registered.erase(name);
+ for (auto &waiting_for : waiting) {
+ drop_name(waiting_for.first, waiting_for.second, name);
+ }
+ }
+
+ void drop_name(std::tuple<Vs...> v, std::unordered_set<std::string> &waits, const std::string &name)
+ {
+ waits.erase(name);
+ finalize_if_empty(v);
+ }
+
+ void on_timeout(std::tuple<Vs...> v)
+ {
+ if (!waiting.contains(v))
+ return;
+ waiting[v].clear();
+
+ finalize_if_empty(v);
+
+ // One thing that we'd like to do here is removing the timeout from the array.
+ // However doing so is a bit sus, since glib_timeout still has pointer.
+ // What we do instead, is clearing the array up a bit when adding a new timeout in on_start.
+ }
+
+ void finalize_if_empty(std::tuple<Vs...> v)
+ {
+ // TODO: I don't like this, somehow.
+ // Refactor suggestions are more than welcome.
+ if (!waiting.contains(v) && !waiting[v].empty())
+ return;
+
+ signal(v);
+
+ // Again, we'd like to remove the now empty vector.
+ // However, doing so is again sus, since other functions (for example on_client_disappeared)
+ // might actually iterate on waiting, and this invalidates the vector.
+ // Again, let's clean up in on_start instead.
+ }
+
+ void signal(std::tuple<Vs...> v)
+ {
+ GError *err = nullptr;
+ if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), completion_signal.data(),
+ tuple_to_g_variant(std::tuple_cat(std::make_tuple(session_uid), v)), &err))
+ g_error_throw(err, "Failed to emit a signal: ");
+ }
+
+ static void glib_client_disappeared(GDBusConnection *conn, const gchar *name, gpointer user_data)
+ // As this is called directly by GLib, we need to swallow exceptions here.
+ try {
+ auto self = static_cast<wait_manager *>(user_data);
+ self->on_client_disappeared(std::string(name));
+ } catch (const std::exception &ex) {
+ std::cerr << "Exception " << ex.what() << "\n" <<
+ "while handling " << name << " disappearing\n";
+ }
+
+ struct timeout_data {
+ guint timeout_id;
+ wait_manager *self;
+ std::tuple<Vs...> v;
+ bool active;
+ };
+ static gboolean glib_timeout(gpointer user_data)
+ // As this is called directly by GLib, we need to swallow exceptions here.
+ try {
+ auto data = static_cast<timeout_data *>(user_data);
+ data->self->on_timeout(data->v);
+ data->active = false;
+ return G_SOURCE_REMOVE;
+ } catch (const std::exception &ex) {
+ std::cerr << "Exception " << ex.what() << "\n" <<
+ "while timeout\n";
+ return G_SOURCE_REMOVE;
+ }
+
+ int session_uid;
+ GDBusConnection *connection;
+ std::string_view completion_signal;
+
+ std::unordered_map<std::string, guint> registered = {};
+ std::unordered_map<std::tuple<Vs...>, std::unordered_set<std::string>, tuple_hash<std::tuple<Vs...>>> waiting = {};
+
+ // We hold all the timeout_data, because I couldn't get memory management to work otherwise.
+ std::vector<std::unique_ptr<timeout_data>> timeouts = {};
+};