From ee7ed7dc3598d4e6e9b972dcda0b09577d8abfb2 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 9 Dec 2019 20:37:48 +0000 Subject: [PATCH] libmalcontent: Add support for setting session limits Signed-off-by: Philip Withnall --- libmalcontent/manager.c | 229 +++++++++++++++++++++++++++++ libmalcontent/manager.h | 17 +++ libmalcontent/session-limits.c | 260 +++++++++++++++++++++++++++++++++ libmalcontent/session-limits.h | 63 ++++++++ 4 files changed, 569 insertions(+) diff --git a/libmalcontent/manager.c b/libmalcontent/manager.c index 871223b..eef42fb 100644 --- a/libmalcontent/manager.c +++ b/libmalcontent/manager.c @@ -1104,3 +1104,232 @@ mct_manager_get_session_limits_finish (MctManager *self, return g_task_propagate_pointer (G_TASK (result), error); } + +/** + * mct_manager_set_session_limits: + * @self: a #MctManager + * @user_id: ID of the user to set the limits for, typically coming from getuid() + * @session_limits: (transfer none): the session limits to set for the user + * @flags: flags to affect the behaviour of the call + * @cancellable: (nullable): a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Synchronous version of mct_manager_set_session_limits_async(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 0.5.0 + */ +gboolean +mct_manager_set_session_limits (MctManager *self, + uid_t user_id, + MctSessionLimits *session_limits, + MctManagerSetValueFlags flags, + GCancellable *cancellable, + GError **error) +{ + g_autofree gchar *object_path = NULL; + g_autoptr(GVariant) limit_variant = NULL; + const gchar *limit_property_name = NULL; + g_autoptr(GVariant) limit_type_variant = NULL; + g_autoptr(GVariant) limit_result_variant = NULL; + g_autoptr(GVariant) limit_type_result_variant = NULL; + g_autoptr(GError) local_error = NULL; + + g_return_val_if_fail (MCT_IS_MANAGER (self), FALSE); + g_return_val_if_fail (session_limits != NULL, FALSE); + g_return_val_if_fail (session_limits->ref_count >= 1, FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + object_path = accounts_find_user_by_id (self->connection, user_id, + (flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE), + cancellable, error); + if (object_path == NULL) + return FALSE; + + switch (session_limits->limit_type) + { + case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE: + limit_variant = g_variant_new ("(uu)", + session_limits->daily_start_time, + session_limits->daily_end_time); + limit_property_name = "DailySchedule"; + break; + case MCT_SESSION_LIMITS_TYPE_NONE: + limit_variant = NULL; + limit_property_name = NULL; + break; + default: + g_assert_not_reached (); + } + + limit_type_variant = g_variant_new_uint32 (session_limits->limit_type); + + if (limit_property_name != NULL) + { + /* Change the details of the new limit first, so that all the properties are + * correct by the time the limit type is changed over. */ + limit_result_variant = + g_dbus_connection_call_sync (self->connection, + "org.freedesktop.Accounts", + object_path, + "org.freedesktop.DBus.Properties", + "Set", + g_variant_new ("(ssv)", + "com.endlessm.ParentalControls.SessionLimits", + limit_property_name, + g_steal_pointer (&limit_variant)), + G_VARIANT_TYPE ("()"), + (flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE) + ? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION + : G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout, ms */ + cancellable, + &local_error); + if (local_error != NULL) + { + g_propagate_error (error, bus_error_to_manager_error (local_error, user_id)); + return FALSE; + } + } + + limit_type_result_variant = + g_dbus_connection_call_sync (self->connection, + "org.freedesktop.Accounts", + object_path, + "org.freedesktop.DBus.Properties", + "Set", + g_variant_new ("(ssv)", + "com.endlessm.ParentalControls.SessionLimits", + "LimitType", + g_steal_pointer (&limit_type_variant)), + G_VARIANT_TYPE ("()"), + (flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE) + ? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION + : G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout, ms */ + cancellable, + &local_error); + if (local_error != NULL) + { + g_propagate_error (error, bus_error_to_manager_error (local_error, user_id)); + return FALSE; + } + + return TRUE; +} + +static void set_session_limits_thread_cb (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +typedef struct +{ + uid_t user_id; + MctSessionLimits *session_limits; /* (owned) */ + MctManagerSetValueFlags flags; +} SetSessionLimitsData; + +static void +set_session_limits_data_free (SetSessionLimitsData *data) +{ + mct_session_limits_unref (data->session_limits); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SetSessionLimitsData, set_session_limits_data_free) + +/** + * mct_manager_set_session_limits_async: + * @self: a #MctManager + * @user_id: ID of the user to set the limits for, typically coming from getuid() + * @session_limits: (transfer none): the session limits to set for the user + * @flags: flags to affect the behaviour of the call + * @cancellable: (nullable): a #GCancellable, or %NULL + * @callback: a #GAsyncReadyCallback + * @user_data: user data to pass to @callback + * + * Asynchronously set the session limits settings for the given @user_id to the + * given @session_limits instance. + * + * On failure, an #MctManagerError, a #GDBusError or a #GIOError will be + * returned via mct_manager_set_session_limits_finish(). The user’s session + * limits settings will be left in an undefined state. + * + * Since: 0.5.0 + */ +void +mct_manager_set_session_limits_async (MctManager *self, + uid_t user_id, + MctSessionLimits *session_limits, + MctManagerSetValueFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + g_autoptr(SetSessionLimitsData) data = NULL; + + g_return_if_fail (MCT_IS_MANAGER (self)); + g_return_if_fail (session_limits != NULL); + g_return_if_fail (session_limits->ref_count >= 1); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, mct_manager_set_session_limits_async); + + data = g_new0 (SetSessionLimitsData, 1); + data->user_id = user_id; + data->session_limits = mct_session_limits_ref (session_limits); + data->flags = flags; + g_task_set_task_data (task, g_steal_pointer (&data), + (GDestroyNotify) set_session_limits_data_free); + + g_task_run_in_thread (task, set_session_limits_thread_cb); +} + +static void +set_session_limits_thread_cb (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + gboolean success; + MctManager *manager = MCT_MANAGER (source_object); + SetSessionLimitsData *data = task_data; + g_autoptr(GError) local_error = NULL; + + success = mct_manager_set_session_limits (manager, data->user_id, + data->session_limits, data->flags, + cancellable, &local_error); + + if (local_error != NULL) + g_task_return_error (task, g_steal_pointer (&local_error)); + else + g_task_return_boolean (task, success); +} + +/** + * mct_manager_set_session_limits_finish: + * @self: a #MctManager + * @result: a #GAsyncResult + * @error: return location for a #GError, or %NULL + * + * Finish an asynchronous operation to set the session limits for a user, + * started with mct_manager_set_session_limits_async(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 0.5.0 + */ +gboolean +mct_manager_set_session_limits_finish (MctManager *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (MCT_IS_MANAGER (self), FALSE); + g_return_val_if_fail (g_task_is_valid (result, self), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} diff --git a/libmalcontent/manager.h b/libmalcontent/manager.h index 641c577..f1bf1ce 100644 --- a/libmalcontent/manager.h +++ b/libmalcontent/manager.h @@ -151,4 +151,21 @@ MctSessionLimits *mct_manager_get_session_limits_finish (MctManager GAsyncResult *result, GError **error); +gboolean mct_manager_set_session_limits (MctManager *self, + uid_t user_id, + MctSessionLimits *session_limits, + MctManagerSetValueFlags flags, + GCancellable *cancellable, + GError **error); +void mct_manager_set_session_limits_async (MctManager *self, + uid_t user_id, + MctSessionLimits *session_limits, + MctManagerSetValueFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mct_manager_set_session_limits_finish (MctManager *self, + GAsyncResult *result, + GError **error); + G_END_DECLS diff --git a/libmalcontent/session-limits.c b/libmalcontent/session-limits.c index de390fd..be4373f 100644 --- a/libmalcontent/session-limits.c +++ b/libmalcontent/session-limits.c @@ -189,3 +189,263 @@ out: return user_allowed_now; } + +/* + * Actual implementation of #MctSessionLimitsBuilder. + * + * All members are %NULL if un-initialised, cleared, or ended. + */ +typedef struct +{ + MctSessionLimitsType limit_type; + + /* Which member is used is determined by @limit_type: */ + union + { + struct + { + guint start_time; /* seconds since midnight */ + guint end_time; /* seconds since midnight */ + } daily_schedule; + }; + + /*< private >*/ + gpointer padding[10]; +} MctSessionLimitsBuilderReal; + +G_STATIC_ASSERT (sizeof (MctSessionLimitsBuilderReal) == + sizeof (MctSessionLimitsBuilder)); +G_STATIC_ASSERT (__alignof__ (MctSessionLimitsBuilderReal) == + __alignof__ (MctSessionLimitsBuilder)); + +G_DEFINE_BOXED_TYPE (MctSessionLimitsBuilder, mct_session_limits_builder, + mct_session_limits_builder_copy, mct_session_limits_builder_free) + +/** + * mct_session_limits_builder_init: + * @builder: an uninitialised #MctSessionLimitsBuilder + * + * Initialise the given @builder so it can be used to construct a new + * #MctSessionLimits. @builder must have been allocated on the stack, and must + * not already be initialised. + * + * Construct the #MctSessionLimits by calling methods on @builder, followed by + * mct_session_limits_builder_end(). To abort construction, use + * mct_session_limits_builder_clear(). + * + * Since: 0.5.0 + */ +void +mct_session_limits_builder_init (MctSessionLimitsBuilder *builder) +{ + MctSessionLimitsBuilder local_builder = MCT_SESSION_LIMITS_BUILDER_INIT (); + MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + g_return_if_fail (_builder->limit_type == MCT_SESSION_LIMITS_TYPE_NONE); + + memcpy (builder, &local_builder, sizeof (local_builder)); +} + +/** + * mct_session_limits_builder_clear: + * @builder: an #MctSessionLimitsBuilder + * + * Clear @builder, freeing any internal state in it. This will not free the + * top-level storage for @builder itself, which is assumed to be allocated on + * the stack. + * + * If called on an already-cleared #MctSessionLimitsBuilder, this function is + * idempotent. + * + * Since: 0.5.0 + */ +void +mct_session_limits_builder_clear (MctSessionLimitsBuilder *builder) +{ + MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + + /* Nothing to free here for now. */ + _builder->limit_type = MCT_SESSION_LIMITS_TYPE_NONE; +} + +/** + * mct_session_limits_builder_new: + * + * Construct a new #MctSessionLimitsBuilder on the heap. This is intended for + * language bindings. The returned builder must eventually be freed with + * mct_session_limits_builder_free(), but can be cleared zero or more times with + * mct_session_limits_builder_clear() first. + * + * Returns: (transfer full): a new heap-allocated #MctSessionLimitsBuilder + * Since: 0.5.0 + */ +MctSessionLimitsBuilder * +mct_session_limits_builder_new (void) +{ + g_autoptr(MctSessionLimitsBuilder) builder = NULL; + + builder = g_new0 (MctSessionLimitsBuilder, 1); + mct_session_limits_builder_init (builder); + + return g_steal_pointer (&builder); +} + +/** + * mct_session_limits_builder_copy: + * @builder: an #MctSessionLimitsBuilder + * + * Copy the given @builder to a newly-allocated #MctSessionLimitsBuilder on the + * heap. This is safe to use with cleared, stack-allocated + * #MctSessionLimitsBuilders. + * + * Returns: (transfer full): a copy of @builder + * Since: 0.5.0 + */ +MctSessionLimitsBuilder * +mct_session_limits_builder_copy (MctSessionLimitsBuilder *builder) +{ + MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder; + g_autoptr(MctSessionLimitsBuilder) copy = NULL; + MctSessionLimitsBuilderReal *_copy; + + g_return_val_if_fail (builder != NULL, NULL); + + copy = mct_session_limits_builder_new (); + _copy = (MctSessionLimitsBuilderReal *) copy; + + mct_session_limits_builder_clear (copy); + _copy->limit_type = _builder->limit_type; + + switch (_builder->limit_type) + { + case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE: + _copy->daily_schedule.start_time = _builder->daily_schedule.start_time; + _copy->daily_schedule.end_time = _builder->daily_schedule.end_time; + break; + case MCT_SESSION_LIMITS_TYPE_NONE: + default: + break; + } + + return g_steal_pointer (©); +} + +/** + * mct_session_limits_builder_free: + * @builder: a heap-allocated #MctSessionLimitsBuilder + * + * Free an #MctSessionLimitsBuilder originally allocated using + * mct_session_limits_builder_new(). This must not be called on stack-allocated + * builders initialised using mct_session_limits_builder_init(). + * + * Since: 0.5.0 + */ +void +mct_session_limits_builder_free (MctSessionLimitsBuilder *builder) +{ + g_return_if_fail (builder != NULL); + + mct_session_limits_builder_clear (builder); + g_free (builder); +} + +/** + * mct_session_limits_builder_end: + * @builder: an initialised #MctSessionLimitsBuilder + * + * Finish constructing an #MctSessionLimits with the given @builder, and return + * it. The #MctSessionLimitsBuilder will be cleared as if + * mct_session_limits_builder_clear() had been called. + * + * Returns: (transfer full): a newly constructed #MctSessionLimits + * Since: 0.5.0 + */ +MctSessionLimits * +mct_session_limits_builder_end (MctSessionLimitsBuilder *builder) +{ + MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder; + g_autoptr(MctSessionLimits) session_limits = NULL; + + g_return_val_if_fail (_builder != NULL, NULL); + + /* Build the #MctSessionLimits. */ + session_limits = g_new0 (MctSessionLimits, 1); + session_limits->ref_count = 1; + session_limits->user_id = -1; + session_limits->limit_type = _builder->limit_type; + + switch (_builder->limit_type) + { + case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE: + session_limits->daily_start_time = _builder->daily_schedule.start_time; + session_limits->daily_end_time = _builder->daily_schedule.end_time; + break; + case MCT_SESSION_LIMITS_TYPE_NONE: + default: + /* Defaults: */ + session_limits->daily_start_time = 0; + session_limits->daily_end_time = 24 * 60 * 60; + break; + } + + mct_session_limits_builder_clear (builder); + + return g_steal_pointer (&session_limits); +} + +/** + * mct_session_limits_builder_set_none: + * @builder: an initialised #MctSessionLimitsBuilder + * + * Unset any session limits currently set in the @builder. + * + * Since: 0.5.0 + */ +void +mct_session_limits_builder_set_none (MctSessionLimitsBuilder *builder) +{ + MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + + /* This will need to free other limit types’ data first in future. */ + _builder->limit_type = MCT_SESSION_LIMITS_TYPE_NONE; +} + +/** + * mct_session_limits_builder_set_daily_schedule: + * @builder: an initialised #MctSessionLimitsBuilder + * @start_time_secs: number of seconds since midnight when the user’s session + * can first start + * @end_time_secs: number of seconds since midnight when the user’s session can + * last end + * + * Set the session limits in @builder to be a daily schedule, where sessions are + * allowed between @start_time_secs and @end_time_secs every day. + * @start_time_secs and @end_time_secs are given as offsets from the start of + * the day, in seconds. @end_time_secs must be greater than @start_time_secs. + * @end_time_secs must be at most `24 * 60 * 60`. + * + * This will overwrite any other session limits. + * + * Since: 0.5.0 + */ +void +mct_session_limits_builder_set_daily_schedule (MctSessionLimitsBuilder *builder, + guint start_time_secs, + guint end_time_secs) +{ + MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + g_return_if_fail (start_time_secs < end_time_secs); + g_return_if_fail (end_time_secs <= 24 * 60 * 60); + + /* This will need to free other limit types’ data first in future. */ + _builder->limit_type = MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE; + _builder->daily_schedule.start_time = start_time_secs; + _builder->daily_schedule.end_time = end_time_secs; +} diff --git a/libmalcontent/session-limits.h b/libmalcontent/session-limits.h index ba29ca0..1ac356c 100644 --- a/libmalcontent/session-limits.h +++ b/libmalcontent/session-limits.h @@ -56,4 +56,67 @@ gboolean mct_session_limits_check_time_remaining (MctSessionLimits *limits, guint64 *time_remaining_secs_out, gboolean *time_limit_enabled_out); +/** + * MctSessionLimitsBuilder: + * + * #MctSessionLimitsBuilder is a stack-allocated mutable structure used to build + * an #MctSessionLimits instance. Use mct_session_limits_builder_init(), various + * method calls to set properties of the session limits, and then + * mct_session_limits_builder_end(), to construct an #MctSessionLimits. + * + * Since: 0.5.0 + */ +typedef struct +{ + /*< private >*/ + guint u0; + guint u1; + guint u2; + gpointer p0[10]; +} MctSessionLimitsBuilder; + +GType mct_session_limits_builder_get_type (void); + +/** + * MCT_SESSION_LIMITS_BUILDER_INIT: + * + * Initialise a stack-allocated #MctSessionLimitsBuilder instance at declaration + * time. + * + * This is typically used with g_auto(): + * |[ + * g_auto(MctSessionLimitsBuilder) builder = MCT_SESSION_LIMITS_BUILDER_INIT (); + * ]| + * + * Since: 0.5.0 + */ +#define MCT_SESSION_LIMITS_BUILDER_INIT() \ + { \ + 0, /* MCT_SESSION_LIMITS_TYPE_NONE */ \ + 0, \ + 0, \ + /* padding: */ \ + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } \ + } + +void mct_session_limits_builder_init (MctSessionLimitsBuilder *builder); +void mct_session_limits_builder_clear (MctSessionLimitsBuilder *builder); + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (MctSessionLimitsBuilder, + mct_session_limits_builder_clear) + +MctSessionLimitsBuilder *mct_session_limits_builder_new (void); +MctSessionLimitsBuilder *mct_session_limits_builder_copy (MctSessionLimitsBuilder *builder); +void mct_session_limits_builder_free (MctSessionLimitsBuilder *builder); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (MctSessionLimitsBuilder, mct_session_limits_builder_free) + +MctSessionLimits *mct_session_limits_builder_end (MctSessionLimitsBuilder *builder); + +void mct_session_limits_builder_set_none (MctSessionLimitsBuilder *builder); + +void mct_session_limits_builder_set_daily_schedule (MctSessionLimitsBuilder *builder, + guint start_time_secs, + guint end_time_secs); + G_END_DECLS