From 26c8b29ee12f20cf63866b87b9a3c41fab9153c5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 19 Dec 2018 19:58:16 -0500 Subject: [PATCH 1/4] settings: Make the keyfile backend parameterless Make it possible to instantiate a keyfile settings backend without specifying parameters, by turning the arguments to the new() function into construct-only properties. If no filename is specified, default to $XDG_CONFIG_HOME/glib-2.0/settings/keyfile --- gio/gkeyfilesettingsbackend.c | 252 ++++++++++++++++++++++++++------- gio/gsettingsbackendinternal.h | 2 + 2 files changed, 199 insertions(+), 55 deletions(-) diff --git a/gio/gkeyfilesettingsbackend.c b/gio/gkeyfilesettingsbackend.c index a37978e83..e8e9f44a5 100644 --- a/gio/gkeyfilesettingsbackend.c +++ b/gio/gkeyfilesettingsbackend.c @@ -21,6 +21,9 @@ #include "config.h" +#include +#include + #include #include @@ -28,7 +31,8 @@ #include "gfileinfo.h" #include "gfilemonitor.h" #include "gsimplepermission.h" -#include "gsettingsbackend.h" +#include "gsettingsbackendinternal.h" +#include "giomodule.h" #define G_TYPE_KEYFILE_SETTINGS_BACKEND (g_keyfile_settings_backend_get_type ()) @@ -41,6 +45,12 @@ typedef GSettingsBackendClass GKeyfileSettingsBackendClass; +enum { + PROP_FILENAME = 1, + PROP_ROOT_PATH, + PROP_ROOT_GROUP +}; + typedef struct { GSettingsBackend parent_instance; @@ -61,7 +71,6 @@ typedef struct GFileMonitor *dir_monitor; } GKeyfileSettingsBackend; -static GType g_keyfile_settings_backend_get_type (void); G_DEFINE_TYPE (GKeyfileSettingsBackend, g_keyfile_settings_backend, G_TYPE_SETTINGS_BACKEND) @@ -287,7 +296,8 @@ g_keyfile_settings_backend_check_one (gpointer key, { WriteManyData *data = user_data; - return data->failed = !path_is_valid (data->kfsb, key); + return data->failed = g_hash_table_contains (data->kfsb->system_locks, key) || + !path_is_valid (data->kfsb, key); } static gboolean @@ -522,25 +532,6 @@ g_keyfile_settings_backend_init (GKeyfileSettingsBackend *kfsb) { } -static void -g_keyfile_settings_backend_class_init (GKeyfileSettingsBackendClass *class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (class); - - object_class->finalize = g_keyfile_settings_backend_finalize; - - class->read = g_keyfile_settings_backend_read; - class->write = g_keyfile_settings_backend_write; - class->write_tree = g_keyfile_settings_backend_write_tree; - class->reset = g_keyfile_settings_backend_reset; - class->get_writable = g_keyfile_settings_backend_get_writable; - class->get_permission = g_keyfile_settings_backend_get_permission; - /* No need to implement subscribed/unsubscribe: the only point would be to - * stop monitoring the file when there's no GSettings anymore, which is no - * big win. - */ -} - static void file_changed (GFileMonitor *monitor, GFile *file, @@ -567,6 +558,185 @@ dir_changed (GFileMonitor *monitor, g_keyfile_settings_backend_keyfile_writable (kfsb); } +static void +g_keyfile_settings_backend_constructed (GObject *object) +{ + GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); + + if (kfsb->file == NULL) + { + char *filename = g_build_filename (g_get_user_config_dir (), + "glib-2.0", "settings", "keyfile", + NULL); + kfsb->file = g_file_new_for_path (filename); + g_free (filename); + } + + if (kfsb->prefix == NULL) + { + kfsb->prefix = g_strdup ("/"); + kfsb->prefix_len = 1; + } + + kfsb->keyfile = g_key_file_new (); + kfsb->permission = g_simple_permission_new (TRUE); + + kfsb->dir = g_file_get_parent (kfsb->file); + g_file_make_directory_with_parents (kfsb->dir, NULL, NULL); + + kfsb->file_monitor = g_file_monitor (kfsb->file, G_FILE_MONITOR_NONE, NULL, NULL); + kfsb->dir_monitor = g_file_monitor (kfsb->dir, G_FILE_MONITOR_NONE, NULL, NULL); + + compute_checksum (kfsb->digest, NULL, 0); + + g_signal_connect (kfsb->file_monitor, "changed", + G_CALLBACK (file_changed), kfsb); + g_signal_connect (kfsb->dir_monitor, "changed", + G_CALLBACK (dir_changed), kfsb); + + g_keyfile_settings_backend_keyfile_writable (kfsb); + g_keyfile_settings_backend_keyfile_reload (kfsb); +} + +static void +g_keyfile_settings_backend_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); + + switch (prop_id) + { + case PROP_FILENAME: + /* Construct only. */ + g_assert (kfsb->file == NULL); + kfsb->file = g_file_new_for_path (g_value_get_string (value)); + break; + + case PROP_ROOT_PATH: + /* Construct only. */ + g_assert (kfsb->prefix == NULL); + kfsb->prefix = g_value_dup_string (value); + if (kfsb->prefix) + kfsb->prefix_len = strlen (kfsb->prefix); + break; + + case PROP_ROOT_GROUP: + /* Construct only. */ + g_assert (kfsb->root_group == NULL); + kfsb->root_group = g_value_dup_string (value); + if (kfsb->root_group) + kfsb->root_group_len = strlen (kfsb->root_group); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_keyfile_settings_backend_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); + + switch (prop_id) + { + case PROP_FILENAME: + g_value_set_string (value, g_file_peek_path (kfsb->file)); + break; + + case PROP_ROOT_PATH: + g_value_set_string (value, kfsb->prefix); + break; + + case PROP_ROOT_GROUP: + g_value_set_string (value, kfsb->root_group); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_keyfile_settings_backend_class_init (GKeyfileSettingsBackendClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = g_keyfile_settings_backend_finalize; + object_class->constructed = g_keyfile_settings_backend_constructed; + object_class->get_property = g_keyfile_settings_backend_get_property; + object_class->set_property = g_keyfile_settings_backend_set_property; + + class->read = g_keyfile_settings_backend_read; + class->write = g_keyfile_settings_backend_write; + class->write_tree = g_keyfile_settings_backend_write_tree; + class->reset = g_keyfile_settings_backend_reset; + class->get_writable = g_keyfile_settings_backend_get_writable; + class->get_permission = g_keyfile_settings_backend_get_permission; + /* No need to implement subscribed/unsubscribe: the only point would be to + * stop monitoring the file when there's no GSettings anymore, which is no + * big win. + */ + + /** + * GKeyfileSettingsBackend:filename: + * + * The location where the settings are stored on disk. + * + * Defaults to `$XDG_CONFIG_HOME/glib-2.0/settings/keyfile`. + */ + g_object_class_install_property (object_class, + PROP_FILENAME, + g_param_spec_string ("filename", + P_("Filename"), + P_("The filename"), + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GKeyfileSettingsBackend:root-path: + * + * All settings read to or written from the backend must fall under the + * path given in @root_path (which must start and end with a slash and + * not contain two consecutive slashes). @root_path may be "/". + * + * Defaults to "/". + */ + g_object_class_install_property (object_class, + PROP_ROOT_PATH, + g_param_spec_string ("root-path", + P_("Root path"), + P_("The root path"), + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GKeyfileSettingsBackend:root-group: + * + * If @root_group is non-%NULL then it specifies the name of the keyfile + * group used for keys that are written directly below the root path. + * + * Defaults to NULL. + */ + g_object_class_install_property (object_class, + PROP_ROOT_GROUP, + g_param_spec_string ("root-group", + P_("Root group"), + P_("The root group"), + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + /** * g_keyfile_settings_backend_new: * @filename: the filename of the keyfile @@ -626,43 +796,15 @@ g_keyfile_settings_backend_new (const gchar *filename, const gchar *root_path, const gchar *root_group) { - GKeyfileSettingsBackend *kfsb; - g_return_val_if_fail (filename != NULL, NULL); g_return_val_if_fail (root_path != NULL, NULL); g_return_val_if_fail (g_str_has_prefix (root_path, "/"), NULL); g_return_val_if_fail (g_str_has_suffix (root_path, "/"), NULL); g_return_val_if_fail (strstr (root_path, "//") == NULL, NULL); - kfsb = g_object_new (G_TYPE_KEYFILE_SETTINGS_BACKEND, NULL); - kfsb->keyfile = g_key_file_new (); - kfsb->permission = g_simple_permission_new (TRUE); - - kfsb->file = g_file_new_for_path (filename); - kfsb->dir = g_file_get_parent (kfsb->file); - g_file_make_directory_with_parents (kfsb->dir, NULL, NULL); - - kfsb->file_monitor = g_file_monitor (kfsb->file, 0, NULL, NULL); - kfsb->dir_monitor = g_file_monitor (kfsb->dir, 0, NULL, NULL); - - kfsb->prefix_len = strlen (root_path); - kfsb->prefix = g_strdup (root_path); - - if (root_group) - { - kfsb->root_group_len = strlen (root_group); - kfsb->root_group = g_strdup (root_group); - } - - compute_checksum (kfsb->digest, NULL, 0); - - g_signal_connect (kfsb->file_monitor, "changed", - G_CALLBACK (file_changed), kfsb); - g_signal_connect (kfsb->dir_monitor, "changed", - G_CALLBACK (dir_changed), kfsb); - - g_keyfile_settings_backend_keyfile_writable (kfsb); - g_keyfile_settings_backend_keyfile_reload (kfsb); - - return G_SETTINGS_BACKEND (kfsb); + return G_SETTINGS_BACKEND (g_object_new (G_TYPE_KEYFILE_SETTINGS_BACKEND, + "filename", filename, + "root-path", root_path, + "root-group", root_group, + NULL)); } diff --git a/gio/gsettingsbackendinternal.h b/gio/gsettingsbackendinternal.h index 2a76a80bc..9e1d51dba 100644 --- a/gio/gsettingsbackendinternal.h +++ b/gio/gsettingsbackendinternal.h @@ -87,6 +87,8 @@ GType g_null_settings_backend_get_type (void); GType g_memory_settings_backend_get_type (void); +GType g_keyfile_settings_backend_get_type (void); + #ifdef HAVE_COCOA GType g_nextstep_settings_backend_get_type (void); #endif From 5357a23321c699465021461f55f34e48c81440dd Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 19 Dec 2018 20:03:29 -0500 Subject: [PATCH 2/4] settings: Register the keyfile backend as extension This was not done previously because the backend could not be instantiated without parameters. --- gio/gkeyfilesettingsbackend.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gio/gkeyfilesettingsbackend.c b/gio/gkeyfilesettingsbackend.c index e8e9f44a5..0e9789cde 100644 --- a/gio/gkeyfilesettingsbackend.c +++ b/gio/gkeyfilesettingsbackend.c @@ -32,7 +32,7 @@ #include "gfilemonitor.h" #include "gsimplepermission.h" #include "gsettingsbackendinternal.h" -#include "giomodule.h" +#include "giomodule-priv.h" #define G_TYPE_KEYFILE_SETTINGS_BACKEND (g_keyfile_settings_backend_get_type ()) @@ -71,9 +71,12 @@ typedef struct GFileMonitor *dir_monitor; } GKeyfileSettingsBackend; -G_DEFINE_TYPE (GKeyfileSettingsBackend, - g_keyfile_settings_backend, - G_TYPE_SETTINGS_BACKEND) +G_DEFINE_TYPE_WITH_CODE (GKeyfileSettingsBackend, + g_keyfile_settings_backend, + G_TYPE_SETTINGS_BACKEND, + _g_io_modules_ensure_extension_points_registered (); + g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, + g_define_type_id, "keyfile", 10)) static void compute_checksum (guint8 *digest, From c63e3a4ada9852138042cb69441644bfe06ac430 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 5 Nov 2018 16:07:55 -0500 Subject: [PATCH 3/4] settings: Add support for defaults to keyfile backend Stacked databases and locks are dconf features that allow management software like Fleet Commander to set system-wide defaults and overrides centrally for applications. This patch adds minimal support for the same to the keyfile backend. We look for a keyfile named 'defaults' and a lock-list named 'locks'. Suitable files can be produced from a dconf database with dconf dump and dconf list-locks, respectively. The default location for these files is /etc/glib-2.0/settings/. For test purposes, this can be overwritten with the GSETTINGS_DEFAULTS_DIR environment variable. Writes always go to the per-user keyfile. --- gio/gkeyfilesettingsbackend.c | 138 ++++++++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 6 deletions(-) diff --git a/gio/gkeyfilesettingsbackend.c b/gio/gkeyfilesettingsbackend.c index 0e9789cde..0a4e81511 100644 --- a/gio/gkeyfilesettingsbackend.c +++ b/gio/gkeyfilesettingsbackend.c @@ -29,6 +29,7 @@ #include "gfile.h" #include "gfileinfo.h" +#include "gfileenumerator.h" #include "gfilemonitor.h" #include "gsimplepermission.h" #include "gsettingsbackendinternal.h" @@ -45,11 +46,12 @@ typedef GSettingsBackendClass GKeyfileSettingsBackendClass; -enum { +typedef enum { PROP_FILENAME = 1, PROP_ROOT_PATH, - PROP_ROOT_GROUP -}; + PROP_ROOT_GROUP, + PROP_DEFAULTS_DIR +} GKeyfileSettingsBackendProperty; typedef struct { @@ -58,6 +60,9 @@ typedef struct GKeyFile *keyfile; GPermission *permission; gboolean writable; + char *defaults_dir; + GKeyFile *system_keyfile; + GHashTable *system_locks; /* Used as a set, owning the strings it contains */ gchar *prefix; gint prefix_len; @@ -196,10 +201,19 @@ get_from_keyfile (GKeyfileSettingsBackend *kfsb, if (convert_path (kfsb, key, &group, &name)) { gchar *str; + gchar *sysstr; g_assert (*name); + sysstr = g_key_file_get_value (kfsb->system_keyfile, group, name, NULL); str = g_key_file_get_value (kfsb->keyfile, group, name, NULL); + if (sysstr && + (g_hash_table_contains (kfsb->system_locks, key) || + str == NULL)) + { + g_free (str); + str = g_steal_pointer (&sysstr); + } if (str) { @@ -207,6 +221,8 @@ get_from_keyfile (GKeyfileSettingsBackend *kfsb, g_free (str); } + g_free (sysstr); + g_free (group); g_free (name); } @@ -221,6 +237,9 @@ set_to_keyfile (GKeyfileSettingsBackend *kfsb, { gchar *group, *name; + if (g_hash_table_contains (kfsb->system_locks, key)) + return FALSE; + if (convert_path (kfsb, key, &group, &name)) { if (value) @@ -368,7 +387,9 @@ g_keyfile_settings_backend_get_writable (GSettingsBackend *backend, { GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend); - return kfsb->writable && path_is_valid (kfsb, name); + return kfsb->writable && + !g_hash_table_contains (kfsb->system_locks, name) && + path_is_valid (kfsb, name); } static GPermission * @@ -514,6 +535,9 @@ g_keyfile_settings_backend_finalize (GObject *object) g_key_file_free (kfsb->keyfile); g_object_unref (kfsb->permission); + g_key_file_unref (kfsb->system_keyfile); + g_hash_table_unref (kfsb->system_locks); + g_free (kfsb->defaults_dir); g_file_monitor_cancel (kfsb->file_monitor); g_object_unref (kfsb->file_monitor); @@ -561,6 +585,75 @@ dir_changed (GFileMonitor *monitor, g_keyfile_settings_backend_keyfile_writable (kfsb); } +static void +load_system_settings (GKeyfileSettingsBackend *kfsb) +{ + GError *error = NULL; + const char *dir = "/etc/glib-2.0/settings"; + char *path; + char *contents; + + kfsb->system_keyfile = g_key_file_new (); + kfsb->system_locks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + if (kfsb->defaults_dir) + dir = kfsb->defaults_dir; + + path = g_build_filename (dir, "defaults", NULL); + + /* The defaults are in the same keyfile format that we use for the settings. + * It can be produced from a dconf database using: dconf dump + */ + if (!g_key_file_load_from_file (kfsb->system_keyfile, path, G_KEY_FILE_NONE, &error)) + { + if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + g_warning ("Failed to read %s: %s", path, error->message); + g_clear_error (&error); + } + else + g_debug ("Loading default settings from %s", path); + + g_free (path); + + path = g_build_filename (dir, "locks", NULL); + + /* The locks file is a text file containing a list paths to lock, one per line. + * It can be produced from a dconf database using: dconf list-locks + */ + if (!g_file_get_contents (path, &contents, NULL, &error)) + { + if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + g_warning ("Failed to read %s: %s", path, error->message); + g_clear_error (&error); + } + else + { + char **lines; + gsize i; + + g_debug ("Loading locks from %s", path); + + lines = g_strsplit (contents, "\n", 0); + for (i = 0; lines[i]; i++) + { + char *line = lines[i]; + if (line[0] == '#' || line[0] == '\0') + { + g_free (line); + continue; + } + + g_debug ("Locking key %s", line); + g_hash_table_add (kfsb->system_locks, g_steal_pointer (&line)); + } + + g_free (lines); + } + g_free (contents); + + g_free (path); +} + static void g_keyfile_settings_backend_constructed (GObject *object) { @@ -599,6 +692,8 @@ g_keyfile_settings_backend_constructed (GObject *object) g_keyfile_settings_backend_keyfile_writable (kfsb); g_keyfile_settings_backend_keyfile_reload (kfsb); + + load_system_settings (kfsb); } static void @@ -609,7 +704,7 @@ g_keyfile_settings_backend_set_property (GObject *object, { GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); - switch (prop_id) + switch ((GKeyfileSettingsBackendProperty)prop_id) { case PROP_FILENAME: /* Construct only. */ @@ -633,6 +728,12 @@ g_keyfile_settings_backend_set_property (GObject *object, kfsb->root_group_len = strlen (kfsb->root_group); break; + case PROP_DEFAULTS_DIR: + /* Construct only. */ + g_assert (kfsb->defaults_dir == NULL); + kfsb->defaults_dir = g_value_dup_string (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -647,7 +748,7 @@ g_keyfile_settings_backend_get_property (GObject *object, { GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); - switch (prop_id) + switch ((GKeyfileSettingsBackendProperty)prop_id) { case PROP_FILENAME: g_value_set_string (value, g_file_peek_path (kfsb->file)); @@ -661,6 +762,10 @@ g_keyfile_settings_backend_get_property (GObject *object, g_value_set_string (value, kfsb->root_group); break; + case PROP_DEFAULTS_DIR: + g_value_set_string (value, kfsb->defaults_dir); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -738,6 +843,22 @@ g_keyfile_settings_backend_class_init (GKeyfileSettingsBackendClass *class) NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * GKeyfileSettingsBackend:default-dir: + * + * The directory where the system defaults and locks are located. + * + * Defaults to `/etc/glib-2.0/settings`. + */ + g_object_class_install_property (object_class, + PROP_DEFAULTS_DIR, + g_param_spec_string ("defaults-dir", + P_("Default dir"), + P_("Defaults dir"), + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); } /** @@ -792,6 +913,11 @@ g_keyfile_settings_backend_class_init (GKeyfileSettingsBackendClass *class) * characters in your path names or '=' in your key names you may be in * trouble. * + * The backend reads default values from a keyfile called `defaults` in + * the directory specified by the #GKeyfileSettingsBackend:defaults-dir property, + * and a list of locked keys from a text file with the name `locks` in + * the same location. + * * Returns: (transfer full): a keyfile-backed #GSettingsBackend **/ GSettingsBackend * From 285452237989b957e0745a412c49497771ccf8b5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 2 Nov 2018 23:00:49 -0400 Subject: [PATCH 4/4] settings: Prefer the keyfile backend when sandboxed When we are in a sandboxed situation, bump the priority of the keyfile settings backend above the dconf one, so we use a keyfile inside the sandbox instead of requiring holes in the sandbox for dconf. --- gio/gkeyfilesettingsbackend.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gio/gkeyfilesettingsbackend.c b/gio/gkeyfilesettingsbackend.c index 0a4e81511..398cb053a 100644 --- a/gio/gkeyfilesettingsbackend.c +++ b/gio/gkeyfilesettingsbackend.c @@ -34,6 +34,7 @@ #include "gsimplepermission.h" #include "gsettingsbackendinternal.h" #include "giomodule-priv.h" +#include "gportalsupport.h" #define G_TYPE_KEYFILE_SETTINGS_BACKEND (g_keyfile_settings_backend_get_type ()) @@ -76,12 +77,18 @@ typedef struct GFileMonitor *dir_monitor; } GKeyfileSettingsBackend; +#ifdef G_OS_WIN32 +#define EXTENSION_PRIORITY 10 +#else +#define EXTENSION_PRIORITY (glib_should_use_portal () ? 110 : 10) +#endif + G_DEFINE_TYPE_WITH_CODE (GKeyfileSettingsBackend, g_keyfile_settings_backend, G_TYPE_SETTINGS_BACKEND, _g_io_modules_ensure_extension_points_registered (); g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, - g_define_type_id, "keyfile", 10)) + g_define_type_id, "keyfile", EXTENSION_PRIORITY)) static void compute_checksum (guint8 *digest,