/src/glib/gio/gkeyfilesettingsbackend.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright © 2010 Codethink Limited |
3 | | * Copyright © 2010 Novell, Inc. |
4 | | * |
5 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
6 | | * |
7 | | * This library is free software; you can redistribute it and/or |
8 | | * modify it under the terms of the GNU Lesser General Public |
9 | | * License as published by the Free Software Foundation; either |
10 | | * version 2.1 of the License, or (at your option) any later version. |
11 | | * |
12 | | * This library is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | | * Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public |
18 | | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
19 | | * |
20 | | * Authors: Vincent Untz <vuntz@gnome.org> |
21 | | * Ryan Lortie <desrt@desrt.ca> |
22 | | */ |
23 | | |
24 | | #include "config.h" |
25 | | |
26 | | #include <glib.h> |
27 | | #include <glibintl.h> |
28 | | |
29 | | #include <stdio.h> |
30 | | #include <string.h> |
31 | | |
32 | | #include "gfile.h" |
33 | | #include "gfileinfo.h" |
34 | | #include "gfileenumerator.h" |
35 | | #include "gfilemonitor.h" |
36 | | #include "gsimplepermission.h" |
37 | | #include "gsettingsbackendinternal.h" |
38 | | #include "giomodule-priv.h" |
39 | | #include "gportalsupport.h" |
40 | | |
41 | | |
42 | | #define G_TYPE_KEYFILE_SETTINGS_BACKEND (g_keyfile_settings_backend_get_type ()) |
43 | 0 | #define G_KEYFILE_SETTINGS_BACKEND(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ |
44 | 0 | G_TYPE_KEYFILE_SETTINGS_BACKEND, \ |
45 | 0 | GKeyfileSettingsBackend)) |
46 | | #define G_IS_KEYFILE_SETTINGS_BACKEND(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ |
47 | | G_TYPE_KEYFILE_SETTINGS_BACKEND)) |
48 | | |
49 | | |
50 | | typedef GSettingsBackendClass GKeyfileSettingsBackendClass; |
51 | | |
52 | | typedef enum { |
53 | | PROP_FILENAME = 1, |
54 | | PROP_ROOT_PATH, |
55 | | PROP_ROOT_GROUP, |
56 | | PROP_DEFAULTS_DIR |
57 | | } GKeyfileSettingsBackendProperty; |
58 | | |
59 | | typedef struct |
60 | | { |
61 | | GSettingsBackend parent_instance; |
62 | | |
63 | | GKeyFile *keyfile; |
64 | | GPermission *permission; |
65 | | gboolean writable; |
66 | | char *defaults_dir; |
67 | | GKeyFile *system_keyfile; |
68 | | GHashTable *system_locks; /* Used as a set, owning the strings it contains */ |
69 | | |
70 | | gchar *prefix; |
71 | | gsize prefix_len; |
72 | | gchar *root_group; |
73 | | gsize root_group_len; |
74 | | |
75 | | GFile *file; |
76 | | GFileMonitor *file_monitor; |
77 | | guint8 digest[32]; |
78 | | GFile *dir; |
79 | | GFileMonitor *dir_monitor; |
80 | | } GKeyfileSettingsBackend; |
81 | | |
82 | | #ifdef G_OS_WIN32 |
83 | | #define EXTENSION_PRIORITY 10 |
84 | | #else |
85 | | #define EXTENSION_PRIORITY (glib_should_use_portal () && !glib_has_dconf_access_in_sandbox () ? 110 : 10) |
86 | | #endif |
87 | | |
88 | | G_DEFINE_TYPE_WITH_CODE (GKeyfileSettingsBackend, |
89 | | g_keyfile_settings_backend, |
90 | | G_TYPE_SETTINGS_BACKEND, |
91 | | _g_io_modules_ensure_extension_points_registered (); |
92 | | g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, |
93 | | g_define_type_id, "keyfile", EXTENSION_PRIORITY)) |
94 | | |
95 | | static void |
96 | | compute_checksum (guint8 *digest, |
97 | | gconstpointer contents, |
98 | | gsize length) |
99 | 0 | { |
100 | 0 | GChecksum *checksum; |
101 | 0 | gsize len = 32; |
102 | |
|
103 | 0 | checksum = g_checksum_new (G_CHECKSUM_SHA256); |
104 | 0 | g_checksum_update (checksum, contents, length); |
105 | 0 | g_checksum_get_digest (checksum, digest, &len); |
106 | 0 | g_checksum_free (checksum); |
107 | 0 | g_assert (len == 32); |
108 | 0 | } |
109 | | |
110 | | static gboolean |
111 | | g_keyfile_settings_backend_keyfile_write (GKeyfileSettingsBackend *kfsb, |
112 | | GError **error) |
113 | 0 | { |
114 | 0 | gchar *contents; |
115 | 0 | gsize length; |
116 | 0 | gboolean success; |
117 | |
|
118 | 0 | contents = g_key_file_to_data (kfsb->keyfile, &length, NULL); |
119 | 0 | success = g_file_replace_contents (kfsb->file, contents, length, NULL, FALSE, |
120 | 0 | G_FILE_CREATE_REPLACE_DESTINATION | |
121 | 0 | G_FILE_CREATE_PRIVATE, |
122 | 0 | NULL, NULL, error); |
123 | |
|
124 | 0 | compute_checksum (kfsb->digest, contents, length); |
125 | 0 | g_free (contents); |
126 | |
|
127 | 0 | return success; |
128 | 0 | } |
129 | | |
130 | | static gboolean |
131 | | group_name_matches (const gchar *group_name, |
132 | | const gchar *prefix) |
133 | 0 | { |
134 | | /* sort of like g_str_has_prefix() except that it must be an exact |
135 | | * match or the prefix followed by '/'. |
136 | | * |
137 | | * for example 'a' is a prefix of 'a' and 'a/b' but not 'ab'. |
138 | | */ |
139 | 0 | gint i; |
140 | |
|
141 | 0 | for (i = 0; prefix[i]; i++) |
142 | 0 | if (prefix[i] != group_name[i]) |
143 | 0 | return FALSE; |
144 | | |
145 | 0 | return group_name[i] == '\0' || group_name[i] == '/'; |
146 | 0 | } |
147 | | |
148 | | static gboolean |
149 | | convert_path (GKeyfileSettingsBackend *kfsb, |
150 | | const gchar *key, |
151 | | gchar **group, |
152 | | gchar **basename) |
153 | 0 | { |
154 | 0 | gsize key_len = strlen (key); |
155 | 0 | const gchar *last_slash; |
156 | |
|
157 | 0 | if (key_len < kfsb->prefix_len || |
158 | 0 | memcmp (key, kfsb->prefix, kfsb->prefix_len) != 0) |
159 | 0 | return FALSE; |
160 | | |
161 | 0 | key_len -= kfsb->prefix_len; |
162 | 0 | key += kfsb->prefix_len; |
163 | |
|
164 | 0 | last_slash = strrchr (key, '/'); |
165 | | |
166 | | /* Disallow empty group names or key names */ |
167 | 0 | if (key_len == 0 || |
168 | 0 | (last_slash != NULL && |
169 | 0 | (*(last_slash + 1) == '\0' || |
170 | 0 | last_slash == key))) |
171 | 0 | return FALSE; |
172 | | |
173 | 0 | if (kfsb->root_group) |
174 | 0 | { |
175 | | /* if a root_group was specified, make sure the user hasn't given |
176 | | * a path that ghosts that group name |
177 | | */ |
178 | 0 | if (last_slash != NULL && last_slash - key >= 0 && |
179 | 0 | (gsize) (last_slash - key) == kfsb->root_group_len && |
180 | 0 | memcmp (key, kfsb->root_group, last_slash - key) == 0) |
181 | 0 | return FALSE; |
182 | 0 | } |
183 | 0 | else |
184 | 0 | { |
185 | | /* if no root_group was given, ensure that the user gave a path */ |
186 | 0 | if (last_slash == NULL) |
187 | 0 | return FALSE; |
188 | 0 | } |
189 | | |
190 | 0 | if (group) |
191 | 0 | { |
192 | 0 | if (last_slash != NULL) |
193 | 0 | { |
194 | 0 | *group = g_memdup2 (key, (last_slash - key) + 1); |
195 | 0 | (*group)[(last_slash - key)] = '\0'; |
196 | 0 | } |
197 | 0 | else |
198 | 0 | *group = g_strdup (kfsb->root_group); |
199 | 0 | } |
200 | |
|
201 | 0 | if (basename) |
202 | 0 | { |
203 | 0 | if (last_slash != NULL) |
204 | 0 | *basename = g_memdup2 (last_slash + 1, key_len - (last_slash - key)); |
205 | 0 | else |
206 | 0 | *basename = g_strdup (key); |
207 | 0 | } |
208 | |
|
209 | 0 | return TRUE; |
210 | 0 | } |
211 | | |
212 | | static gboolean |
213 | | path_is_valid (GKeyfileSettingsBackend *kfsb, |
214 | | const gchar *path) |
215 | 0 | { |
216 | 0 | return convert_path (kfsb, path, NULL, NULL); |
217 | 0 | } |
218 | | |
219 | | static GVariant * |
220 | | get_from_keyfile (GKeyfileSettingsBackend *kfsb, |
221 | | const GVariantType *type, |
222 | | const gchar *key) |
223 | 0 | { |
224 | 0 | GVariant *return_value = NULL; |
225 | 0 | gchar *group, *name; |
226 | |
|
227 | 0 | if (convert_path (kfsb, key, &group, &name)) |
228 | 0 | { |
229 | 0 | gchar *str; |
230 | 0 | gchar *sysstr; |
231 | |
|
232 | 0 | g_assert (*name); |
233 | | |
234 | 0 | sysstr = g_key_file_get_value (kfsb->system_keyfile, group, name, NULL); |
235 | 0 | str = g_key_file_get_value (kfsb->keyfile, group, name, NULL); |
236 | 0 | if (sysstr && |
237 | 0 | (g_hash_table_contains (kfsb->system_locks, key) || |
238 | 0 | str == NULL)) |
239 | 0 | { |
240 | 0 | g_free (str); |
241 | 0 | str = g_steal_pointer (&sysstr); |
242 | 0 | } |
243 | |
|
244 | 0 | if (str) |
245 | 0 | { |
246 | 0 | return_value = g_variant_parse (type, str, NULL, NULL, NULL); |
247 | | |
248 | | /* As a special case, support values of type %G_VARIANT_TYPE_STRING |
249 | | * not being quoted, since users keep forgetting to do it and then |
250 | | * getting confused. */ |
251 | 0 | if (return_value == NULL && |
252 | 0 | g_variant_type_equal (type, G_VARIANT_TYPE_STRING) && |
253 | 0 | str[0] != '\"') |
254 | 0 | { |
255 | 0 | GString *s = g_string_sized_new (strlen (str) + 2); |
256 | 0 | char *p = str; |
257 | |
|
258 | 0 | g_string_append_c (s, '\"'); |
259 | 0 | while (*p) |
260 | 0 | { |
261 | 0 | if (*p == '\"') |
262 | 0 | g_string_append_c (s, '\\'); |
263 | 0 | g_string_append_c (s, *p); |
264 | 0 | p++; |
265 | 0 | } |
266 | 0 | g_string_append_c (s, '\"'); |
267 | 0 | return_value = g_variant_parse (type, s->str, NULL, NULL, NULL); |
268 | 0 | g_string_free (s, TRUE); |
269 | 0 | } |
270 | 0 | g_free (str); |
271 | 0 | } |
272 | |
|
273 | 0 | g_free (sysstr); |
274 | |
|
275 | 0 | g_free (group); |
276 | 0 | g_free (name); |
277 | 0 | } |
278 | | |
279 | 0 | return return_value; |
280 | 0 | } |
281 | | |
282 | | static gboolean |
283 | | set_to_keyfile (GKeyfileSettingsBackend *kfsb, |
284 | | const gchar *key, |
285 | | GVariant *value) |
286 | 0 | { |
287 | 0 | gchar *group, *name; |
288 | |
|
289 | 0 | if (g_hash_table_contains (kfsb->system_locks, key)) |
290 | 0 | return FALSE; |
291 | | |
292 | 0 | if (convert_path (kfsb, key, &group, &name)) |
293 | 0 | { |
294 | 0 | if (value) |
295 | 0 | { |
296 | 0 | gchar *str = g_variant_print (value, FALSE); |
297 | 0 | g_key_file_set_value (kfsb->keyfile, group, name, str); |
298 | 0 | g_variant_unref (g_variant_ref_sink (value)); |
299 | 0 | g_free (str); |
300 | 0 | } |
301 | 0 | else |
302 | 0 | { |
303 | 0 | if (*name == '\0') |
304 | 0 | { |
305 | 0 | gchar **groups; |
306 | 0 | gint i; |
307 | |
|
308 | 0 | groups = g_key_file_get_groups (kfsb->keyfile, NULL); |
309 | |
|
310 | 0 | for (i = 0; groups[i]; i++) |
311 | 0 | if (group_name_matches (groups[i], group)) |
312 | 0 | g_key_file_remove_group (kfsb->keyfile, groups[i], NULL); |
313 | |
|
314 | 0 | g_strfreev (groups); |
315 | 0 | } |
316 | 0 | else |
317 | 0 | g_key_file_remove_key (kfsb->keyfile, group, name, NULL); |
318 | 0 | } |
319 | |
|
320 | 0 | g_free (group); |
321 | 0 | g_free (name); |
322 | |
|
323 | 0 | return TRUE; |
324 | 0 | } |
325 | | |
326 | 0 | return FALSE; |
327 | 0 | } |
328 | | |
329 | | static GVariant * |
330 | | g_keyfile_settings_backend_read (GSettingsBackend *backend, |
331 | | const gchar *key, |
332 | | const GVariantType *expected_type, |
333 | | gboolean default_value) |
334 | 0 | { |
335 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend); |
336 | |
|
337 | 0 | if (default_value) |
338 | 0 | return NULL; |
339 | | |
340 | 0 | return get_from_keyfile (kfsb, expected_type, key); |
341 | 0 | } |
342 | | |
343 | | typedef struct |
344 | | { |
345 | | GKeyfileSettingsBackend *kfsb; |
346 | | gboolean failed; |
347 | | } WriteManyData; |
348 | | |
349 | | static gboolean |
350 | | g_keyfile_settings_backend_write_one (gpointer key, |
351 | | gpointer value, |
352 | | gpointer user_data) |
353 | 0 | { |
354 | 0 | WriteManyData *data = user_data; |
355 | 0 | gboolean success G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; |
356 | |
|
357 | 0 | success = set_to_keyfile (data->kfsb, key, value); |
358 | 0 | g_assert (success); |
359 | | |
360 | 0 | return FALSE; |
361 | 0 | } |
362 | | |
363 | | static gboolean |
364 | | g_keyfile_settings_backend_check_one (gpointer key, |
365 | | gpointer value, |
366 | | gpointer user_data) |
367 | 0 | { |
368 | 0 | WriteManyData *data = user_data; |
369 | |
|
370 | 0 | return data->failed = g_hash_table_contains (data->kfsb->system_locks, key) || |
371 | 0 | !path_is_valid (data->kfsb, key); |
372 | 0 | } |
373 | | |
374 | | static gboolean |
375 | | g_keyfile_settings_backend_write_tree (GSettingsBackend *backend, |
376 | | GTree *tree, |
377 | | gpointer origin_tag) |
378 | 0 | { |
379 | 0 | WriteManyData data = { G_KEYFILE_SETTINGS_BACKEND (backend), 0 }; |
380 | 0 | gboolean success; |
381 | 0 | GError *error = NULL; |
382 | |
|
383 | 0 | if (!data.kfsb->writable) |
384 | 0 | return FALSE; |
385 | | |
386 | 0 | g_tree_foreach (tree, g_keyfile_settings_backend_check_one, &data); |
387 | |
|
388 | 0 | if (data.failed) |
389 | 0 | return FALSE; |
390 | | |
391 | 0 | g_tree_foreach (tree, g_keyfile_settings_backend_write_one, &data); |
392 | 0 | success = g_keyfile_settings_backend_keyfile_write (data.kfsb, &error); |
393 | 0 | if (error) |
394 | 0 | { |
395 | 0 | g_warning ("Failed to write keyfile to %s: %s", g_file_peek_path (data.kfsb->file), error->message); |
396 | 0 | g_error_free (error); |
397 | 0 | } |
398 | |
|
399 | 0 | g_settings_backend_changed_tree (backend, tree, origin_tag); |
400 | |
|
401 | 0 | return success; |
402 | 0 | } |
403 | | |
404 | | static gboolean |
405 | | g_keyfile_settings_backend_write (GSettingsBackend *backend, |
406 | | const gchar *key, |
407 | | GVariant *value, |
408 | | gpointer origin_tag) |
409 | 0 | { |
410 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend); |
411 | 0 | gboolean success; |
412 | 0 | GError *error = NULL; |
413 | |
|
414 | 0 | if (!kfsb->writable) |
415 | 0 | return FALSE; |
416 | | |
417 | 0 | success = set_to_keyfile (kfsb, key, value); |
418 | |
|
419 | 0 | if (success) |
420 | 0 | { |
421 | 0 | g_settings_backend_changed (backend, key, origin_tag); |
422 | 0 | success = g_keyfile_settings_backend_keyfile_write (kfsb, &error); |
423 | 0 | if (error) |
424 | 0 | { |
425 | 0 | g_warning ("Failed to write keyfile to %s: %s", g_file_peek_path (kfsb->file), error->message); |
426 | 0 | g_error_free (error); |
427 | 0 | } |
428 | 0 | } |
429 | |
|
430 | 0 | return success; |
431 | 0 | } |
432 | | |
433 | | static void |
434 | | g_keyfile_settings_backend_reset (GSettingsBackend *backend, |
435 | | const gchar *key, |
436 | | gpointer origin_tag) |
437 | 0 | { |
438 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend); |
439 | 0 | GError *error = NULL; |
440 | |
|
441 | 0 | if (set_to_keyfile (kfsb, key, NULL)) |
442 | 0 | { |
443 | 0 | g_keyfile_settings_backend_keyfile_write (kfsb, &error); |
444 | 0 | if (error) |
445 | 0 | { |
446 | 0 | g_warning ("Failed to write keyfile to %s: %s", g_file_peek_path (kfsb->file), error->message); |
447 | 0 | g_error_free (error); |
448 | 0 | } |
449 | 0 | } |
450 | |
|
451 | 0 | g_settings_backend_changed (backend, key, origin_tag); |
452 | 0 | } |
453 | | |
454 | | static gboolean |
455 | | g_keyfile_settings_backend_get_writable (GSettingsBackend *backend, |
456 | | const gchar *name) |
457 | 0 | { |
458 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend); |
459 | |
|
460 | 0 | return kfsb->writable && |
461 | 0 | !g_hash_table_contains (kfsb->system_locks, name) && |
462 | 0 | path_is_valid (kfsb, name); |
463 | 0 | } |
464 | | |
465 | | static GPermission * |
466 | | g_keyfile_settings_backend_get_permission (GSettingsBackend *backend, |
467 | | const gchar *path) |
468 | 0 | { |
469 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend); |
470 | |
|
471 | 0 | return g_object_ref (kfsb->permission); |
472 | 0 | } |
473 | | |
474 | | static void |
475 | | keyfile_to_tree (GKeyfileSettingsBackend *kfsb, |
476 | | GTree *tree, |
477 | | GKeyFile *keyfile, |
478 | | gboolean dup_check) |
479 | 0 | { |
480 | 0 | gchar **groups; |
481 | 0 | gint i; |
482 | |
|
483 | 0 | groups = g_key_file_get_groups (keyfile, NULL); |
484 | 0 | for (i = 0; groups[i]; i++) |
485 | 0 | { |
486 | 0 | gboolean is_root_group; |
487 | 0 | gchar **keys; |
488 | 0 | gint j; |
489 | |
|
490 | 0 | is_root_group = g_strcmp0 (kfsb->root_group, groups[i]) == 0; |
491 | | |
492 | | /* reject group names that will form invalid key names */ |
493 | 0 | if (!is_root_group && |
494 | 0 | (g_str_has_prefix (groups[i], "/") || |
495 | 0 | g_str_has_suffix (groups[i], "/") || strstr (groups[i], "//"))) |
496 | 0 | continue; |
497 | | |
498 | 0 | keys = g_key_file_get_keys (keyfile, groups[i], NULL, NULL); |
499 | 0 | g_assert (keys != NULL); |
500 | | |
501 | 0 | for (j = 0; keys[j]; j++) |
502 | 0 | { |
503 | 0 | gchar *path, *value; |
504 | | |
505 | | /* reject key names with slashes in them */ |
506 | 0 | if (strchr (keys[j], '/')) |
507 | 0 | continue; |
508 | | |
509 | 0 | if (is_root_group) |
510 | 0 | path = g_strdup_printf ("%s%s", kfsb->prefix, keys[j]); |
511 | 0 | else |
512 | 0 | path = g_strdup_printf ("%s%s/%s", kfsb->prefix, groups[i], keys[j]); |
513 | |
|
514 | 0 | value = g_key_file_get_value (keyfile, groups[i], keys[j], NULL); |
515 | |
|
516 | 0 | if (dup_check && g_strcmp0 (g_tree_lookup (tree, path), value) == 0) |
517 | 0 | { |
518 | 0 | g_tree_remove (tree, path); |
519 | 0 | g_free (value); |
520 | 0 | g_free (path); |
521 | 0 | } |
522 | 0 | else |
523 | 0 | g_tree_insert (tree, path, value); |
524 | 0 | } |
525 | |
|
526 | 0 | g_strfreev (keys); |
527 | 0 | } |
528 | 0 | g_strfreev (groups); |
529 | 0 | } |
530 | | |
531 | | static void |
532 | | g_keyfile_settings_backend_keyfile_reload (GKeyfileSettingsBackend *kfsb) |
533 | 0 | { |
534 | 0 | guint8 digest[32]; |
535 | 0 | gchar *contents; |
536 | 0 | gsize length; |
537 | |
|
538 | 0 | contents = NULL; |
539 | 0 | length = 0; |
540 | |
|
541 | 0 | g_file_load_contents (kfsb->file, NULL, &contents, &length, NULL, NULL); |
542 | 0 | compute_checksum (digest, contents, length); |
543 | |
|
544 | 0 | if (memcmp (kfsb->digest, digest, sizeof digest) != 0) |
545 | 0 | { |
546 | 0 | GKeyFile *keyfiles[2]; |
547 | 0 | GTree *tree; |
548 | |
|
549 | 0 | tree = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, |
550 | 0 | g_free, g_free); |
551 | |
|
552 | 0 | keyfiles[0] = kfsb->keyfile; |
553 | 0 | keyfiles[1] = g_key_file_new (); |
554 | |
|
555 | 0 | if (length > 0) |
556 | 0 | g_key_file_load_from_data (keyfiles[1], contents, length, |
557 | 0 | G_KEY_FILE_KEEP_COMMENTS | |
558 | 0 | G_KEY_FILE_KEEP_TRANSLATIONS, NULL); |
559 | |
|
560 | 0 | keyfile_to_tree (kfsb, tree, keyfiles[0], FALSE); |
561 | 0 | keyfile_to_tree (kfsb, tree, keyfiles[1], TRUE); |
562 | 0 | g_key_file_free (keyfiles[0]); |
563 | 0 | kfsb->keyfile = keyfiles[1]; |
564 | |
|
565 | 0 | if (g_tree_nnodes (tree) > 0) |
566 | 0 | g_settings_backend_changed_tree (&kfsb->parent_instance, tree, NULL); |
567 | |
|
568 | 0 | g_tree_unref (tree); |
569 | |
|
570 | 0 | memcpy (kfsb->digest, digest, sizeof digest); |
571 | 0 | } |
572 | |
|
573 | 0 | g_free (contents); |
574 | 0 | } |
575 | | |
576 | | static void |
577 | | g_keyfile_settings_backend_keyfile_writable (GKeyfileSettingsBackend *kfsb) |
578 | 0 | { |
579 | 0 | GFileInfo *fileinfo; |
580 | 0 | gboolean writable; |
581 | |
|
582 | 0 | fileinfo = g_file_query_info (kfsb->dir, "access::*", 0, NULL, NULL); |
583 | |
|
584 | 0 | if (fileinfo) |
585 | 0 | { |
586 | 0 | writable = |
587 | 0 | g_file_info_get_attribute_boolean (fileinfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) && |
588 | 0 | g_file_info_get_attribute_boolean (fileinfo, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE); |
589 | 0 | g_object_unref (fileinfo); |
590 | 0 | } |
591 | 0 | else |
592 | 0 | writable = FALSE; |
593 | |
|
594 | 0 | if (writable != kfsb->writable) |
595 | 0 | { |
596 | 0 | kfsb->writable = writable; |
597 | 0 | g_settings_backend_path_writable_changed (&kfsb->parent_instance, "/"); |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | | static void |
602 | | g_keyfile_settings_backend_finalize (GObject *object) |
603 | 0 | { |
604 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); |
605 | |
|
606 | 0 | g_key_file_free (kfsb->keyfile); |
607 | 0 | g_object_unref (kfsb->permission); |
608 | 0 | g_key_file_unref (kfsb->system_keyfile); |
609 | 0 | g_hash_table_unref (kfsb->system_locks); |
610 | 0 | g_free (kfsb->defaults_dir); |
611 | |
|
612 | 0 | if (kfsb->file_monitor) |
613 | 0 | { |
614 | 0 | g_file_monitor_cancel (kfsb->file_monitor); |
615 | 0 | g_object_unref (kfsb->file_monitor); |
616 | 0 | } |
617 | 0 | g_object_unref (kfsb->file); |
618 | |
|
619 | 0 | if (kfsb->dir_monitor) |
620 | 0 | { |
621 | 0 | g_file_monitor_cancel (kfsb->dir_monitor); |
622 | 0 | g_object_unref (kfsb->dir_monitor); |
623 | 0 | } |
624 | 0 | g_object_unref (kfsb->dir); |
625 | |
|
626 | 0 | g_free (kfsb->root_group); |
627 | 0 | g_free (kfsb->prefix); |
628 | |
|
629 | 0 | G_OBJECT_CLASS (g_keyfile_settings_backend_parent_class)->finalize (object); |
630 | 0 | } |
631 | | |
632 | | static void |
633 | | g_keyfile_settings_backend_init (GKeyfileSettingsBackend *kfsb) |
634 | 0 | { |
635 | 0 | } |
636 | | |
637 | | static void |
638 | | file_changed (GFileMonitor *monitor, |
639 | | GFile *file, |
640 | | GFile *other_file, |
641 | | GFileMonitorEvent event_type, |
642 | | gpointer user_data) |
643 | 0 | { |
644 | 0 | GKeyfileSettingsBackend *kfsb = user_data; |
645 | | |
646 | | /* Ignore file deletions, let the GKeyFile content remain in tact. */ |
647 | 0 | if (event_type != G_FILE_MONITOR_EVENT_DELETED) |
648 | 0 | g_keyfile_settings_backend_keyfile_reload (kfsb); |
649 | 0 | } |
650 | | |
651 | | static void |
652 | | dir_changed (GFileMonitor *monitor, |
653 | | GFile *file, |
654 | | GFile *other_file, |
655 | | GFileMonitorEvent event_type, |
656 | | gpointer user_data) |
657 | 0 | { |
658 | 0 | GKeyfileSettingsBackend *kfsb = user_data; |
659 | |
|
660 | 0 | g_keyfile_settings_backend_keyfile_writable (kfsb); |
661 | 0 | } |
662 | | |
663 | | static void |
664 | | load_system_settings (GKeyfileSettingsBackend *kfsb) |
665 | 0 | { |
666 | 0 | GError *error = NULL; |
667 | 0 | const char *dir = "/etc/glib-2.0/settings"; |
668 | 0 | char *path; |
669 | 0 | char *contents; |
670 | |
|
671 | 0 | kfsb->system_keyfile = g_key_file_new (); |
672 | 0 | kfsb->system_locks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); |
673 | |
|
674 | 0 | if (kfsb->defaults_dir) |
675 | 0 | dir = kfsb->defaults_dir; |
676 | |
|
677 | 0 | path = g_build_filename (dir, "defaults", NULL); |
678 | | |
679 | | /* The defaults are in the same keyfile format that we use for the settings. |
680 | | * It can be produced from a dconf database using: dconf dump |
681 | | */ |
682 | 0 | if (!g_key_file_load_from_file (kfsb->system_keyfile, path, G_KEY_FILE_NONE, &error)) |
683 | 0 | { |
684 | 0 | if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) |
685 | 0 | g_warning ("Failed to read %s: %s", path, error->message); |
686 | 0 | g_clear_error (&error); |
687 | 0 | } |
688 | 0 | else |
689 | 0 | g_debug ("Loading default settings from %s", path); |
690 | |
|
691 | 0 | g_free (path); |
692 | |
|
693 | 0 | path = g_build_filename (dir, "locks", NULL); |
694 | | |
695 | | /* The locks file is a text file containing a list paths to lock, one per line. |
696 | | * It can be produced from a dconf database using: dconf list-locks |
697 | | */ |
698 | 0 | if (!g_file_get_contents (path, &contents, NULL, &error)) |
699 | 0 | { |
700 | 0 | if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) |
701 | 0 | g_warning ("Failed to read %s: %s", path, error->message); |
702 | 0 | g_clear_error (&error); |
703 | 0 | } |
704 | 0 | else |
705 | 0 | { |
706 | 0 | char **lines; |
707 | 0 | gsize i; |
708 | |
|
709 | 0 | g_debug ("Loading locks from %s", path); |
710 | |
|
711 | 0 | lines = g_strsplit (contents, "\n", 0); |
712 | 0 | for (i = 0; lines[i]; i++) |
713 | 0 | { |
714 | 0 | char *line = lines[i]; |
715 | 0 | if (line[0] == '#' || line[0] == '\0') |
716 | 0 | { |
717 | 0 | g_free (line); |
718 | 0 | continue; |
719 | 0 | } |
720 | | |
721 | 0 | g_debug ("Locking key %s", line); |
722 | 0 | g_hash_table_add (kfsb->system_locks, g_steal_pointer (&line)); |
723 | 0 | } |
724 | |
|
725 | 0 | g_free (lines); |
726 | 0 | } |
727 | 0 | g_free (contents); |
728 | |
|
729 | 0 | g_free (path); |
730 | 0 | } |
731 | | |
732 | | static void |
733 | | g_keyfile_settings_backend_constructed (GObject *object) |
734 | 0 | { |
735 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); |
736 | 0 | GError *error = NULL; |
737 | 0 | const char *path; |
738 | |
|
739 | 0 | if (kfsb->file == NULL) |
740 | 0 | { |
741 | 0 | char *filename = g_build_filename (g_get_user_config_dir (), |
742 | 0 | "glib-2.0", "settings", "keyfile", |
743 | 0 | NULL); |
744 | 0 | kfsb->file = g_file_new_for_path (filename); |
745 | 0 | g_free (filename); |
746 | 0 | } |
747 | |
|
748 | 0 | if (kfsb->prefix == NULL) |
749 | 0 | { |
750 | 0 | kfsb->prefix = g_strdup ("/"); |
751 | 0 | kfsb->prefix_len = 1; |
752 | 0 | } |
753 | | |
754 | 0 | kfsb->keyfile = g_key_file_new (); |
755 | 0 | kfsb->permission = g_simple_permission_new (TRUE); |
756 | |
|
757 | 0 | kfsb->dir = g_file_get_parent (kfsb->file); |
758 | 0 | path = g_file_peek_path (kfsb->dir); |
759 | 0 | if (g_mkdir_with_parents (path, 0700) == -1) |
760 | 0 | g_warning ("Failed to create %s: %s", path, g_strerror (errno)); |
761 | |
|
762 | 0 | kfsb->file_monitor = g_file_monitor (kfsb->file, G_FILE_MONITOR_NONE, NULL, &error); |
763 | 0 | if (!kfsb->file_monitor) |
764 | 0 | { |
765 | 0 | g_warning ("Failed to create file monitor for %s: %s", g_file_peek_path (kfsb->file), error->message); |
766 | 0 | g_clear_error (&error); |
767 | 0 | } |
768 | 0 | else |
769 | 0 | { |
770 | 0 | g_signal_connect (kfsb->file_monitor, "changed", |
771 | 0 | G_CALLBACK (file_changed), kfsb); |
772 | 0 | } |
773 | |
|
774 | 0 | kfsb->dir_monitor = g_file_monitor (kfsb->dir, G_FILE_MONITOR_NONE, NULL, &error); |
775 | 0 | if (!kfsb->dir_monitor) |
776 | 0 | { |
777 | 0 | g_warning ("Failed to create file monitor for %s: %s", g_file_peek_path (kfsb->file), error->message); |
778 | 0 | g_clear_error (&error); |
779 | 0 | } |
780 | 0 | else |
781 | 0 | { |
782 | 0 | g_signal_connect (kfsb->dir_monitor, "changed", |
783 | 0 | G_CALLBACK (dir_changed), kfsb); |
784 | 0 | } |
785 | |
|
786 | 0 | compute_checksum (kfsb->digest, NULL, 0); |
787 | |
|
788 | 0 | g_keyfile_settings_backend_keyfile_writable (kfsb); |
789 | 0 | g_keyfile_settings_backend_keyfile_reload (kfsb); |
790 | |
|
791 | 0 | load_system_settings (kfsb); |
792 | 0 | } |
793 | | |
794 | | static void |
795 | | g_keyfile_settings_backend_set_property (GObject *object, |
796 | | guint prop_id, |
797 | | const GValue *value, |
798 | | GParamSpec *pspec) |
799 | 0 | { |
800 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); |
801 | |
|
802 | 0 | switch ((GKeyfileSettingsBackendProperty)prop_id) |
803 | 0 | { |
804 | 0 | case PROP_FILENAME: |
805 | | /* Construct only. */ |
806 | 0 | g_assert (kfsb->file == NULL); |
807 | 0 | if (g_value_get_string (value)) |
808 | 0 | kfsb->file = g_file_new_for_path (g_value_get_string (value)); |
809 | 0 | break; |
810 | | |
811 | 0 | case PROP_ROOT_PATH: |
812 | | /* Construct only. */ |
813 | 0 | g_assert (kfsb->prefix == NULL); |
814 | 0 | kfsb->prefix = g_value_dup_string (value); |
815 | 0 | if (kfsb->prefix) |
816 | 0 | kfsb->prefix_len = strlen (kfsb->prefix); |
817 | 0 | break; |
818 | | |
819 | 0 | case PROP_ROOT_GROUP: |
820 | | /* Construct only. */ |
821 | 0 | g_assert (kfsb->root_group == NULL); |
822 | 0 | kfsb->root_group = g_value_dup_string (value); |
823 | 0 | if (kfsb->root_group) |
824 | 0 | kfsb->root_group_len = strlen (kfsb->root_group); |
825 | 0 | break; |
826 | | |
827 | 0 | case PROP_DEFAULTS_DIR: |
828 | | /* Construct only. */ |
829 | 0 | g_assert (kfsb->defaults_dir == NULL); |
830 | 0 | kfsb->defaults_dir = g_value_dup_string (value); |
831 | 0 | break; |
832 | | |
833 | 0 | default: |
834 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
835 | 0 | break; |
836 | 0 | } |
837 | 0 | } |
838 | | |
839 | | static void |
840 | | g_keyfile_settings_backend_get_property (GObject *object, |
841 | | guint prop_id, |
842 | | GValue *value, |
843 | | GParamSpec *pspec) |
844 | 0 | { |
845 | 0 | GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object); |
846 | |
|
847 | 0 | switch ((GKeyfileSettingsBackendProperty)prop_id) |
848 | 0 | { |
849 | 0 | case PROP_FILENAME: |
850 | 0 | g_value_set_string (value, g_file_peek_path (kfsb->file)); |
851 | 0 | break; |
852 | | |
853 | 0 | case PROP_ROOT_PATH: |
854 | 0 | g_value_set_string (value, kfsb->prefix); |
855 | 0 | break; |
856 | | |
857 | 0 | case PROP_ROOT_GROUP: |
858 | 0 | g_value_set_string (value, kfsb->root_group); |
859 | 0 | break; |
860 | | |
861 | 0 | case PROP_DEFAULTS_DIR: |
862 | 0 | g_value_set_string (value, kfsb->defaults_dir); |
863 | 0 | break; |
864 | | |
865 | 0 | default: |
866 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
867 | 0 | break; |
868 | 0 | } |
869 | 0 | } |
870 | | |
871 | | static void |
872 | | g_keyfile_settings_backend_class_init (GKeyfileSettingsBackendClass *class) |
873 | 0 | { |
874 | 0 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
875 | |
|
876 | 0 | object_class->finalize = g_keyfile_settings_backend_finalize; |
877 | 0 | object_class->constructed = g_keyfile_settings_backend_constructed; |
878 | 0 | object_class->get_property = g_keyfile_settings_backend_get_property; |
879 | 0 | object_class->set_property = g_keyfile_settings_backend_set_property; |
880 | |
|
881 | 0 | class->read = g_keyfile_settings_backend_read; |
882 | 0 | class->write = g_keyfile_settings_backend_write; |
883 | 0 | class->write_tree = g_keyfile_settings_backend_write_tree; |
884 | 0 | class->reset = g_keyfile_settings_backend_reset; |
885 | 0 | class->get_writable = g_keyfile_settings_backend_get_writable; |
886 | 0 | class->get_permission = g_keyfile_settings_backend_get_permission; |
887 | | /* No need to implement subscribed/unsubscribe: the only point would be to |
888 | | * stop monitoring the file when there's no GSettings anymore, which is no |
889 | | * big win. |
890 | | */ |
891 | | |
892 | | /** |
893 | | * GKeyfileSettingsBackend:filename: |
894 | | * |
895 | | * The location where the settings are stored on disk. |
896 | | * |
897 | | * Defaults to `$XDG_CONFIG_HOME/glib-2.0/settings/keyfile`. |
898 | | */ |
899 | 0 | g_object_class_install_property (object_class, |
900 | 0 | PROP_FILENAME, |
901 | 0 | g_param_spec_string ("filename", |
902 | 0 | P_("Filename"), |
903 | 0 | P_("The filename"), |
904 | 0 | NULL, |
905 | 0 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
906 | 0 | G_PARAM_STATIC_STRINGS)); |
907 | | |
908 | | /** |
909 | | * GKeyfileSettingsBackend:root-path: |
910 | | * |
911 | | * All settings read to or written from the backend must fall under the |
912 | | * path given in @root_path (which must start and end with a slash and |
913 | | * not contain two consecutive slashes). @root_path may be "/". |
914 | | * |
915 | | * Defaults to "/". |
916 | | */ |
917 | 0 | g_object_class_install_property (object_class, |
918 | 0 | PROP_ROOT_PATH, |
919 | 0 | g_param_spec_string ("root-path", |
920 | 0 | P_("Root path"), |
921 | 0 | P_("The root path"), |
922 | 0 | NULL, |
923 | 0 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
924 | 0 | G_PARAM_STATIC_STRINGS)); |
925 | | |
926 | | /** |
927 | | * GKeyfileSettingsBackend:root-group: |
928 | | * |
929 | | * If @root_group is non-%NULL then it specifies the name of the keyfile |
930 | | * group used for keys that are written directly below the root path. |
931 | | * |
932 | | * Defaults to NULL. |
933 | | */ |
934 | 0 | g_object_class_install_property (object_class, |
935 | 0 | PROP_ROOT_GROUP, |
936 | 0 | g_param_spec_string ("root-group", |
937 | 0 | P_("Root group"), |
938 | 0 | P_("The root group"), |
939 | 0 | NULL, |
940 | 0 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
941 | 0 | G_PARAM_STATIC_STRINGS)); |
942 | | |
943 | | /** |
944 | | * GKeyfileSettingsBackend:default-dir: |
945 | | * |
946 | | * The directory where the system defaults and locks are located. |
947 | | * |
948 | | * Defaults to `/etc/glib-2.0/settings`. |
949 | | */ |
950 | 0 | g_object_class_install_property (object_class, |
951 | 0 | PROP_DEFAULTS_DIR, |
952 | 0 | g_param_spec_string ("defaults-dir", |
953 | 0 | P_("Default dir"), |
954 | 0 | P_("Defaults dir"), |
955 | 0 | NULL, |
956 | 0 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
957 | 0 | G_PARAM_STATIC_STRINGS)); |
958 | 0 | } |
959 | | |
960 | | /** |
961 | | * g_keyfile_settings_backend_new: |
962 | | * @filename: the filename of the keyfile |
963 | | * @root_path: the path under which all settings keys appear |
964 | | * @root_group: (nullable): the group name corresponding to |
965 | | * @root_path, or %NULL |
966 | | * |
967 | | * Creates a keyfile-backed #GSettingsBackend. |
968 | | * |
969 | | * The filename of the keyfile to use is given by @filename. |
970 | | * |
971 | | * All settings read to or written from the backend must fall under the |
972 | | * path given in @root_path (which must start and end with a slash and |
973 | | * not contain two consecutive slashes). @root_path may be "/". |
974 | | * |
975 | | * If @root_group is non-%NULL then it specifies the name of the keyfile |
976 | | * group used for keys that are written directly below @root_path. For |
977 | | * example, if @root_path is "/apps/example/" and @root_group is |
978 | | * "toplevel", then settings the key "/apps/example/enabled" to a value |
979 | | * of %TRUE will cause the following to appear in the keyfile: |
980 | | * |
981 | | * |[ |
982 | | * [toplevel] |
983 | | * enabled=true |
984 | | * ]| |
985 | | * |
986 | | * If @root_group is %NULL then it is not permitted to store keys |
987 | | * directly below the @root_path. |
988 | | * |
989 | | * For keys not stored directly below @root_path (ie: in a sub-path), |
990 | | * the name of the subpath (with the final slash stripped) is used as |
991 | | * the name of the keyfile group. To continue the example, if |
992 | | * "/apps/example/profiles/default/font-size" were set to |
993 | | * 12 then the following would appear in the keyfile: |
994 | | * |
995 | | * |[ |
996 | | * [profiles/default] |
997 | | * font-size=12 |
998 | | * ]| |
999 | | * |
1000 | | * The backend will refuse writes (and return writability as being |
1001 | | * %FALSE) for keys outside of @root_path and, in the event that |
1002 | | * @root_group is %NULL, also for keys directly under @root_path. |
1003 | | * Writes will also be refused if the backend detects that it has the |
1004 | | * inability to rewrite the keyfile (ie: the containing directory is not |
1005 | | * writable). |
1006 | | * |
1007 | | * There is no checking done for your key namespace clashing with the |
1008 | | * syntax of the key file format. For example, if you have '[' or ']' |
1009 | | * characters in your path names or '=' in your key names you may be in |
1010 | | * trouble. |
1011 | | * |
1012 | | * The backend reads default values from a keyfile called `defaults` in |
1013 | | * the directory specified by the #GKeyfileSettingsBackend:defaults-dir property, |
1014 | | * and a list of locked keys from a text file with the name `locks` in |
1015 | | * the same location. |
1016 | | * |
1017 | | * Returns: (transfer full): a keyfile-backed #GSettingsBackend |
1018 | | **/ |
1019 | | GSettingsBackend * |
1020 | | g_keyfile_settings_backend_new (const gchar *filename, |
1021 | | const gchar *root_path, |
1022 | | const gchar *root_group) |
1023 | 0 | { |
1024 | 0 | g_return_val_if_fail (filename != NULL, NULL); |
1025 | 0 | g_return_val_if_fail (root_path != NULL, NULL); |
1026 | 0 | g_return_val_if_fail (g_str_has_prefix (root_path, "/"), NULL); |
1027 | 0 | g_return_val_if_fail (g_str_has_suffix (root_path, "/"), NULL); |
1028 | 0 | g_return_val_if_fail (strstr (root_path, "//") == NULL, NULL); |
1029 | | |
1030 | 0 | return G_SETTINGS_BACKEND (g_object_new (G_TYPE_KEYFILE_SETTINGS_BACKEND, |
1031 | 0 | "filename", filename, |
1032 | 0 | "root-path", root_path, |
1033 | 0 | "root-group", root_group, |
1034 | 0 | NULL)); |
1035 | 0 | } |