service: Allow opening corrupt GVDB files when writing
[dconf.git] / service / dconf-gvdb-utils.c
blobfdd90e24fd7027cbcb06c35a74509c8f5bcb6c4d
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-gvdb-utils.h"
25 #include "../common/dconf-paths.h"
26 #include "../gvdb/gvdb-builder.h"
27 #include "../gvdb/gvdb-reader.h"
29 #include <errno.h>
30 #include <glib.h>
31 #include <glib/gstdio.h>
32 #include <string.h>
34 DConfChangeset *
35 dconf_gvdb_utils_read_file (const gchar *filename,
36 gboolean *file_missing,
37 GError **error)
39 DConfChangeset *database;
40 GError *my_error = NULL;
41 GvdbTable *table = NULL;
42 gchar *contents;
43 gsize size;
45 if (g_file_get_contents (filename, &contents, &size, &my_error))
47 GBytes *bytes;
49 bytes = g_bytes_new_take (contents, size);
50 table = gvdb_table_new_from_bytes (bytes, FALSE, &my_error);
51 g_bytes_unref (bytes);
54 /* It is perfectly fine if the file does not exist -- then it's
55 * just empty.
57 if (g_error_matches (my_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
58 g_clear_error (&my_error);
60 /* Otherwise, we should report errors to prevent ourselves from
61 * overwriting the database in other situations...
63 if (g_error_matches (my_error, G_FILE_ERROR, G_FILE_ERROR_INVAL))
65 /* Move the database to a backup file, warn and continue with a new
66 * database. The alternative is erroring out and exiting the daemon,
67 * which leaves the user’s session essentially unusable.
69 * The code to find an unused backup filename is racy, but this is an
70 * error handling path. Who cares. */
71 g_autofree gchar *backup_filename = NULL;
72 guint i;
74 for (i = 0;
75 i < G_MAXUINT &&
76 (backup_filename == NULL || g_file_test (backup_filename, G_FILE_TEST_EXISTS));
77 i++)
79 g_free (backup_filename);
80 backup_filename = g_strdup_printf ("%s~%u", filename, i);
83 if (g_rename (filename, backup_filename) != 0)
84 g_warning ("Error renaming corrupt database from ‘%s’ to ‘%s’: %s",
85 filename, backup_filename, g_strerror (errno));
86 else
87 g_warning ("Database ‘%s’ was corrupt: moved it to ‘%s’ and created an empty replacement",
88 filename, backup_filename);
90 g_clear_error (&my_error);
92 else if (my_error)
94 g_propagate_prefixed_error (error, my_error, "Cannot open dconf database: ");
95 return NULL;
98 /* Only allocate once we know we are in a non-error situation */
99 database = dconf_changeset_new_database (NULL);
101 /* Fill the table up with the initial state */
102 if (table != NULL)
104 gchar **names;
105 gint n_names;
106 gint i;
108 names = gvdb_table_get_names (table, &n_names);
109 for (i = 0; i < n_names; i++)
111 if (dconf_is_key (names[i], NULL))
113 GVariant *value;
115 value = gvdb_table_get_value (table, names[i]);
117 if (value != NULL)
119 dconf_changeset_set (database, names[i], value);
120 g_variant_unref (value);
124 g_free (names[i]);
127 gvdb_table_free (table);
128 g_free (names);
131 if (file_missing)
132 *file_missing = (table == NULL);
134 return database;
137 static GvdbItem *
138 dconf_gvdb_utils_get_parent (GHashTable *table,
139 const gchar *key)
141 GvdbItem *grandparent, *parent;
142 gchar *parent_name;
143 gint len;
145 if (g_str_equal (key, "/"))
146 return NULL;
148 len = strlen (key);
149 if (key[len - 1] == '/')
150 len--;
152 while (key[len - 1] != '/')
153 len--;
155 parent_name = g_strndup (key, len);
156 parent = g_hash_table_lookup (table, parent_name);
158 if (parent == NULL)
160 parent = gvdb_hash_table_insert (table, parent_name);
162 grandparent = dconf_gvdb_utils_get_parent (table, parent_name);
164 if (grandparent != NULL)
165 gvdb_item_set_parent (parent, grandparent);
168 g_free (parent_name);
170 return parent;
173 static gboolean
174 dconf_gvdb_utils_add_key (const gchar *path,
175 GVariant *value,
176 gpointer user_data)
178 GHashTable *gvdb = user_data;
179 GvdbItem *item;
181 g_assert (g_hash_table_lookup (gvdb, path) == NULL);
182 item = gvdb_hash_table_insert (gvdb, path);
183 gvdb_item_set_parent (item, dconf_gvdb_utils_get_parent (gvdb, path));
184 gvdb_item_set_value (item, value);
186 return TRUE;
189 gboolean
190 dconf_gvdb_utils_write_file (const gchar *filename,
191 DConfChangeset *database,
192 GError **error)
194 GHashTable *gvdb;
195 gboolean success;
197 gvdb = gvdb_hash_table_new (NULL, NULL);
198 dconf_changeset_all (database, dconf_gvdb_utils_add_key, gvdb);
199 success = gvdb_table_write_contents (gvdb, filename, FALSE, error);
201 if (!success)
203 gchar *dirname;
205 /* Maybe it failed because the directory doesn't exist. Try
206 * again, after mkdir().
208 dirname = g_path_get_dirname (filename);
209 g_mkdir_with_parents (dirname, 0700);
210 g_free (dirname);
212 g_clear_error (error);
213 success = gvdb_table_write_contents (gvdb, filename, FALSE, error);
216 g_hash_table_unref (gvdb);
218 return success;