Hi Andrew, On Fri, Jun 20, 2025 at 7:39 AM Andrew Sayers <kernel.org@xxxxxxxxxxxxxxx> wrote: > > We talked recently about refactoring logind.{c,h} to fit more neatly > into the program's wider architecture. This patch sketches out a > possible approach. It compiles, but has not been tested beyond that. > If I'm on the right track, I'll come back with a proper solution. > > The old API provided callbacks on high-level "init" and "exit" events. > That made sense for the limited case it was used for, but seems > unlikely to scale if this starts picking up more use cases. So the > RFC API just provides a single event that passes a `logind_cb_context` > struct to callbacks, and provides `LOGIND_USER_IS_ACTIVE(ctxt)` to > replicate the old behaviour. > > The old API invited individual transports/drivers to use it directly, > which again made sense for version 1, but bypassed `transport.h` and > `driver.h`. The RFC API registers callbacks with `driver.h` and > `transport.h` in a more normal way, and they pass `logind_cb_context` > straight through to the callbacks. That means we won't need to do > much refactoring if the struct changes some day, but also means > individual transports/drivers need to know about `logind.h`. > The alternative would look something like this: > > --- obexd/client/driver.h > +++ obexd/client/driver.h > struct obc_driver { > const char *service; > const char *uuid; > void *target; > gsize target_len; > void *(*supported_features) (struct obc_session *session); > int (*probe) (struct obc_session *session); > void (*remove) (struct obc_session *session); > + int (*uid_state) (const char *state, const int seats); Don't think we really need to expose the seat state, etc, instead I would just have a seated and unseated callback which just indicates to the driver it can do the likes of RegisterProfile/UnregisterProfile that way we can restrict the handling of seats just to driver.c and I don't think we really need it for transport drivers the transports are usable if there are drivers in obc_session_create: driver = obc_driver_find(service); if (driver == NULL) return NULL; So once we integrate the seat logic into the driver.c I assume obc_driver_find will only return drivers that are seated, so users/applications on a unseated D-Bus connection won't be able to create any sessions. > }; > --- obexd/client/driver.c > +++ obexd/client/driver.c > +static void call_cb(gpointer data, gpointer ctxt_) > +{ > + struct obc_driver *driver = (struct obc_driver *)data; > + struct logind_cb_context *ctxt = (struct logind_cb_context *)ctxt_; > + if (driver->uid_state) { > + ctxt->res |= driver->uid_state(ctxt->state, ctxt->seats); > + } > +} > > That would have a better best case (fewer `#include logind.h`s strewn > throughout the code) but a worse worst case (bigger refactoring job if > we need to pass an extra argument some day). I think the RFC patch is > a better balance of risks, but am happy either way. > > The RFC patches for `pbap.c` and `bluetooth.c` are designed to show > the difference between the old and new API. A proper patch will > probably get rid of the `_cb()`, `_init()` and `_exit()` functions. > > Suggested-by: Luiz Augusto von Dentz <luiz.dentz@xxxxxxxxx> > Signed-off-by: Andrew Sayers <kernel.org@xxxxxxxxxxxxxxx> > --- > obexd/client/driver.c | 18 +++++ > obexd/client/driver.h | 5 ++ > obexd/client/pbap.c | 62 ++++++++++------ > obexd/plugins/bluetooth.c | 30 ++++++-- > obexd/src/logind.c | 152 ++++++++++++++++++-------------------- > obexd/src/logind.h | 59 ++++++++++++--- > obexd/src/main.c | 12 +++ > obexd/src/transport.c | 17 +++++ > obexd/src/transport.h | 5 ++ > 9 files changed, 237 insertions(+), 123 deletions(-) > > diff --git a/obexd/client/driver.c b/obexd/client/driver.c > index 195cdd2f1..d5d0fe2ab 100644 > --- a/obexd/client/driver.c > +++ b/obexd/client/driver.c > @@ -74,3 +74,21 @@ void obc_driver_unregister(struct obc_driver *driver) > > drivers = g_slist_remove(drivers, driver); > } > + > +static void call_cb(gpointer data, gpointer ctxt) > +{ > + struct obc_driver *driver = (struct obc_driver *)data; > + > + if (driver->uid_state) > + driver->uid_state(((struct logind_cb_context *)ctxt)); > +} > + > +static void call_uid_state_cb(gpointer ctxt) > +{ > + g_slist_foreach(drivers, call_cb, ctxt); > +} > + > +gboolean obc_driver_init(void) > +{ > + return logind_register(call_uid_state_cb) >= 0; > +} > diff --git a/obexd/client/driver.h b/obexd/client/driver.h > index cc4cace7b..928c0c558 100644 > --- a/obexd/client/driver.h > +++ b/obexd/client/driver.h > @@ -8,6 +8,9 @@ > * > */ > > +#include "obexd/src/logind.h" > +struct obc_session; > + > struct obc_driver { > const char *service; > const char *uuid; > @@ -16,8 +19,10 @@ struct obc_driver { > void *(*supported_features) (struct obc_session *session); > int (*probe) (struct obc_session *session); > void (*remove) (struct obc_session *session); > + void (*uid_state) (struct logind_cb_context *ctxt); > }; > > int obc_driver_register(struct obc_driver *driver); > void obc_driver_unregister(struct obc_driver *driver); > struct obc_driver *obc_driver_find(const char *pattern); > +gboolean obc_driver_init(void); > diff --git a/obexd/client/pbap.c b/obexd/client/pbap.c > index 78c46bf86..dc2a519e5 100644 > --- a/obexd/client/pbap.c > +++ b/obexd/client/pbap.c > @@ -146,6 +146,9 @@ static DBusConnection *system_conn; > static unsigned int listener_id; > static char *client_path; > > +static int pbap_init_cb(void); > +static void pbap_exit_cb(void); > + > static struct pending_request *pending_request_new(struct pbap_data *pbap, > DBusMessage *message) > { > @@ -1300,6 +1303,20 @@ static void pbap_remove(struct obc_session *session) > g_dbus_unregister_interface(conn, path, PBAP_INTERFACE); > } > > +static gboolean user_was_active = FALSE; > +static void pbap_uid_state(struct logind_cb_context *ctxt) > +{ > + gboolean user_is_active = LOGIND_USER_IS_ACTIVE(ctxt); > + > + if (user_was_active == user_is_active) > + return; > + user_was_active = user_is_active; > + if (user_is_active) > + pbap_init_cb(); > + else > + pbap_exit_cb(); > +} > + > static DBusMessage *pbap_release(DBusConnection *conn, > DBusMessage *msg, void *data) > { > @@ -1452,13 +1469,13 @@ static struct obc_driver pbap = { > .target_len = OBEX_PBAP_UUID_LEN, > .supported_features = pbap_supported_features, > .probe = pbap_probe, > - .remove = pbap_remove > + .remove = pbap_remove, > + .uid_state = pbap_uid_state > }; > > -static int pbap_init_cb(gboolean at_register) > +static int pbap_init_cb(void) > { > int err; > - (void)at_register; > > DBG(""); > > @@ -1483,7 +1500,7 @@ static int pbap_init_cb(gboolean at_register) > return 0; > } > > -static void pbap_exit_cb(gboolean at_unregister) > +static void pbap_exit_cb(void) > { > DBusMessage *msg; > DBusMessageIter iter; > @@ -1491,22 +1508,28 @@ static void pbap_exit_cb(gboolean at_unregister) > > DBG(""); > > - if (!at_unregister) { > - client_path = g_strconcat("/org/bluez/obex/", uuid, NULL); > - g_strdelimit(client_path, "-", '_'); > + client_path = g_strconcat("/org/bluez/obex/", uuid, NULL); > + g_strdelimit(client_path, "-", '_'); > > - msg = dbus_message_new_method_call("org.bluez", "/org/bluez", > - "org.bluez.ProfileManager1", > - "UnregisterProfile"); > + msg = dbus_message_new_method_call("org.bluez", "/org/bluez", > + "org.bluez.ProfileManager1", > + "UnregisterProfile"); > > - dbus_message_iter_init_append(msg, &iter); > + dbus_message_iter_init_append(msg, &iter); > > - dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, > - &client_path); > + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, > + &client_path); > > - g_dbus_send_message(system_conn, msg); > - } > + g_dbus_send_message(system_conn, msg); > > + pbap_exit(); > +} > + > +int pbap_init(void) > +{ > +} > +void pbap_exit(void) > +{ > g_dbus_remove_watch(system_conn, listener_id); > > unregister_profile(); > @@ -1524,12 +1547,3 @@ static void pbap_exit_cb(gboolean at_unregister) > > obc_driver_unregister(&pbap); > } > - > -int pbap_init(void) > -{ > - return logind_register(pbap_init_cb, pbap_exit_cb); > -} > -void pbap_exit(void) > -{ > - return logind_unregister(pbap_init_cb, pbap_exit_cb); > -} > diff --git a/obexd/plugins/bluetooth.c b/obexd/plugins/bluetooth.c > index 355921479..ebff88db3 100644 > --- a/obexd/plugins/bluetooth.c > +++ b/obexd/plugins/bluetooth.c > @@ -57,6 +57,9 @@ static DBusMessage *profile_release(DBusConnection *conn, DBusMessage *msg, > return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); > } > > +static int bluetooth_init_cb(void); > +static void bluetooth_exit_cb(void); > + > static void connect_event(GIOChannel *io, GError *err, void *user_data) > { > int sk = g_io_channel_unix_get_fd(io); > @@ -381,6 +384,20 @@ static void bluetooth_stop(void *data) > profiles = NULL; > } > > +static gboolean user_was_active = FALSE; > +static void bluetooth_uid_state(struct logind_cb_context *ctxt) > +{ > + gboolean user_is_active = LOGIND_USER_IS_ACTIVE(ctxt); > + > + if (user_was_active == user_is_active) > + return; > + user_was_active = user_is_active; > + if (user_is_active) > + bluetooth_init_cb(); > + else > + bluetooth_exit_cb(); > +} > + > static int bluetooth_getpeername(GIOChannel *io, char **name) > { > GError *gerr = NULL; > @@ -422,14 +439,14 @@ static const struct obex_transport_driver driver = { > .start = bluetooth_start, > .getpeername = bluetooth_getpeername, > .getsockname = bluetooth_getsockname, > - .stop = bluetooth_stop > + .stop = bluetooth_stop, > + .uid_state = bluetooth_uid_state > }; > > static unsigned int listener_id = 0; > > -static int bluetooth_init_cb(gboolean at_register) > +static int bluetooth_init_cb(void) > { > - (void)at_register; > connection = g_dbus_setup_private(DBUS_BUS_SYSTEM, NULL, NULL); > if (connection == NULL) > return -EPERM; > @@ -440,10 +457,9 @@ static int bluetooth_init_cb(gboolean at_register) > return obex_transport_driver_register(&driver); > } > > -static void bluetooth_exit_cb(gboolean at_unregister) > +static void bluetooth_exit_cb(void) > { > GSList *l; > - (void)at_unregister; > > g_dbus_remove_watch(connection, listener_id); > > @@ -467,11 +483,11 @@ static void bluetooth_exit_cb(gboolean at_unregister) > > static int bluetooth_init(void) > { > - return logind_register(bluetooth_init_cb, bluetooth_exit_cb); > } > static void bluetooth_exit(void) > { > - return logind_unregister(bluetooth_init_cb, bluetooth_exit_cb); > + if (user_was_active) > + bluetooth_exit_cb(); > } > > OBEX_PLUGIN_DEFINE(bluetooth, bluetooth_init, bluetooth_exit) > diff --git a/obexd/src/logind.c b/obexd/src/logind.c > index b4279b209..a8ea25543 100644 > --- a/obexd/src/logind.c > +++ b/obexd/src/logind.c > @@ -27,66 +27,48 @@ > > static sd_login_monitor * monitor; > static int uid; > -static gboolean active = FALSE; > static gboolean monitoring_enabled = TRUE; > static guint event_source; > static guint timeout_source; > > -struct callback_pair { > - logind_init_cb init_cb; > - logind_exit_cb exit_cb; > -}; > - > GSList *callbacks; > > -static void call_init_cb(gpointer data, gpointer user_data) > -{ > - int res; > - > - res = ((struct callback_pair *)data)->init_cb(FALSE); > - if (res) > - *(int *)user_data = res; > -} > -static void call_exit_cb(gpointer data, gpointer user_data) > +static void call_cb(gpointer data, gpointer user_data) > { > - ((struct callback_pair *)data)->exit_cb(FALSE); > + (*((logind_cb *)data))(user_data); > } > > -static int update(void) > +static void logind_cb_context_init(struct logind_cb_context *ctxt) > { > - char *state = NULL; > - gboolean state_is_active; > - int res; > - > - res = sd_login_monitor_flush(monitor); > - if (res < 0) > - return res; > - res = sd_uid_get_state(uid, &state); > - state_is_active = g_strcmp0(state, "active"); > - free(state); > - if (res < 0) > - return res; > - > - if (state_is_active) { > - if (!active) > - return 0; > - } else { > - res = sd_uid_get_seats(uid, 1, NULL); > - if (res < 0) > - return res; > - if (active == !!res) > - return 0; > + ctxt->res = sd_login_monitor_flush(monitor); > + if (ctxt->res < 0) > + return; > + > + ctxt->res = ctxt->seats = sd_uid_get_seats(uid, 1, NULL); > + if (ctxt->res < 0) > + return; > + > + /* > + * the documentation for sd_uid_get_state() isn't clear about > + * what to do with the state on error. The following should > + * be safe even if the behaviour changes in future > + */ > + ctxt->state = 0; > + ctxt->res = sd_uid_get_state(uid, (char **)&ctxt->state); > + if (ctxt->res <= 0) { > + free((char *)ctxt->state); > + return; > } > - active ^= TRUE; > - res = 0; > - g_slist_foreach(callbacks, active ? call_init_cb : call_exit_cb, &res); > - return res; > + > + ctxt->res = 0; > + return; > } > > static gboolean timeout_handler(gpointer user_data); > > static int check_event(void) > { > + struct logind_cb_context ctxt; > uint64_t timeout_usec; > int res; > > @@ -95,9 +77,14 @@ static int check_event(void) > return res; > if (!monitoring_enabled) > return 0; > - res = update(); > - if (res < 0) > - return res; > + > + logind_cb_context_init(&ctxt); > + if (ctxt.res) > + return ctxt.res; > + g_slist_foreach(callbacks, call_cb, &ctxt); > + free((char *)ctxt.state); > + if (ctxt.res) > + return ctxt.res; > > res = sd_login_monitor_get_timeout(monitor, &timeout_usec); > if (res < 0) > @@ -154,6 +141,7 @@ static gboolean timeout_handler(gpointer user_data) > > static int logind_init(void) > { > + struct logind_cb_context ctxt; > GIOChannel *channel; > int events; > int fd; > @@ -174,11 +162,6 @@ static int logind_init(void) > goto FAIL; > } > > - // Check this after creating the monitor, in case of race conditions: > - res = update(); > - if (res < 0) > - goto FAIL; > - > events = res = sd_login_monitor_get_events(monitor); > if (res < 0) > goto FAIL; > @@ -202,7 +185,10 @@ static int logind_init(void) > FAIL: > sd_login_monitor_unref(monitor); > monitoring_enabled = FALSE; > - active = TRUE; > + ctxt.state = "active"; > + ctxt.seats = 1; > + ctxt.res = 0; > + g_slist_foreach(callbacks, call_cb, &ctxt); > return res; > } > > @@ -219,17 +205,18 @@ static void logind_exit(void) > sd_login_monitor_unref(monitor); > } > > -static gint find_cb(gconstpointer a, gconstpointer b) > +int logind_register(logind_cb cb) > { > - return ((struct callback_pair *)a)->init_cb - (logind_init_cb)b; > -} > + struct logind_cb_context ctxt; > > -int logind_register(logind_init_cb init_cb, logind_exit_cb exit_cb) > -{ > - struct callback_pair *cbs; > + logind_cb_context_init(&ctxt); > + if (ctxt.res) { > + free((char *)ctxt.state); > + return ctxt.res; > + } > > if (!monitoring_enabled) > - return init_cb(TRUE); > + goto CALL_CB; > if (callbacks == NULL) { > int res; > > @@ -237,24 +224,23 @@ int logind_register(logind_init_cb init_cb, logind_exit_cb exit_cb) > if (res) { > error("logind_init(): %s - login detection disabled", > strerror(-res)); > - return init_cb(TRUE); > + goto CALL_CB; > } > } > - cbs = g_new(struct callback_pair, 1); > - cbs->init_cb = init_cb; > - cbs->exit_cb = exit_cb; > - callbacks = g_slist_prepend(callbacks, cbs); > - return active ? init_cb(TRUE) : 0; > + callbacks = g_slist_prepend(callbacks, cb); > + > +CALL_CB: > + cb(&ctxt); > + free((char *)ctxt.state); > + return ctxt.res; > } > -void logind_unregister(logind_init_cb init_cb, logind_exit_cb exit_cb) > +void logind_unregister(logind_cb cb) > { > GSList *cb_node; > > if (!monitoring_enabled) > - return exit_cb(TRUE); > - if (active) > - exit_cb(TRUE); > - cb_node = g_slist_find_custom(callbacks, init_cb, find_cb); > + return; > + cb_node = g_slist_find(callbacks, cb); > if (cb_node != NULL) > callbacks = g_slist_delete_link(callbacks, cb_node); > if (callbacks == NULL) > @@ -263,20 +249,26 @@ void logind_unregister(logind_init_cb init_cb, logind_exit_cb exit_cb) > > int logind_set(gboolean enabled) > { > - int res = 0; > - > - if (monitoring_enabled == enabled) > - return 0; > - > monitoring_enabled = enabled; > if (enabled) { > - active = FALSE; > - return update(); > + struct logind_cb_context ctxt; > + > + logind_cb_context_init(&ctxt); > + if (ctxt.res) > + return ctxt.res; > + g_slist_foreach(callbacks, call_cb, &ctxt); > + free((char *)ctxt.state); > + return ctxt.res; > } > > - active = TRUE; > - g_slist_foreach(callbacks, call_exit_cb, &res); > - return res; > + struct logind_cb_context ctxt = { > + .state = "active", > + .seats = 1, > + .res = 0 > + }; > + > + g_slist_foreach(callbacks, call_cb, &ctxt); > + return ctxt.res; > } > > #endif > diff --git a/obexd/src/logind.h b/obexd/src/logind.h > index 3cdb03338..243aa17bd 100644 > --- a/obexd/src/logind.h > +++ b/obexd/src/logind.h > @@ -8,30 +8,65 @@ > * > */ > > -typedef int (*logind_init_cb)(gboolean at_register); > -typedef void (*logind_exit_cb)(gboolean at_unregister); > +#ifndef OBEXD_SRC_LOGIND_H > +#define OBEXD_SRC_LOGIND_H > + > +struct logind_cb_context { > + const char *state; > + int seats; > + int res; > +}; > + > +typedef void (*logind_cb)(gpointer ctxt); > > #ifdef SYSTEMD > > -int logind_register(logind_init_cb init_cb, logind_exit_cb exit_cb); > -void logind_unregister(logind_init_cb init_cb, logind_exit_cb exit_cb); > +/* > + * Register callback and call it with the current state > + */ > +int logind_register(logind_cb init_cb); > +/* > + * Unregister callback but DO NOT call it - > + * unregistration usually happens when the user is logging out, > + * and other programs are going away. > + * > + * If possible, close resources at exit instead of at unregister. > + * Otherwise, you will need to explicitly call your callback. > + */ > +void logind_unregister(logind_cb cb); > +/* > + * Override the detected login state > + */ > int logind_set(gboolean enabled); > > -#else > +/* Recommended way to detect (in)activity */ > +#define LOGIND_USER_IS_ACTIVE(ctxt) \ > + (!g_strcmp0(ctxt->state, "active") && !!(ctxt->seats)) > > -static inline int logind_register(logind_init_cb init_cb, > - logind_exit_cb exit_cb) > +#else /* SYSTEMD */ > + > +static inline int logind_register(logind_cb cb) > { > - return init_cb(TRUE); > + (void)cb; > + struct logind_cb_context ctxt = { > + .state = "active", > + .seats = 1, > + .res = 0 > + }; > + cb(&ctxt); > + return ctxt.res; > } > -static inline void logind_unregister(logind_init_cb init_cb, > - logind_exit_cb exit_cb) > +static inline void logind_unregister(logind_cb cb) > { > - return exit_cb(TRUE); > + (void)cb; > } > static inline int logind_set(gboolean enabled) > { > return 0; > } > > -#endif > +#define LOGIND_USER_IS_ACTIVE(...) 1 > + > +#endif /* SYSTEMD */ > + > +#endif /* OBEXD_SRC_LOGIND_H */ > diff --git a/obexd/src/main.c b/obexd/src/main.c > index 6837f0d73..116888a78 100644 > --- a/obexd/src/main.c > +++ b/obexd/src/main.c > @@ -34,6 +34,9 @@ > > #include "../client/manager.h" > > +#include "../client/driver.h" > +#include "transport.h" > + > #include "log.h" > #include "logind.h" > #include "obexd.h" > @@ -279,6 +282,15 @@ int main(int argc, char *argv[]) > if (option_system_bus) > logind_set(FALSE); > > + if (obc_driver_init() == FALSE) { > + error("manager_init failed"); > + exit(EXIT_FAILURE); > + } > + if (obex_transport_driver_init() == FALSE) { > + error("manager_init failed"); > + exit(EXIT_FAILURE); > + } > + > DBG("Entering main loop"); > > main_loop = g_main_loop_new(NULL, FALSE); > diff --git a/obexd/src/transport.c b/obexd/src/transport.c > index 527d9ffce..fdd49b7a2 100644 > --- a/obexd/src/transport.c > +++ b/obexd/src/transport.c > @@ -79,3 +79,20 @@ obex_transport_driver_unregister(const struct obex_transport_driver *driver) > > drivers = g_slist_remove(drivers, driver); > } > + > +static void call_cb(gpointer data, gpointer ctxt) > +{ > + struct obex_transport_driver *driver = > + (struct obex_transport_driver *)data; > + if (driver->uid_state) > + driver->uid_state(((struct logind_cb_context *)ctxt)); > +} > + > +static int call_uid_state_cb(gpointer ctxt) > +{ > + g_slist_foreach(drivers, call_cb, ctxt); > +} > + > +gboolean obex_transport_driver_init(void) > +{ > +} > diff --git a/obexd/src/transport.h b/obexd/src/transport.h > index 322d8f526..087d8c211 100644 > --- a/obexd/src/transport.h > +++ b/obexd/src/transport.h > @@ -8,6 +8,9 @@ > * > */ > > +#include "obexd/src/logind.h" > +struct obex_server; > + > struct obex_transport_driver { > const char *name; > uint16_t service; > @@ -15,9 +18,11 @@ struct obex_transport_driver { > int (*getpeername) (GIOChannel *io, char **name); > int (*getsockname) (GIOChannel *io, char **name); > void (*stop) (void *data); > + void (*uid_state) (struct logind_cb_context *ctxt); > }; > > int obex_transport_driver_register(const struct obex_transport_driver *driver); > void > obex_transport_driver_unregister(const struct obex_transport_driver *driver); > const GSList *obex_transport_driver_list(void); > +gboolean obex_transport_driver_init(void); > -- > 2.49.0 > -- Luiz Augusto von Dentz