Replace all hard-coded /etc path with sysconfdir
[dconf.git] / service / dconf-keyfile-writer.c
blobf4951bbc072d9b2c693b433b03854a978ef2d2c9
1 /*
2 * Copyright © 2010 Codethink Limited
3 * Copyright © 2012 Canonical Limited
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 of the licence, or (at your option) any later version.
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.
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/>.
18 * Author: Ryan Lortie <desrt@desrt.ca>
21 #include "config.h"
23 #include "dconf-writer.h"
25 #include <string.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #include <fcntl.h>
30 typedef DConfWriterClass DConfKeyfileWriterClass;
32 typedef struct
34 DConfWriter parent_instance;
35 gchar *filename;
36 gchar *lock_filename;
37 gint lock_fd;
38 GFileMonitor *monitor;
39 guint scheduled_update;
40 gchar *contents;
41 GKeyFile *keyfile;
42 } DConfKeyfileWriter;
44 G_DEFINE_TYPE (DConfKeyfileWriter, dconf_keyfile_writer, DCONF_TYPE_WRITER)
46 static DConfChangeset *
47 dconf_keyfile_to_changeset (GKeyFile *keyfile,
48 const gchar *filename_fyi)
50 DConfChangeset *changeset;
51 gchar **groups;
52 gint i;
54 changeset = dconf_changeset_new_database (NULL);
56 groups = g_key_file_get_groups (keyfile, NULL);
57 for (i = 0; groups[i]; i++)
59 const gchar *group = groups[i];
60 gchar *key_prefix;
61 gchar **keys;
62 gint j;
64 /* Special case the [/] group to be able to contain keys at the
65 * root (/a, /b, etc.). All others must not start or end with a
66 * slash (ie: group [x/y] contains keys such as /x/y/z).
68 if (!g_str_equal (group, "/"))
70 if (g_str_has_prefix (group, "/") || g_str_has_suffix (group, "/") || strstr (group, "//"))
72 g_warning ("%s: ignoring invalid group name: %s\n", filename_fyi, group);
73 continue;
76 key_prefix = g_strconcat ("/", group, "/", NULL);
78 else
79 key_prefix = g_strdup ("/");
81 keys = g_key_file_get_keys (keyfile, group, NULL, NULL);
82 g_assert (keys != NULL);
84 for (j = 0; keys[j]; j++)
86 const gchar *key = keys[j];
87 GError *error = NULL;
88 gchar *value_str;
89 GVariant *value;
90 gchar *path;
92 if (strchr (key, '/'))
94 g_warning ("%s: [%s]: ignoring invalid key name: %s\n", filename_fyi, group, key);
95 continue;
98 value_str = g_key_file_get_value (keyfile, group, key, NULL);
99 g_assert (value_str != NULL);
101 value = g_variant_parse (NULL, value_str, NULL, NULL, &error);
102 g_free (value_str);
104 if (value == NULL)
106 g_warning ("%s: [%s]: %s: skipping invalid value: %s (%s)\n",
107 filename_fyi, group, key, value_str, error->message);
108 g_error_free (error);
109 continue;
112 path = g_strconcat (key_prefix, key, NULL);
113 dconf_changeset_set (changeset, path, value);
114 g_variant_unref (value);
115 g_free (path);
118 g_free (key_prefix);
119 g_strfreev (keys);
122 g_strfreev (groups);
124 return changeset;
127 static void
128 dconf_keyfile_writer_list (GHashTable *set)
130 const gchar *name;
131 gchar *dirname;
132 GDir *dir;
134 dirname = g_build_filename (g_get_user_config_dir (), "dconf", NULL);
135 dir = g_dir_open (dirname, 0, NULL);
137 if (!dir)
138 return;
140 while ((name = g_dir_read_name (dir)))
142 const gchar *dottxt;
144 dottxt = strstr (name, ".txt");
146 if (dottxt && dottxt[4] == '\0')
147 g_hash_table_add (set, g_strndup (name, dottxt - name));
150 g_dir_close (dir);
153 static gboolean dconf_keyfile_update (gpointer user_data);
155 static void
156 dconf_keyfile_changed (GFileMonitor *monitor,
157 GFile *file,
158 GFile *other_file,
159 GFileMonitorEvent event_type,
160 gpointer user_data)
162 DConfKeyfileWriter *kfw = user_data;
164 if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT ||
165 event_type == G_FILE_MONITOR_EVENT_CREATED)
167 if (!kfw->scheduled_update)
168 kfw->scheduled_update = g_idle_add (dconf_keyfile_update, kfw);
172 static gboolean
173 dconf_keyfile_writer_begin (DConfWriter *writer,
174 GError **error)
176 DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer;
177 GError *local_error = NULL;
178 DConfChangeset *contents;
179 DConfChangeset *changes;
181 if (kfw->filename == NULL)
183 gchar *filename_base;
184 GFile *file;
186 filename_base = g_build_filename (g_get_user_config_dir (), "dconf", dconf_writer_get_name (writer), NULL);
187 kfw->filename = g_strconcat (filename_base, ".txt", NULL);
188 kfw->lock_filename = g_strconcat (kfw->filename, "-lock", NULL);
189 g_free (filename_base);
191 /* See https://bugzilla.gnome.org/show_bug.cgi?id=691618 */
192 file = g_vfs_get_file_for_path (g_vfs_get_local (), kfw->filename);
193 kfw->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
194 g_object_unref (file);
196 g_signal_connect (kfw->monitor, "changed", G_CALLBACK (dconf_keyfile_changed), kfw);
199 g_clear_pointer (&kfw->contents, g_free);
201 kfw->lock_fd = open (kfw->lock_filename, O_RDWR | O_CREAT, 0666);
202 if (kfw->lock_fd == -1)
204 gchar *dirname;
206 /* Maybe it failed because the directory doesn't exist. Try
207 * again, after mkdir().
209 dirname = g_path_get_dirname (kfw->lock_filename);
210 g_mkdir_with_parents (dirname, 0700);
211 g_free (dirname);
213 kfw->lock_fd = open (kfw->lock_filename, O_RDWR | O_CREAT, 0666);
214 if (kfw->lock_fd == -1)
216 gint saved_errno = errno;
218 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno),
219 "%s: %s", kfw->lock_filename, g_strerror (saved_errno));
220 return FALSE;
224 while (TRUE)
226 struct flock lock;
228 lock.l_type = F_WRLCK;
229 lock.l_whence = 0;
230 lock.l_start = 0;
231 lock.l_len = 0; /* lock all bytes */
233 if (fcntl (kfw->lock_fd, F_SETLKW, &lock) == 0)
234 break;
236 if (errno != EINTR)
238 gint saved_errno = errno;
240 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno),
241 "%s: unable to fcntl(F_SETLKW): %s", kfw->lock_filename, g_strerror (saved_errno));
242 close (kfw->lock_fd);
243 kfw->lock_fd = -1;
244 return FALSE;
247 /* it was EINTR. loop again. */
250 if (!g_file_get_contents (kfw->filename, &kfw->contents, NULL, &local_error))
252 if (!g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
254 g_propagate_error (error, local_error);
255 return FALSE;
258 g_clear_error (&local_error);
261 kfw->keyfile = g_key_file_new ();
263 if (kfw->contents)
265 if (!g_key_file_load_from_data (kfw->keyfile, kfw->contents, -1, G_KEY_FILE_KEEP_COMMENTS, &local_error))
267 g_clear_pointer (&kfw->keyfile, g_key_file_free);
268 g_clear_pointer (&kfw->contents, g_free);
269 g_propagate_error (error, local_error);
270 return FALSE;
274 if (!DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->begin (writer, error))
276 g_clear_pointer (&kfw->keyfile, g_key_file_free);
277 return FALSE;
280 /* Diff the keyfile to the current contents of the database and apply
281 * any changes that we notice.
283 * This will catch both the case of people outside of the service
284 * making changes to the file and also the case of starting for the
285 * first time.
287 contents = dconf_keyfile_to_changeset (kfw->keyfile, kfw->filename);
288 changes = dconf_writer_diff (writer, contents);
290 if (changes)
292 DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->change (writer, changes, "");
293 dconf_changeset_unref (changes);
296 dconf_changeset_unref (contents);
298 return TRUE;
301 static void
302 dconf_keyfile_writer_change (DConfWriter *writer,
303 DConfChangeset *changeset,
304 const gchar *tag)
306 DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer;
307 const gchar *prefix;
308 const gchar * const *paths;
309 GVariant * const *values;
310 guint n, i;
312 DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->change (writer, changeset, tag);
314 n = dconf_changeset_describe (changeset, &prefix, &paths, &values);
316 for (i = 0; i < n; i++)
318 gchar *path = g_strconcat (prefix, paths[i], NULL);
319 GVariant *value = values[i];
321 if (g_str_equal (path, "/"))
323 g_assert (value == NULL);
325 /* This is a request to reset everything.
327 * Easiest way to do this:
329 g_key_file_free (kfw->keyfile);
330 kfw->keyfile = g_key_file_new ();
332 else if (g_str_has_suffix (path, "/"))
334 gchar *group_to_remove;
335 gchar **groups;
336 gint i;
338 g_assert (value == NULL);
340 /* Time to do a path reset.
342 * We must reset the group for the path plus any "subgroups".
344 * We dealt with the case of "/" above, so we know we have
345 * something with at least a separate leading and trailing slash,
346 * with the group name in the middle.
348 group_to_remove = g_strndup (path + 1, strlen (path) - 2);
349 g_key_file_remove_group (kfw->keyfile, group_to_remove, NULL);
350 g_free (group_to_remove);
352 /* Now the rest...
354 * For this case we check if the group is prefixed by the path
355 * given to us, including the trailing slash (but not the leading
356 * one). That means a reset on "/a/" (group "[a]") will match
357 * group "[a/b]" but not will not match group "[another]".
359 groups = g_key_file_get_groups (kfw->keyfile, NULL);
360 for (i = 0; groups[i]; i++)
361 if (g_str_has_prefix (groups[i], path + 1)) /* remove only leading slash */
362 g_key_file_remove_group (kfw->keyfile, groups[i], NULL);
363 g_strfreev (groups);
365 else
367 /* A simple set or reset of a single key. */
368 const gchar *last_slash;
369 gchar *group;
370 gchar *key;
372 last_slash = strrchr (path, '/');
374 /* If the last slash is the first one then the group will be the
375 * special case: [/]. Otherwise we remove the leading and
376 * trailing slashes.
378 if (last_slash != path)
379 group = g_strndup (path + 1, last_slash - (path + 1));
380 else
381 group = g_strdup ("/");
383 /* Key is the non-empty part following the last slash (we know
384 * that it's non-empty because we dealt with strings ending with
385 * '/' above).
387 key = g_strdup (last_slash + 1);
389 if (value != NULL)
391 gchar *printed;
393 printed = g_variant_print (value, TRUE);
394 g_key_file_set_value (kfw->keyfile, group, key, printed);
395 g_free (printed);
397 else
398 g_key_file_remove_key (kfw->keyfile, group, key, NULL);
400 g_free (group);
401 g_free (key);
404 g_free (path);
408 static gboolean
409 dconf_keyfile_writer_commit (DConfWriter *writer,
410 GError **error)
412 DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer;
414 /* Pretty simple. Write the keyfile. */
416 gchar *data;
417 gsize size;
419 /* docs say: "Note that this function never reports an error" */
420 data = g_key_file_to_data (kfw->keyfile, &size, NULL);
422 /* don't write it again if nothing changed */
423 if (!kfw->contents || !g_str_equal (kfw->contents, data))
425 if (!g_file_set_contents (kfw->filename, data, size, error))
427 gchar *dirname;
429 /* Maybe it failed because the directory doesn't exist. Try
430 * again, after mkdir().
432 dirname = g_path_get_dirname (kfw->filename);
433 g_mkdir_with_parents (dirname, 0777);
434 g_free (dirname);
436 g_clear_error (error);
437 if (!g_file_set_contents (kfw->filename, data, size, error))
439 g_free (data);
440 return FALSE;
445 g_free (data);
448 /* Failing to update the shm file after writing the keyfile is
449 * unlikely to occur. It can only happen if the runtime dir hits
450 * quota.
452 * If it does happen, we're in a bit of a bad spot because the on-disk
453 * keyfile is now out-of-sync with the contents of the shm file. We
454 * fail the write because the apps will see the old values in the shm
455 * file.
457 * Meanwhile we keep the on-disk keyfile as-is. The next time we open
458 * it we will notice that it's not in sync with the shm file and we'll
459 * try to merge the two as if the changes were made by an outsider.
460 * Eventually that may succeed... If it doesn't, what can we do?
462 return DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->commit (writer, error);
465 static void
466 dconf_keyfile_writer_end (DConfWriter *writer)
468 DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer;
470 DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->end (writer);
472 g_clear_pointer (&kfw->keyfile, g_key_file_free);
473 g_clear_pointer (&kfw->contents, g_free);
474 close (kfw->lock_fd);
475 kfw->lock_fd = -1;
478 static gboolean
479 dconf_keyfile_update (gpointer user_data)
481 DConfKeyfileWriter *kfw = user_data;
483 if (dconf_keyfile_writer_begin (DCONF_WRITER (kfw), NULL))
485 dconf_keyfile_writer_commit (DCONF_WRITER (kfw), NULL);
486 dconf_keyfile_writer_end (DCONF_WRITER (kfw));
489 kfw->scheduled_update = 0;
491 return G_SOURCE_REMOVE;
494 static void
495 dconf_keyfile_writer_finalize (GObject *object)
497 DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) object;
499 if (kfw->scheduled_update)
500 g_source_remove (kfw->scheduled_update);
502 g_clear_object (&kfw->monitor);
503 g_free (kfw->lock_filename);
504 g_free (kfw->filename);
506 G_OBJECT_CLASS (dconf_keyfile_writer_parent_class)->finalize (object);
509 static void
510 dconf_keyfile_writer_init (DConfKeyfileWriter *kfw)
512 dconf_writer_set_basepath (DCONF_WRITER (kfw), "keyfile");
514 kfw->lock_fd = -1;
517 static void
518 dconf_keyfile_writer_class_init (DConfWriterClass *class)
520 GObjectClass *object_class = G_OBJECT_CLASS (class);
522 object_class->finalize = dconf_keyfile_writer_finalize;
524 class->list = dconf_keyfile_writer_list;
525 class->begin = dconf_keyfile_writer_begin;
526 class->change = dconf_keyfile_writer_change;
527 class->commit = dconf_keyfile_writer_commit;
528 class->end = dconf_keyfile_writer_end;