gkeyfilesettingsbackend.c 18.8 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Copyright © 2010 Codethink Limited
 * Copyright © 2010 Novell, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * 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 11 12 13 14 15
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * 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 19 20 21 22 23 24 25 26 27 28 29
 *
 * Authors: Vincent Untz <vuntz@gnome.org>
 *          Ryan Lortie <desrt@desrt.ca>
 */

#include "config.h"

#include <stdio.h>
#include <string.h>

#include "gfile.h"
#include "gfileinfo.h"
#include "gfilemonitor.h"
30
#include "gsimplepermission.h"
31
#include "gsettingsbackend.h"
32 33


34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
#define G_TYPE_KEYFILE_SETTINGS_BACKEND      (g_keyfile_settings_backend_get_type ())
#define G_KEYFILE_SETTINGS_BACKEND(inst)     (G_TYPE_CHECK_INSTANCE_CAST ((inst),      \
                                              G_TYPE_KEYFILE_SETTINGS_BACKEND,         \
                                              GKeyfileSettingsBackend))
#define G_IS_KEYFILE_SETTINGS_BACKEND(inst)  (G_TYPE_CHECK_INSTANCE_TYPE ((inst),      \
                                              G_TYPE_KEYFILE_SETTINGS_BACKEND))


typedef GSettingsBackendClass GKeyfileSettingsBackendClass;

typedef struct
{
  GSettingsBackend   parent_instance;

  GKeyFile          *keyfile;
  GPermission       *permission;
  gboolean           writable;

  gchar             *prefix;
  gint               prefix_len;
  gchar             *root_group;
  gint               root_group_len;

  GFile             *file;
  GFileMonitor      *file_monitor;
  guint8             digest[32];
  GFile             *dir;
  GFileMonitor      *dir_monitor;
} GKeyfileSettingsBackend;

64
static GType g_keyfile_settings_backend_get_type (void);
65 66 67
G_DEFINE_TYPE (GKeyfileSettingsBackend,
               g_keyfile_settings_backend,
               G_TYPE_SETTINGS_BACKEND)
68

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
static void
compute_checksum (guint8        *digest,
                  gconstpointer  contents,
                  gsize          length)
{
  GChecksum *checksum;
  gsize len = 32;

  checksum = g_checksum_new (G_CHECKSUM_SHA256);
  g_checksum_update (checksum, contents, length);
  g_checksum_get_digest (checksum, digest, &len);
  g_checksum_free (checksum);
  g_assert (len == 32);
}

static void
g_keyfile_settings_backend_keyfile_write (GKeyfileSettingsBackend *kfsb)
{
  gchar *contents;
  gsize length;

  contents = g_key_file_to_data (kfsb->keyfile, &length, NULL);
  g_file_replace_contents (kfsb->file, contents, length, NULL, FALSE,
                           G_FILE_CREATE_REPLACE_DESTINATION,
                           NULL, NULL, NULL);

  compute_checksum (kfsb->digest, contents, length);
  g_free (contents);
}

static gboolean
group_name_matches (const gchar *group_name,
                    const gchar *prefix)
{
  /* sort of like g_str_has_prefix() except that it must be an exact
   * match or the prefix followed by '/'.
   *
   * for example 'a' is a prefix of 'a' and 'a/b' but not 'ab'.
   */
  gint i;

  for (i = 0; prefix[i]; i++)
    if (prefix[i] != group_name[i])
      return FALSE;

  return group_name[i] == '\0' || group_name[i] == '/';
}

static gboolean
convert_path (GKeyfileSettingsBackend  *kfsb,
              const gchar              *key,
              gchar                   **group,
              gchar                   **basename)
{
  gint key_len = strlen (key);
  gint i;

  if (key_len < kfsb->prefix_len ||
      memcmp (key, kfsb->prefix, kfsb->prefix_len) != 0)
    return FALSE;

  key_len -= kfsb->prefix_len;
  key += kfsb->prefix_len;

  for (i = key_len; i >= 0; i--)
    if (key[i] == '/')
      break;

  if (kfsb->root_group)
    {
      /* if a root_group was specified, make sure the user hasn't given
       * a path that ghosts that group name
       */
      if (i == kfsb->root_group_len && memcmp (key, kfsb->root_group, i) == 0)
        return FALSE;
    }
  else
    {
      /* if no root_group was given, ensure that the user gave a path */
      if (i == -1)
        return FALSE;
    }

  if (group)
    {
      if (i >= 0)
        {
          *group = g_memdup (key, i + 1);
          (*group)[i] = '\0';
        }
      else
        *group = g_strdup (kfsb->root_group);
    }

  if (basename)
    *basename = g_memdup (key + i + 1, key_len - i);

  return TRUE;
}

Allison Karlitskaya's avatar
Allison Karlitskaya committed
169
static gboolean
170 171
path_is_valid (GKeyfileSettingsBackend *kfsb,
               const gchar             *path)
172
{
173 174
  return convert_path (kfsb, path, NULL, NULL);
}
175 176

static GVariant *
177 178 179
get_from_keyfile (GKeyfileSettingsBackend *kfsb,
                  const GVariantType      *type,
                  const gchar             *key)
180
{
181 182
  GVariant *return_value = NULL;
  gchar *group, *name;
183

184 185 186
  if (convert_path (kfsb, key, &group, &name))
    {
      gchar *str;
187

188
      g_assert (*name);
189

190
      str = g_key_file_get_value (kfsb->keyfile, group, name, NULL);
191

192 193 194 195 196 197 198 199 200 201 202
      if (str)
        {
          return_value = g_variant_parse (type, str, NULL, NULL, NULL);
          g_free (str);
        }

      g_free (group);
      g_free (name);
    }

  return return_value;
203 204 205
}

static gboolean
206 207 208
set_to_keyfile (GKeyfileSettingsBackend *kfsb,
                const gchar             *key,
                GVariant                *value)
209
{
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
  gchar *group, *name;

  if (convert_path (kfsb, key, &group, &name))
    {
      if (value)
        {
          gchar *str = g_variant_print (value, FALSE);
          g_key_file_set_value (kfsb->keyfile, group, name, str);
          g_variant_unref (g_variant_ref_sink (value));
          g_free (str);
        }
      else
        {
          if (*name == '\0')
            {
              gchar **groups;
              gint i;

              groups = g_key_file_get_groups (kfsb->keyfile, NULL);
229

230 231 232
              for (i = 0; groups[i]; i++)
                if (group_name_matches (groups[i], group))
                  g_key_file_remove_group (kfsb->keyfile, groups[i], NULL);
233

234 235 236 237 238
              g_strfreev (groups);
            }
          else
            g_key_file_remove_key (kfsb->keyfile, group, name, NULL);
        }
239

240 241
      g_free (group);
      g_free (name);
242

243 244
      return TRUE;
    }
245 246 247 248

  return FALSE;
}

249 250 251 252 253
static GVariant *
g_keyfile_settings_backend_read (GSettingsBackend   *backend,
                                 const gchar        *key,
                                 const GVariantType *expected_type,
                                 gboolean            default_value)
254
{
255 256 257 258
  GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend);

  if (default_value)
    return NULL;
259

260 261
  return get_from_keyfile (kfsb, expected_type, key);
}
262

263 264 265 266 267
typedef struct
{
  GKeyfileSettingsBackend *kfsb;
  gboolean failed;
} WriteManyData;
268

269 270 271 272 273 274 275
static gboolean
g_keyfile_settings_backend_write_one (gpointer key,
                                      gpointer value,
                                      gpointer user_data)
{
  WriteManyData *data = user_data;
  gboolean success;
276

277 278
  success = set_to_keyfile (data->kfsb, key, value);
  g_assert (success);
279

280
  return FALSE;
281 282 283
}

static gboolean
284 285 286
g_keyfile_settings_backend_check_one (gpointer key,
                                      gpointer value,
                                      gpointer user_data)
287
{
288
  WriteManyData *data = user_data;
289

290
  return data->failed = !path_is_valid (data->kfsb, key);
291 292 293
}

static gboolean
294
g_keyfile_settings_backend_write_tree (GSettingsBackend *backend,
295 296 297
                                       GTree            *tree,
                                       gpointer          origin_tag)
{
298 299 300 301
  WriteManyData data = { G_KEYFILE_SETTINGS_BACKEND (backend) };

  if (!data.kfsb->writable)
    return FALSE;
302

303 304 305 306 307 308 309
  g_tree_foreach (tree, g_keyfile_settings_backend_check_one, &data);

  if (data.failed)
    return FALSE;

  g_tree_foreach (tree, g_keyfile_settings_backend_write_one, &data);
  g_keyfile_settings_backend_keyfile_write (data.kfsb);
310 311 312 313 314 315

  g_settings_backend_changed_tree (backend, tree, origin_tag);

  return TRUE;
}

316 317 318 319 320
static gboolean
g_keyfile_settings_backend_write (GSettingsBackend *backend,
                                  const gchar      *key,
                                  GVariant         *value,
                                  gpointer          origin_tag)
321
{
322 323
  GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend);
  gboolean success;
324

325 326
  if (!kfsb->writable)
    return FALSE;
327

328
  success = set_to_keyfile (kfsb, key, value);
329

330
  if (success)
331
    {
332 333
      g_settings_backend_changed (backend, key, origin_tag);
      g_keyfile_settings_backend_keyfile_write (kfsb);
334 335
    }

336
  return success;
337 338
}

339 340 341 342 343 344
static void
g_keyfile_settings_backend_reset (GSettingsBackend *backend,
                                  const gchar      *key,
                                  gpointer          origin_tag)
{
  GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend);
345

346 347
  if (set_to_keyfile (kfsb, key, NULL))
    g_keyfile_settings_backend_keyfile_write (kfsb);
348

349
  g_settings_backend_changed (backend, key, origin_tag);
350 351 352 353 354 355
}

static gboolean
g_keyfile_settings_backend_get_writable (GSettingsBackend *backend,
                                         const gchar      *name)
{
356
  GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend);
357

358
  return kfsb->writable && path_is_valid (kfsb, name);
359 360
}

361 362 363 364
static GPermission *
g_keyfile_settings_backend_get_permission (GSettingsBackend *backend,
                                           const gchar      *path)
{
365 366 367
  GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend);

  return g_object_ref (kfsb->permission);
368 369
}

370
static void
371 372 373 374
keyfile_to_tree (GKeyfileSettingsBackend *kfsb,
                 GTree                   *tree,
                 GKeyFile                *keyfile,
                 gboolean                 dup_check)
375
{
376 377
  gchar **groups;
  gint i;
378

379 380
  groups = g_key_file_get_groups (keyfile, NULL);
  for (i = 0; groups[i]; i++)
381
    {
382 383 384
      gboolean is_root_group;
      gchar **keys;
      gint j;
385

386
      is_root_group = g_strcmp0 (kfsb->root_group, groups[i]) == 0;
387 388 389 390 391 392 393

      /* reject group names that will form invalid key names */
      if (!is_root_group &&
          (g_str_has_prefix (groups[i], "/") ||
           g_str_has_suffix (groups[i], "/") || strstr (groups[i], "//")))
        continue;

394
      keys = g_key_file_get_keys (keyfile, groups[i], NULL, NULL);
395
      g_assert (keys != NULL);
396

397
      for (j = 0; keys[j]; j++)
398
        {
399
          gchar *path, *value;
400

401 402 403 404
          /* reject key names with slashes in them */
          if (strchr (keys[j], '/'))
            continue;

405 406 407 408
          if (is_root_group)
            path = g_strdup_printf ("%s%s", kfsb->prefix, keys[j]);
          else
            path = g_strdup_printf ("%s%s/%s", kfsb->prefix, groups[i], keys[j]);
409

410
          value = g_key_file_get_value (keyfile, groups[i], keys[j], NULL);
411

412
          if (dup_check && g_strcmp0 (g_tree_lookup (tree, path), value) == 0)
413
            {
414 415 416
              g_tree_remove (tree, path);
              g_free (value);
              g_free (path);
417 418
            }
          else
419
            g_tree_insert (tree, path, value);
420 421 422 423 424
        }

      g_strfreev (keys);
    }
  g_strfreev (groups);
425
}
426

427 428 429 430 431 432
static void
g_keyfile_settings_backend_keyfile_reload (GKeyfileSettingsBackend *kfsb)
{
  guint8 digest[32];
  gchar *contents;
  gsize length;
433

434 435
  contents = NULL;
  length = 0;
436

437 438
  g_file_load_contents (kfsb->file, NULL, &contents, &length, NULL, NULL);
  compute_checksum (digest, contents, length);
439

440
  if (memcmp (kfsb->digest, digest, sizeof digest) != 0)
441
    {
442 443
      GKeyFile *keyfiles[2];
      GTree *tree;
444

445 446
      tree = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
                              g_free, g_free);
447

448 449
      keyfiles[0] = kfsb->keyfile;
      keyfiles[1] = g_key_file_new ();
450

451 452 453 454
      if (length > 0)
        g_key_file_load_from_data (keyfiles[1], contents, length,
                                   G_KEY_FILE_KEEP_COMMENTS |
                                   G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
455

456 457 458 459
      keyfile_to_tree (kfsb, tree, keyfiles[0], FALSE);
      keyfile_to_tree (kfsb, tree, keyfiles[1], TRUE);
      g_key_file_free (keyfiles[0]);
      kfsb->keyfile = keyfiles[1];
460

461 462
      if (g_tree_nnodes (tree) > 0)
        g_settings_backend_changed_tree (&kfsb->parent_instance, tree, NULL);
463

464
      g_tree_unref (tree);
465

466
      memcpy (kfsb->digest, digest, sizeof digest);
467 468
    }

469
  g_free (contents);
470 471 472
}

static void
473
g_keyfile_settings_backend_keyfile_writable (GKeyfileSettingsBackend *kfsb)
474
{
475 476
  GFileInfo *fileinfo;
  gboolean writable;
477

478
  fileinfo = g_file_query_info (kfsb->dir, "access::*", 0, NULL, NULL);
479

480
  if (fileinfo)
481
    {
482 483 484 485
      writable =
        g_file_info_get_attribute_boolean (fileinfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) &&
        g_file_info_get_attribute_boolean (fileinfo, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);
      g_object_unref (fileinfo);
486
    }
487 488
  else
    writable = FALSE;
489

490 491 492 493 494
  if (writable != kfsb->writable)
    {
      kfsb->writable = writable;
      g_settings_backend_path_writable_changed (&kfsb->parent_instance, "/");
    }
495 496 497 498 499
}

static void
g_keyfile_settings_backend_finalize (GObject *object)
{
500
  GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (object);
501

502 503
  g_key_file_free (kfsb->keyfile);
  g_object_unref (kfsb->permission);
504

505 506 507
  g_file_monitor_cancel (kfsb->file_monitor);
  g_object_unref (kfsb->file_monitor);
  g_object_unref (kfsb->file);
508

509 510 511
  g_file_monitor_cancel (kfsb->dir_monitor);
  g_object_unref (kfsb->dir_monitor);
  g_object_unref (kfsb->dir);
512

513 514
  g_free (kfsb->root_group);
  g_free (kfsb->prefix);
515 516 517 518 519 520

  G_OBJECT_CLASS (g_keyfile_settings_backend_parent_class)
    ->finalize (object);
}

static void
521
g_keyfile_settings_backend_init (GKeyfileSettingsBackend *kfsb)
522 523 524 525 526 527 528 529 530 531
{
}

static void
g_keyfile_settings_backend_class_init (GKeyfileSettingsBackendClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->finalize = g_keyfile_settings_backend_finalize;

532 533
  class->read = g_keyfile_settings_backend_read;
  class->write = g_keyfile_settings_backend_write;
534
  class->write_tree = g_keyfile_settings_backend_write_tree;
535 536 537
  class->reset = g_keyfile_settings_backend_reset;
  class->get_writable = g_keyfile_settings_backend_get_writable;
  class->get_permission = g_keyfile_settings_backend_get_permission;
538 539
  /* No need to implement subscribed/unsubscribe: the only point would be to
   * stop monitoring the file when there's no GSettings anymore, which is no
540 541
   * big win.
   */
542 543 544 545 546 547 548 549 550 551 552
}

static void
file_changed (GFileMonitor      *monitor,
              GFile             *file,
              GFile             *other_file,
              GFileMonitorEvent  event_type,
              gpointer           user_data)
{
  GKeyfileSettingsBackend *kfsb = user_data;

553 554 555
  /* Ignore file deletions, let the GKeyFile content remain in tact. */
  if (event_type != G_FILE_MONITOR_EVENT_DELETED)
    g_keyfile_settings_backend_keyfile_reload (kfsb);
556
}
557

558 559 560 561 562 563 564 565 566 567
static void
dir_changed (GFileMonitor       *monitor,
              GFile             *file,
              GFile             *other_file,
              GFileMonitorEvent  event_type,
              gpointer           user_data)
{
  GKeyfileSettingsBackend *kfsb = user_data;

  g_keyfile_settings_backend_keyfile_writable (kfsb);
568
}
569

570 571 572 573
/**
 * g_keyfile_settings_backend_new:
 * @filename: the filename of the keyfile
 * @root_path: the path under which all settings keys appear
574
 * @root_group: (nullable): the group name corresponding to
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
 *              @root_path, or %NULL
 *
 * Creates a keyfile-backed #GSettingsBackend.
 *
 * The filename of the keyfile to use is given by @filename.
 *
 * All settings read to or written from the backend must fall under the
 * path given in @root_path (which must start and end with a slash and
 * not contain two consecutive slashes).  @root_path may be "/".
 *
 * If @root_group is non-%NULL then it specifies the name of the keyfile
 * group used for keys that are written directly below @root_path.  For
 * example, if @root_path is "/apps/example/" and @root_group is
 * "toplevel", then settings the key "/apps/example/enabled" to a value
 * of %TRUE will cause the following to appear in the keyfile:
 *
591
 * |[
592
 *   [toplevel]
593 594
 *   enabled=true
 * ]|
595 596 597 598 599 600 601
 *
 * If @root_group is %NULL then it is not permitted to store keys
 * directly below the @root_path.
 *
 * For keys not stored directly below @root_path (ie: in a sub-path),
 * the name of the subpath (with the final slash stripped) is used as
 * the name of the keyfile group.  To continue the example, if
602
 * "/apps/example/profiles/default/font-size" were set to
603 604
 * 12 then the following would appear in the keyfile:
 *
605
 * |[
606 607
 *   [profiles/default]
 *   font-size=12
608
 * ]|
609 610 611 612 613 614 615
 *
 * The backend will refuse writes (and return writability as being
 * %FALSE) for keys outside of @root_path and, in the event that
 * @root_group is %NULL, also for keys directly under @root_path.
 * Writes will also be refused if the backend detects that it has the
 * inability to rewrite the keyfile (ie: the containing directory is not
 * writable).
616 617 618 619 620
 *
 * There is no checking done for your key namespace clashing with the
 * syntax of the key file format.  For example, if you have '[' or ']'
 * characters in your path names or '=' in your key names you may be in
 * trouble.
621 622
 *
 * Returns: (transfer full): a keyfile-backed #GSettingsBackend
623
 **/
624
GSettingsBackend *
625 626 627
g_keyfile_settings_backend_new (const gchar *filename,
                                const gchar *root_path,
                                const gchar *root_group)
628
{
629
  GKeyfileSettingsBackend *kfsb;
630

631 632 633 634 635
  g_return_val_if_fail (filename != NULL, NULL);
  g_return_val_if_fail (root_path != NULL, NULL);
  g_return_val_if_fail (g_str_has_prefix (root_path, "/"), NULL);
  g_return_val_if_fail (g_str_has_suffix (root_path, "/"), NULL);
  g_return_val_if_fail (strstr (root_path, "//") == NULL, NULL);
636

637 638 639
  kfsb = g_object_new (G_TYPE_KEYFILE_SETTINGS_BACKEND, NULL);
  kfsb->keyfile = g_key_file_new ();
  kfsb->permission = g_simple_permission_new (TRUE);
640

641 642 643 644
  kfsb->file = g_file_new_for_path (filename);
  kfsb->dir = g_file_get_parent (kfsb->file);
  g_file_make_directory_with_parents (kfsb->dir, NULL, NULL);

645 646
  kfsb->file_monitor = g_file_monitor (kfsb->file, 0, NULL, NULL);
  kfsb->dir_monitor = g_file_monitor (kfsb->dir, 0, NULL, NULL);
647 648 649 650 651 652 653 654 655

  kfsb->prefix_len = strlen (root_path);
  kfsb->prefix = g_strdup (root_path);

  if (root_group)
    {
      kfsb->root_group_len = strlen (root_group);
      kfsb->root_group = g_strdup (root_group);
    }
656

657
  compute_checksum (kfsb->digest, NULL, 0);
658

659 660 661 662
  g_signal_connect (kfsb->file_monitor, "changed",
                    G_CALLBACK (file_changed), kfsb);
  g_signal_connect (kfsb->dir_monitor, "changed",
                    G_CALLBACK (dir_changed), kfsb);
663

664 665
  g_keyfile_settings_backend_keyfile_writable (kfsb);
  g_keyfile_settings_backend_keyfile_reload (kfsb);
666

667
  return G_SETTINGS_BACKEND (kfsb);
668
}