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