diff --git a/gio/giomodule.c b/gio/giomodule.c index 1ead84c..8c2a464 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -1136,6 +1136,7 @@ _g_io_modules_ensure_loaded (void) /* Initialize types from built-in "modules" */ g_type_ensure (g_null_settings_backend_get_type ()); g_type_ensure (g_memory_settings_backend_get_type ()); + g_type_ensure (g_keyfile_settings_backend_get_type ()); #if defined(HAVE_INOTIFY_INIT1) g_type_ensure (g_inotify_file_monitor_get_type ()); #endif diff --git a/gio/gkeyfilesettingsbackend.c b/gio/gkeyfilesettingsbackend.c index a37978e..f535881 100644 --- a/gio/gkeyfilesettingsbackend.c +++ b/gio/gkeyfilesettingsbackend.c @@ -21,14 +21,20 @@ #include "config.h" +#include +#include + #include #include #include "gfile.h" #include "gfileinfo.h" +#include "gfileenumerator.h" #include "gfilemonitor.h" #include "gsimplepermission.h" -#include "gsettingsbackend.h" +#include "gsettingsbackendinternal.h" +#include "giomodule-priv.h" +#include "gportalsupport.h" #define G_TYPE_KEYFILE_SETTINGS_BACKEND (g_keyfile_settings_backend_get_type ()) @@ -41,6 +47,13 @@ typedef GSettingsBackendClass GKeyfileSettingsBackendClass; +typedef enum { + PROP_FILENAME = 1, + PROP_ROOT_PATH, + PROP_ROOT_GROUP, + PROP_DEFAULTS_DIR +} GKeyfileSettingsBackendProperty; + typedef struct { GSettingsBackend parent_instance; @@ -48,6 +61,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; @@ -61,10 +77,18 @@ 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) +#ifdef G_OS_WIN32 +#define EXTENSION_PRIORITY 10 +#else +#define EXTENSION_PRIORITY (glib_should_use_portal () && !glib_has_dconf_access_in_sandbox () ? 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", EXTENSION_PRIORITY)) static void compute_checksum (guint8 *digest, @@ -184,17 +208,51 @@ 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) { return_value = g_variant_parse (type, str, NULL, NULL, NULL); + + /* As a special case, support values of type %G_VARIANT_TYPE_STRING + * not being quoted, since users keep forgetting to do it and then + * getting confused. */ + if (return_value == NULL && + g_variant_type_equal (type, G_VARIANT_TYPE_STRING) && + str[0] != '\"') + { + GString *s = g_string_sized_new (strlen (str) + 2); + char *p = str; + + g_string_append_c (s, '\"'); + while (*p) + { + if (*p == '\"') + g_string_append_c (s, '\\'); + g_string_append_c (s, *p); + p++; + } + g_string_append_c (s, '\"'); + return_value = g_variant_parse (type, s->str, NULL, NULL, NULL); + g_string_free (s, TRUE); + } g_free (str); } + g_free (sysstr); + g_free (group); g_free (name); } @@ -209,6 +267,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) @@ -287,7 +348,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 @@ -355,7 +417,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 * @@ -501,6 +565,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); @@ -523,25 +590,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, GFile *other_file, @@ -567,6 +615,283 @@ 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) +{ + 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); + + load_system_settings (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 ((GKeyfileSettingsBackendProperty)prop_id) + { + case PROP_FILENAME: + /* Construct only. */ + g_assert (kfsb->file == NULL); + if (g_value_get_string (value)) + 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; + + 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; + } +} + +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 ((GKeyfileSettingsBackendProperty)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; + + 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; + } +} + +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)); + + /** + * 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)); +} + /** * g_keyfile_settings_backend_new: * @filename: the filename of the keyfile @@ -619,6 +944,11 @@ dir_changed (GFileMonitor *monitor, * 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 * @@ -626,43 +956,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/gportalsupport.c b/gio/gportalsupport.c index 2f1e825..b0a94b3 100644 --- a/gio/gportalsupport.c +++ b/gio/gportalsupport.c @@ -23,6 +23,7 @@ static gboolean flatpak_info_read; static gboolean use_portal; static gboolean network_available; +static gboolean dconf_access; static void read_flatpak_info (void) @@ -40,11 +41,13 @@ read_flatpak_info (void) use_portal = TRUE; network_available = FALSE; + dconf_access = FALSE; keyfile = g_key_file_new (); if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) { char **shared = NULL; + char *dconf_policy = NULL; shared = g_key_file_get_string_list (keyfile, "Context", "shared", NULL, NULL); if (shared) @@ -52,6 +55,14 @@ read_flatpak_info (void) network_available = g_strv_contains ((const char * const *)shared, "network"); g_strfreev (shared); } + + dconf_policy = g_key_file_get_string (keyfile, "Session Bus Policy", "ca.desrt.dconf", NULL); + if (dconf_policy) + { + if (strcmp (dconf_policy, "talk") == 0) + dconf_access = TRUE; + g_free (dconf_policy); + } } g_key_file_unref (keyfile); @@ -64,6 +75,7 @@ read_flatpak_info (void) if (var && var[0] == '1') use_portal = TRUE; network_available = TRUE; + dconf_access = TRUE; } } @@ -81,3 +93,9 @@ glib_network_available_in_sandbox (void) return network_available; } +gboolean +glib_has_dconf_access_in_sandbox (void) +{ + read_flatpak_info (); + return dconf_access; +} diff --git a/gio/gportalsupport.h b/gio/gportalsupport.h index a331f45..746f1fd 100644 --- a/gio/gportalsupport.h +++ b/gio/gportalsupport.h @@ -24,6 +24,7 @@ G_BEGIN_DECLS gboolean glib_should_use_portal (void); gboolean glib_network_available_in_sandbox (void); +gboolean glib_has_dconf_access_in_sandbox (void); G_END_DECLS diff --git a/gio/gsettingsbackendinternal.h b/gio/gsettingsbackendinternal.h index 2a76a80..9e1d51d 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 diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index acdeead..65c6074 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -1716,6 +1716,23 @@ test_keyfile (void) g_assert_cmpstr (str, ==, "howdy"); g_free (str); + /* Now check setting a string without quotes */ + called = FALSE; + g_signal_connect (settings, "changed::greeting", G_CALLBACK (key_changed_cb), &called); + + g_key_file_set_string (keyfile, "tests", "greeting", "he\"l🤗uń"); + g_free (data); + data = g_key_file_to_data (keyfile, &len, NULL); + g_file_set_contents ("keyfile/gsettings.store", data, len, &error); + g_assert_no_error (error); + while (!called) + g_main_context_iteration (NULL, FALSE); + g_signal_handlers_disconnect_by_func (settings, key_changed_cb, &called); + + str = g_settings_get_string (settings, "greeting"); + g_assert_cmpstr (str, ==, "he\"l🤗uń"); + g_free (str); + g_settings_set (settings, "farewell", "s", "cheerio"); called = FALSE;