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>
23 #include "dconf-writer.h"
30 typedef DConfWriterClass DConfKeyfileWriterClass
;
34 DConfWriter parent_instance
;
38 GFileMonitor
*monitor
;
39 guint scheduled_update
;
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
;
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
];
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
);
76 key_prefix
= g_strconcat ("/", group
, "/", NULL
);
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
];
92 if (strchr (key
, '/'))
94 g_warning ("%s: [%s]: ignoring invalid key name: %s\n", filename_fyi
, group
, key
);
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
);
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
);
112 path
= g_strconcat (key_prefix
, key
, NULL
);
113 dconf_changeset_set (changeset
, path
, value
);
114 g_variant_unref (value
);
128 dconf_keyfile_writer_list (GHashTable
*set
)
134 dirname
= g_build_filename (g_get_user_config_dir (), "dconf", NULL
);
135 dir
= g_dir_open (dirname
, 0, NULL
);
140 while ((name
= g_dir_read_name (dir
)))
144 dottxt
= strstr (name
, ".txt");
146 if (dottxt
&& dottxt
[4] == '\0')
147 g_hash_table_add (set
, g_strndup (name
, dottxt
- name
));
153 static gboolean
dconf_keyfile_update (gpointer user_data
);
156 dconf_keyfile_changed (GFileMonitor
*monitor
,
159 GFileMonitorEvent event_type
,
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
);
173 dconf_keyfile_writer_begin (DConfWriter
*writer
,
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
;
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)
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);
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
));
228 lock
.l_type
= F_WRLCK
;
231 lock
.l_len
= 0; /* lock all bytes */
233 if (fcntl (kfw
->lock_fd
, F_SETLKW
, &lock
) == 0)
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
);
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
);
258 g_clear_error (&local_error
);
261 kfw
->keyfile
= g_key_file_new ();
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
);
274 if (!DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class
)->begin (writer
, error
))
276 g_clear_pointer (&kfw
->keyfile
, g_key_file_free
);
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
287 contents
= dconf_keyfile_to_changeset (kfw
->keyfile
, kfw
->filename
);
288 changes
= dconf_writer_diff (writer
, contents
);
292 DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class
)->change (writer
, changes
, "");
293 dconf_changeset_unref (changes
);
296 dconf_changeset_unref (contents
);
302 dconf_keyfile_writer_change (DConfWriter
*writer
,
303 DConfChangeset
*changeset
,
306 DConfKeyfileWriter
*kfw
= (DConfKeyfileWriter
*) writer
;
308 const gchar
* const *paths
;
309 GVariant
* const *values
;
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
;
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
);
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
);
367 /* A simple set or reset of a single key. */
368 const gchar
*last_slash
;
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
378 if (last_slash
!= path
)
379 group
= g_strndup (path
+ 1, last_slash
- (path
+ 1));
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
387 key
= g_strdup (last_slash
+ 1);
393 printed
= g_variant_print (value
, TRUE
);
394 g_key_file_set_value (kfw
->keyfile
, group
, key
, printed
);
398 g_key_file_remove_key (kfw
->keyfile
, group
, key
, NULL
);
409 dconf_keyfile_writer_commit (DConfWriter
*writer
,
412 DConfKeyfileWriter
*kfw
= (DConfKeyfileWriter
*) writer
;
414 /* Pretty simple. Write the keyfile. */
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
))
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);
436 g_clear_error (error
);
437 if (!g_file_set_contents (kfw
->filename
, data
, size
, error
))
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
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
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
);
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
);
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
;
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
);
510 dconf_keyfile_writer_init (DConfKeyfileWriter
*kfw
)
512 dconf_writer_set_basepath (DCONF_WRITER (kfw
), "keyfile");
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
;