Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package dconf for openSUSE:Factory checked 
in at 2026-10-30 18:08:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/dconf (Old)
 and      /work/SRC/openSUSE:Factory/.dconf.new.1255 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "dconf"

Fri Oct 30 18:08:49 2026 rev:58 rq:1385505 version:51.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/dconf/dconf.changes      2026-03-06 
18:17:27.949349555 +0100
+++ /work/SRC/openSUSE:Factory/.dconf.new.1255/dconf.changes    2026-10-30 
18:11:30.705286703 +0100
@@ -1,0 +2,27 @@
+Wed Sep 30 17:12:07 UTC 2026 - Dominique Leuenberger <[email protected]>
+
+- Update version dependencies according to meson.build.
+
+-------------------------------------------------------------------
+Mon Sep 14 15:48:08 UTC 2026 - Dominique Leuenberger <[email protected]>
+
+- Update to version 51.0:
+  + No changes since 51.rc.
+
+-------------------------------------------------------------------
+Wed Sep  2 06:56:26 UTC 2026 - Bjørn Lie <[email protected]>
+
+- Update to version 51.rc:
+  + Update overview regarding safe writer.
+
+-------------------------------------------------------------------
+Thu Aug  6 13:58:41 UTC 2026 - Bjørn Lie <[email protected]>
+
+- Update to version 51.beta:
+  + Implement safe GVDB writer and use it by default for homes on
+    NFS
+  + docs:
+    - Describe DBDIR argument in man page
+    - Fix a typo
+
+-------------------------------------------------------------------

Old:
----
  dconf-0.49.0.tar.xz

New:
----
  _scmsync.obsinfo
  _service
  build.specials.obscpio
  dconf-51.0.tar.xz
  dconf.obsinfo

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ dconf.spec ++++++
--- /var/tmp/diff_new_pack.PHn4Ht/_old  2026-10-30 18:11:32.277352514 +0100
+++ /var/tmp/diff_new_pack.PHn4Ht/_new  2026-10-30 18:11:32.280352639 +0100
@@ -17,13 +17,13 @@
 
 
 Name:           dconf
-Version:        0.49.0
+Version:        51.0
 Release:        0
 Summary:        Key-based configuration system
 License:        LGPL-2.1-or-later
 Group:          System/Libraries
 URL:            https://live.gnome.org/dconf
-Source0:        
https://download.gnome.org/sources/dconf/0.49/%{name}-%{version}.tar.xz
+Source0:        %{name}-%{version}.tar.xz
 # PATCH-FIX-UPSTREAM 0001-gvdb-Restore-permissions-on-changed-files.patch 
bsc#971074 bgo#758066 bsc#1203344 [email protected] -- Restore permissions on 
files changed by dconf update.
 Patch0:         0001-gvdb-Restore-permissions-on-changed-files.patch
 BuildRequires:  docbook-xsl-stylesheets
@@ -34,8 +34,8 @@
 BuildRequires:  xsltproc
 BuildRequires:  pkgconfig(bash-completion)
 BuildRequires:  pkgconfig(dbus-1)
-BuildRequires:  pkgconfig(gio-unix-2.0)
-BuildRequires:  pkgconfig(glib-2.0) >= 2.44.0
+BuildRequires:  pkgconfig(gio-unix-2.0) >= 2.25.7
+BuildRequires:  pkgconfig(glib-2.0) >= 2.76
 BuildRequires:  pkgconfig(gobject-introspection-1.0)
 # dconf provides a dbus service, but has no dependency on dbus in any way
 # (because it uses gdbus), so we need an explicit Requires

++++++ _scmsync.obsinfo ++++++
mtime: 1792250026
commit: e63c31ae907b35de58de8f6d8bc1d23c840e24c1672f5b5847387066a8346f28
url: https://src.opensuse.org/GNOME/dconf
revision: e63c31ae907b35de58de8f6d8bc1d23c840e24c1672f5b5847387066a8346f28
trackingbranch: factory
projectscmsync: https://src.opensuse.org/GNOME/_ObsPrj

++++++ _service ++++++
<?xml version="1.0"?>
<services>
  <service name="obs_scm" mode="manual">
    <param name="scm">git</param>
    <param name="url">https://gitlab.gnome.org/GNOME/dconf.git</param>
    <param name="revision">51.0</param>
    <param name="versionformat">@PARENT_TAG@+@TAG_OFFSET@</param>
    <param name="versionrewrite-pattern">(.*)\+0</param>
    <param name="versionrewrite-replacement">\1</param>
    <!--    <param name="changesgenerate">enable</param>
-->
  </service>
  <service name="tar" mode="manual"/>
  <service name="recompress" mode="manual">
    <param name="file">*.tar</param>
    <param name="compression">xz</param>
  </service>
  <service name="set_version" mode="manual"/>
</services>

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-10-17 17:13:46.000000000 +0200
@@ -0,0 +1,6 @@
+*.obscpio
+*.osc
+_build.*
+.pbuild
+*.patches~
+osc-collab.*

++++++ dconf-0.49.0.tar.xz -> dconf-51.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/.gitlab-ci.yml 
new/dconf-51.0/.gitlab-ci.yml
--- old/dconf-0.49.0/.gitlab-ci.yml     2025-09-15 17:53:57.000000000 +0200
+++ new/dconf-51.0/.gitlab-ci.yml       2026-09-14 17:37:59.000000000 +0200
@@ -31,10 +31,10 @@
     - ninja -C _build all dconf-doc
 
     - mkdir -p _coverage
-    - lcov --rc lcov_branch_coverage=1 --directory _build --capture --initial 
--output-file "_coverage/${CI_JOB_NAME}-baseline.lcov"
+    - lcov --rc branch_coverage=1 --directory _build --capture --initial 
--output-file "_coverage/${CI_JOB_NAME}-baseline.lcov"
 
     - meson test -C _build --timeout-multiplier 
${MESON_TEST_TIMEOUT_MULTIPLIER}
-    - lcov --rc lcov_branch_coverage=1 --directory _build --capture 
--output-file "_coverage/${CI_JOB_NAME}.lcov"
+    - lcov --rc branch_coverage=1 --directory _build --capture --output-file 
"_coverage/${CI_JOB_NAME}.lcov"
   coverage: '/^\s+lines\.+:\s+([\d.]+\%)\s+/'
   except:
     - tags
@@ -66,10 +66,10 @@
     - ninja -C _build all dconf-doc
 
     - mkdir -p _coverage
-    - lcov --rc lcov_branch_coverage=1 --directory _build --capture --initial 
--output-file "_coverage/${CI_JOB_NAME}-baseline.lcov"
+    - lcov --rc branch_coverage=1 --directory _build --capture --initial 
--output-file "_coverage/${CI_JOB_NAME}-baseline.lcov"
 
     - meson test -C _build --timeout-multiplier 
${MESON_TEST_TIMEOUT_MULTIPLIER}
-    - lcov --rc lcov_branch_coverage=1 --directory _build --capture 
--output-file "_coverage/${CI_JOB_NAME}.lcov"
+    - lcov --rc branch_coverage=1 --directory _build --capture --output-file 
"_coverage/${CI_JOB_NAME}.lcov"
 
     - mkdir -p public/
     - mv _build/docs/html/ public/docs/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/NEWS new/dconf-51.0/NEWS
--- old/dconf-0.49.0/NEWS       2025-09-15 17:53:57.000000000 +0200
+++ new/dconf-51.0/NEWS 2026-09-14 17:37:59.000000000 +0200
@@ -1,3 +1,22 @@
+Changes in dconf 51.0
+=======================
+
+ - No changes since 51.rc
+
+Changes in dconf 51.rc
+=======================
+
+ - Update overview regarding safe writer (Marek Kasik, !97)
+
+
+Changes in dconf 51.beta
+=======================
+
+ - Implement safe GVDB writer and use it by default for homes on NFS (Marek 
Kasik, Ray Strode, !88)
+ - docs: Describe DBDIR argument in man page (Marek Kasik, !94)
+ - docs: Fix a typo (Marek Kasik)
+
+
 Changes in dconf 0.49.0
 =======================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/common/dconf-gvdb-utils.c 
new/dconf-51.0/common/dconf-gvdb-utils.c
--- old/dconf-0.49.0/common/dconf-gvdb-utils.c  2025-09-15 17:53:57.000000000 
+0200
+++ new/dconf-51.0/common/dconf-gvdb-utils.c    2026-09-14 17:37:59.000000000 
+0200
@@ -62,6 +62,132 @@
   return database;
 }
 
+#include <fcntl.h>
+#include <unistd.h>
+
+#define DCONF_GVDB_UTILS_ESTALE_RETRY_TIMEOUT_USEC (G_USEC_PER_SEC)
+#define DCONF_GVDB_UTILS_ESTALE_INITIAL_DELAY_USEC 5000
+#define DCONF_GVDB_UTILS_ESTALE_MAX_DELAY_USEC     100000
+
+
+static gboolean
+dconf_gvdb_utils_set_file_error (const gchar  *filename,
+                                 gint          saved_errno,
+                                 GError      **error)
+{
+  g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno),
+               "%s: %s", filename, g_strerror (saved_errno));
+
+  return FALSE;
+}
+
+static gboolean
+dconf_gvdb_utils_read_file_once (const gchar  *filename,
+                                 gchar       **contents,
+                                 gsize        *size,
+                                 gint         *saved_errno,
+                                 GError      **error)
+{
+  gint fd;
+
+  *contents = NULL;
+  *size = 0;
+  *saved_errno = 0;
+
+  do
+    fd = g_open (filename, O_RDONLY | O_CLOEXEC, 0);
+  while (fd == -1 && errno == EINTR);
+
+  if (fd == -1)
+    {
+      *saved_errno = errno;
+
+      return dconf_gvdb_utils_set_file_error (filename, *saved_errno, error);
+    }
+  else
+    {
+      GString *data;
+      guint8   buffer[4096];
+
+      data = g_string_new (NULL);
+
+      for (;;)
+        {
+          gssize bytes_read;
+
+          bytes_read = read (fd, buffer, sizeof buffer);
+          if (bytes_read > 0)
+            {
+              g_string_append_len (data, (const gchar *) buffer, bytes_read);
+            }
+          else if (bytes_read == 0)
+            {
+              break;
+            }
+          else if (errno != EINTR)
+            {
+              *saved_errno = errno;
+              close (fd);
+              g_string_free (data, TRUE);
+
+              return dconf_gvdb_utils_set_file_error (filename, *saved_errno, 
error);
+            }
+        }
+
+      close (fd);
+
+      *size = data->len;
+      *contents = g_string_free_and_steal (data);
+
+      return TRUE;
+    }
+}
+
+static gboolean
+dconf_gvdb_utils_read_file (const gchar  *filename,
+                            gchar       **contents,
+                            gsize        *size,
+                            GError      **error)
+{
+  GError *local_error = NULL;
+  gint saved_errno = 0;
+  gint64 estale_retry_deadline = 0;
+  guint estale_retry_delay = DCONF_GVDB_UTILS_ESTALE_INITIAL_DELAY_USEC;
+
+  while (!dconf_gvdb_utils_read_file_once (filename, contents, size,
+                                           &saved_errno, &local_error))
+    {
+#ifdef ESTALE
+      if (saved_errno == ESTALE)
+        {
+          gint64 now = g_get_monotonic_time ();
+
+          if (estale_retry_deadline == 0)
+            estale_retry_deadline = now + 
DCONF_GVDB_UTILS_ESTALE_RETRY_TIMEOUT_USEC;
+
+          if (now < estale_retry_deadline)
+            {
+              gint64 remaining_usec = estale_retry_deadline - now;
+              guint delay_usec = MIN (estale_retry_delay, remaining_usec);
+
+              g_clear_error (&local_error);
+              g_usleep (delay_usec);
+
+              estale_retry_delay = MIN (estale_retry_delay * 2,
+                                        
DCONF_GVDB_UTILS_ESTALE_MAX_DELAY_USEC);
+              continue;
+            }
+        }
+#endif
+
+      g_propagate_error (error, local_error);
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
 DConfChangeset *
 dconf_gvdb_utils_read_and_back_up_file (const gchar  *filename,
                                         gboolean     *file_missing,
@@ -73,7 +199,7 @@
   gchar *contents;
   gsize size;
 
-  if (g_file_get_contents (filename, &contents, &size, &my_error))
+  if (dconf_gvdb_utils_read_file (filename, &contents, &size, &my_error))
     {
       GBytes *bytes;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/docs/dconf-overview.xml 
new/dconf-51.0/docs/dconf-overview.xml
--- old/dconf-0.49.0/docs/dconf-overview.xml    2025-09-15 17:53:57.000000000 
+0200
+++ new/dconf-51.0/docs/dconf-overview.xml      2026-09-14 17:37:59.000000000 
+0200
@@ -62,10 +62,9 @@
       can be read back immediately) until the service signals that a write has 
completed.
     </para>
     <para>
-      The binary database format that dconf uses by default is not suitable 
for use on NFS, where mmap does not
-      work well. To handle this common use case, dconf can be configured to 
place its binary database in
-      <envar>XDG_RUNTIME_DIR</envar> (which is guaranteed to be local, but 
non-persistent) and synchronize it
-      with a plain text keyfile in the users home directory.
+      To overcome mmap issues when the binary database is on NFS, dconf places 
a binary database in
+      <envar>XDG_RUNTIME_DIR</envar> (which is guaranteed to be local, but 
non-persistent) and synchronizes it
+      automatically with the one in the user's home directory.
     </para>
   </refsect1>
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/docs/dconf-tool.xml 
new/dconf-51.0/docs/dconf-tool.xml
--- old/dconf-0.49.0/docs/dconf-tool.xml        2025-09-15 17:53:57.000000000 
+0200
+++ new/dconf-51.0/docs/dconf-tool.xml  2026-09-14 17:37:59.000000000 +0200
@@ -105,7 +105,7 @@
     </para>
 
     <para>
-      The <replaceable>OUTPUT</replaceable> argument must the location to 
write a (binary) dconf database to and the
+      The <replaceable>OUTPUT</replaceable> argument must be the location to 
write a (binary) dconf database to and the
       <replaceable>KEYFILEDIR</replaceable> argument must be a .d directory 
containing keyfiles.
     </para>
 
@@ -115,6 +115,11 @@
     </para>
 
     <para>
+      The optional <replaceable>DBDIR</replaceable> argument is a directory 
containing the system dconf databases to be updated.
+      The default directory is typically <filename>/etc/dconf/db/</filename> 
and users need sufficient privileges to update it.
+    </para>
+
+    <para>
       Note that dconf needs a D-Bus session bus connection to write changes to 
the dconf database.
     </para>
   </refsect1>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/engine/dconf-engine-profile.c 
new/dconf-51.0/engine/dconf-engine-profile.c
--- old/dconf-0.49.0/engine/dconf-engine-profile.c      2025-09-15 
17:53:57.000000000 +0200
+++ new/dconf-51.0/engine/dconf-engine-profile.c        2026-09-14 
17:37:59.000000000 +0200
@@ -28,6 +28,7 @@
 #include <errno.h>
 
 #include "dconf-engine-source.h"
+#include "dconf-engine-source-private.h"
 
 #define MANDATORY_DIR           "/run/dconf/user/" /* + getuid () */
 #define RUNTIME_PROFILE         /* XDG_RUNTIME_DIR + */ "/dconf/profile"
@@ -137,6 +138,30 @@
   return source;
 }
 
+static gboolean
+file_is_on_nfs (const gchar  *filename)
+{
+  GFileInfo *file_info;
+  gboolean   result = FALSE;
+  GFile     *file;
+
+  file = g_file_new_for_path (filename);
+  file_info = g_file_query_filesystem_info (file, 
G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, NULL, NULL); /* ASYNC ? */
+  if (file_info != NULL)
+    {
+      const gchar *filesystem_type = g_file_info_get_attribute_string 
(file_info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
+      if (g_strcmp0 (filesystem_type, "nfs") == 0 ||
+          g_strcmp0 (filesystem_type, "nfs4") == 0)
+        {
+          result = TRUE;
+        }
+      g_object_unref (file_info);
+    }
+  g_clear_object (&file);
+
+  return result;
+}
+
 static DConfEngineSource **
 dconf_engine_read_profile_file (FILE *file,
                                 gint *n_sources)
@@ -145,7 +170,7 @@
   gchar line[80];
   gint n = 0, a;
 
-  sources = g_new (DConfEngineSource *, (a = 4));
+  sources = g_new0 (DConfEngineSource *, (a = 4));
 
   while (fgets (line, sizeof line, file))
     {
@@ -257,6 +282,12 @@
   return dconf_engine_fopen (path, "r");
 }
 
+static inline gboolean
+engine_source_is_user_db (DConfEngineSource *source)
+{
+  return source->vtable == &dconf_engine_source_user_vtable;
+}
+
 DConfEngineSource **
 dconf_engine_profile_open (const gchar *profile,
                            gint        *n_sources)
@@ -300,7 +331,10 @@
 
   /* 5. Default profile */
   if (profile == NULL && file == NULL)
-    return dconf_engine_default_profile (n_sources);
+    {
+      sources = dconf_engine_default_profile (n_sources);
+      goto out;
+    }
 
   /* At this point either we have a profile name or file open, but never
    * both.  If it's a profile name, we try to open it.
@@ -326,5 +360,34 @@
       sources = dconf_engine_null_profile (n_sources);
     }
 
+out:
+  /* Switch to "safe" backend if user config directory is on NFS.
+     This avoids the need to use "keyfile" backend which was recommended
+     for such situation before.
+   */
+  if (sources != NULL && sources[0] != NULL)
+    {
+      if (engine_source_is_user_db (sources[0]))
+        {
+          if (G_UNLIKELY (file_is_on_nfs (g_get_user_config_dir ())))
+            {
+              DConfEngineSource *old_source = sources[0];
+
+              if (old_source->name != NULL)
+                {
+                  gchar *name = g_strdup_printf ("service-db:safe/%s", 
old_source->name);
+                  sources[0] = dconf_engine_source_new (name);
+                  g_free (name);
+                }
+              else
+                {
+                  sources[0] = dconf_engine_source_new 
("service-db:safe/user");
+                }
+
+              dconf_engine_source_free (old_source);
+            }
+        }
+    }
+
   return sources;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/meson.build new/dconf-51.0/meson.build
--- old/dconf-0.49.0/meson.build        2025-09-15 17:53:57.000000000 +0200
+++ new/dconf-51.0/meson.build  2026-09-14 17:37:59.000000000 +0200
@@ -1,6 +1,6 @@
 project(
   'dconf', ['c'],
-  version: '0.49.0',
+  version: '51.0',
   license: 'LGPL2.1+',
   meson_version: '>= 0.60.0',
 )
@@ -43,7 +43,7 @@
 
 gio_dep = dependency('gio-2.0', version: gio_req_version)
 gio_unix_dep = dependency('gio-unix-2.0', version: gio_req_version)
-glib_dep = dependency('glib-2.0', version: '>= 2.67.4')
+glib_dep = dependency('glib-2.0', version: '>= 2.76')
 
 gio_module_dir = gio_dep.get_variable(pkgconfig: 'giomoduledir', 
pkgconfig_define: ['libdir', dconf_libdir])
 dbus_session_service_dir = dependency('dbus-1').get_variable(pkgconfig: 
'session_bus_services_dir', pkgconfig_define: ['datadir', dconf_datadir])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/service/dconf-keyfile-writer.c 
new/dconf-51.0/service/dconf-keyfile-writer.c
--- old/dconf-0.49.0/service/dconf-keyfile-writer.c     2025-09-15 
17:53:57.000000000 +0200
+++ new/dconf-51.0/service/dconf-keyfile-writer.c       2026-09-14 
17:37:59.000000000 +0200
@@ -21,6 +21,7 @@
 #include "config.h"
 
 #include "dconf-writer.h"
+#include "dconf-writer-common.h"
 
 #include <string.h>
 #include <unistd.h>
@@ -198,54 +199,9 @@
 
   g_clear_pointer (&kfw->contents, g_free);
 
-  kfw->lock_fd = open (kfw->lock_filename, O_RDWR | O_CREAT, 0666);
+  kfw->lock_fd = dconf_lock_file_lock (kfw->lock_filename, error);
   if (kfw->lock_fd == -1)
-    {
-      gchar *dirname;
-
-      /* Maybe it failed because the directory doesn't exist.  Try
-       * again, after mkdir().
-       */
-      dirname = g_path_get_dirname (kfw->lock_filename);
-      g_mkdir_with_parents (dirname, 0700);
-      g_free (dirname);
-
-      kfw->lock_fd = open (kfw->lock_filename, O_RDWR | O_CREAT, 0666);
-      if (kfw->lock_fd == -1)
-        {
-          gint saved_errno = errno;
-
-          g_set_error (error, G_FILE_ERROR, g_file_error_from_errno 
(saved_errno),
-                       "%s: %s", kfw->lock_filename, g_strerror (saved_errno));
-          return FALSE;
-        }
-    }
-
-  while (TRUE)
-    {
-      struct flock lock;
-
-      lock.l_type = F_WRLCK;
-      lock.l_whence = 0;
-      lock.l_start = 0;
-      lock.l_len = 0; /* lock all bytes */
-
-      if (fcntl (kfw->lock_fd, F_SETLKW, &lock) == 0)
-        break;
-
-      if (errno != EINTR)
-        {
-          gint saved_errno = errno;
-
-          g_set_error (error, G_FILE_ERROR, g_file_error_from_errno 
(saved_errno),
-                       "%s: unable to fcntl(F_SETLKW): %s", 
kfw->lock_filename, g_strerror (saved_errno));
-          close (kfw->lock_fd);
-          kfw->lock_fd = -1;
-          return FALSE;
-        }
-
-      /* it was EINTR.  loop again. */
-    }
+    return FALSE;
 
   if (!g_file_get_contents (kfw->filename, &kfw->contents, NULL, &local_error))
     {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/service/dconf-safe-writer.c 
new/dconf-51.0/service/dconf-safe-writer.c
--- old/dconf-0.49.0/service/dconf-safe-writer.c        1970-01-01 
01:00:00.000000000 +0100
+++ new/dconf-51.0/service/dconf-safe-writer.c  2026-09-14 17:37:59.000000000 
+0200
@@ -0,0 +1,286 @@
+/*
+ * Copyright © 2025 Red Hat
+ *
+ * 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
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * 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
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Marek Kasik <[email protected]>
+ *
+ * This file is based on DconfWriter and DconfKeyfileWriter.
+ * This backend should be safe to run on homes shared over NFS
+ * while maintaining compatibility of stored data as it uses
+ * GVDB format.
+ *
+ */
+
+#include "config.h"
+
+#include "dconf-writer.h"
+#include "dconf-writer-common.h"
+
+#include "../common/dconf-gvdb-utils.h"
+
+#include <glib/gstdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+typedef DConfWriterClass DConfSafeWriterClass;
+
+typedef struct
+{
+  DConfWriter   parent_instance;
+  gchar        *filename;
+  gchar        *lock_filename;
+  gint          lock_fd;
+  GFileMonitor *monitor;
+  guint         scheduled_update;
+
+  DConfChangeset *uncommitted_values;
+  DConfChangeset *committed_values;
+  gboolean        committed_values_missing;
+} DConfSafeWriter;
+
+G_DEFINE_TYPE (DConfSafeWriter, dconf_safe_writer, DCONF_TYPE_WRITER)
+
+static gboolean dconf_safe_update (gpointer user_data);
+
+static void
+dconf_safe_changed (GFileMonitor      *monitor,
+                    GFile             *file,
+                    GFile             *other_file,
+                    GFileMonitorEvent  event_type,
+                    gpointer           user_data)
+{
+  DConfSafeWriter *sw = user_data;
+
+  if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT ||
+      event_type == G_FILE_MONITOR_EVENT_CREATED)
+    {
+      if (!sw->scheduled_update)
+        sw->scheduled_update = g_idle_add (dconf_safe_update, sw);
+    }
+}
+
+/* Acquire lock and check GVDB for changes against committed values. */
+static gboolean
+dconf_safe_writer_begin (DConfWriter  *writer,
+                         GError      **error)
+{
+  DConfSafeWriter *sw = (DConfSafeWriter *) writer;
+  DConfChangeset *changes;
+  gboolean missing;
+
+  if (sw->filename == NULL)
+    {
+      GFile *file;
+      gchar *lock_base_filename;
+
+      sw->filename = g_build_filename (g_get_user_config_dir (), "dconf", 
dconf_writer_get_name (writer), NULL);
+      lock_base_filename = g_strconcat (".", dconf_writer_get_name (writer), 
".lock", NULL);
+      sw->lock_filename = g_build_filename (g_get_user_config_dir (), "dconf", 
lock_base_filename, NULL);
+      g_free (lock_base_filename);
+
+      /* See https://bugzilla.gnome.org/show_bug.cgi?id=691618 */
+      file = g_vfs_get_file_for_path (g_vfs_get_local (), sw->filename);
+      sw->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, 
error);
+      g_object_unref (file);
+      if (sw->monitor == NULL)
+        {
+          g_clear_pointer (&sw->filename, g_free);
+          g_clear_pointer (&sw->lock_filename, g_free);
+
+          return FALSE;
+        }
+
+      /* There has to be an initial change to begin watching for the "changed" 
signal! */
+      g_signal_connect (sw->monitor, "changed", G_CALLBACK 
(dconf_safe_changed), sw);
+    }
+
+  sw->lock_fd = dconf_lock_file_lock (sw->lock_filename, error);
+  if (sw->lock_fd == -1)
+    return FALSE;
+
+  g_clear_pointer (&sw->committed_values, dconf_changeset_unref);
+  sw->committed_values = dconf_gvdb_utils_read_and_back_up_file (sw->filename, 
&missing, error);
+  if (sw->committed_values == NULL)
+    return FALSE;
+  sw->committed_values_missing = missing;
+
+  if (!DCONF_WRITER_CLASS (dconf_safe_writer_parent_class)->begin (writer, 
error))
+    return FALSE;
+
+  changes = dconf_writer_diff (writer, sw->committed_values);
+  if (changes != NULL)
+    {
+      DCONF_WRITER_CLASS (dconf_safe_writer_parent_class)->change (writer, 
changes, "");
+      dconf_changeset_unref (changes);
+    }
+
+  sw->uncommitted_values = dconf_changeset_new_database (sw->committed_values);
+
+  return TRUE;
+}
+
+/* Add the change to uncommitted changes. */
+static void
+dconf_safe_writer_change (DConfWriter    *writer,
+                          DConfChangeset *changeset,
+                          const gchar    *tag)
+{
+  DConfSafeWriter *sw = (DConfSafeWriter *) writer;
+  DConfChangeset *effective_changeset = dconf_changeset_filter_changes 
(sw->uncommitted_values, changeset);
+
+  DCONF_WRITER_CLASS (dconf_safe_writer_parent_class)->change (writer, 
changeset, tag);
+
+  if (effective_changeset != NULL)
+    {
+      dconf_changeset_unref (effective_changeset);
+      dconf_changeset_change (sw->uncommitted_values, changeset);
+    }
+}
+
+static gboolean
+dconf_safe_writer_rollback (DConfSafeWriter  *sw,
+                            GError          **error)
+{
+  if (sw->committed_values_missing)
+    {
+      if (g_unlink (sw->filename) != 0 && errno != ENOENT)
+        {
+          gint saved_errno = errno;
+
+          g_set_error (error, G_FILE_ERROR, g_file_error_from_errno 
(saved_errno),
+                       "%s: %s", sw->filename, g_strerror (saved_errno));
+          return FALSE;
+        }
+
+      return TRUE;
+    }
+
+  return dconf_gvdb_utils_write_file (sw->filename, sw->committed_values, 
error);
+}
+
+/* Store the uncommitted changes to the GVDB files. */
+static gboolean
+dconf_safe_writer_commit (DConfWriter  *writer,
+                          GError      **error)
+{
+  DConfSafeWriter *sw = (DConfSafeWriter *) writer;
+  g_autoptr(DConfChangeset) effective_changeset = NULL;
+  gboolean wrote_user_database = FALSE;
+
+  effective_changeset = dconf_changeset_diff (sw->committed_values, 
sw->uncommitted_values);
+
+  /* Save the changes to the file in user config directory. */
+  if (effective_changeset != NULL)
+    {
+      if (!dconf_gvdb_utils_write_file (sw->filename, sw->uncommitted_values, 
error))
+        return FALSE;
+
+      wrote_user_database = TRUE;
+    }
+
+  /* The runtime database update is the visible commit point for this process:
+   * the parent writer only emits notifications after this succeeds. If it
+   * fails, undo the user database write so the failed operation is not
+   * persisted on disk.
+   */
+  if (!DCONF_WRITER_CLASS (dconf_safe_writer_parent_class)->commit (writer, 
error))
+    {
+      if (wrote_user_database)
+        {
+          g_autoptr(GError) rollback_error = NULL;
+
+          if (!dconf_safe_writer_rollback (sw, &rollback_error))
+            g_warning ("Error rolling back database %s after failed write: %s",
+                       sw->filename, rollback_error->message);
+        }
+
+      return FALSE;
+    }
+
+  g_clear_pointer (&sw->committed_values, dconf_changeset_unref);
+  sw->committed_values = sw->uncommitted_values;
+  sw->uncommitted_values = NULL;
+  if (wrote_user_database)
+    sw->committed_values_missing = FALSE;
+
+  return TRUE;
+}
+
+static void
+dconf_safe_writer_end (DConfWriter *writer)
+{
+  DConfSafeWriter *sw = (DConfSafeWriter *) writer;
+
+  DCONF_WRITER_CLASS (dconf_safe_writer_parent_class)->end (writer);
+
+  g_clear_pointer (&sw->uncommitted_values, dconf_changeset_unref);
+  close (sw->lock_fd);
+  sw->lock_fd = -1;
+}
+
+static gboolean
+dconf_safe_update (gpointer user_data)
+{
+  DConfSafeWriter *sw = user_data;
+
+  if (dconf_safe_writer_begin (DCONF_WRITER (sw), NULL))
+    dconf_safe_writer_commit (DCONF_WRITER (sw), NULL);
+  dconf_safe_writer_end (DCONF_WRITER (sw));
+
+  sw->scheduled_update = 0;
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+dconf_safe_writer_finalize (GObject *object)
+{
+  DConfSafeWriter *sw = (DConfSafeWriter *) object;
+
+  if (sw->scheduled_update)
+    g_source_remove (sw->scheduled_update);
+
+  g_clear_object (&sw->monitor);
+  g_free (sw->lock_filename);
+  if (sw->lock_fd >= 0)
+    close (sw->lock_fd);
+  g_free (sw->filename);
+  g_clear_pointer (&sw->committed_values, dconf_changeset_unref);
+
+  G_OBJECT_CLASS (dconf_safe_writer_parent_class)->finalize (object);
+}
+
+static void
+dconf_safe_writer_init (DConfSafeWriter *sw)
+{
+  dconf_writer_set_basepath (DCONF_WRITER (sw), "safe");
+
+  sw->lock_fd = -1;
+}
+
+static void
+dconf_safe_writer_class_init (DConfWriterClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = dconf_safe_writer_finalize;
+
+  class->begin = dconf_safe_writer_begin;
+  class->change = dconf_safe_writer_change;
+  class->commit = dconf_safe_writer_commit;
+  class->end = dconf_safe_writer_end;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/service/dconf-service.c 
new/dconf-51.0/service/dconf-service.c
--- old/dconf-0.49.0/service/dconf-service.c    2025-09-15 17:53:57.000000000 
+0200
+++ new/dconf-51.0/service/dconf-service.c      2026-09-14 17:37:59.000000000 
+0200
@@ -238,6 +238,7 @@
   g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_WRITER, 
"Writer", 0);
   g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_KEYFILE_WRITER, 
"keyfile", 0);
   g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_SHM_WRITER, 
"shm", 0);
+  g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_SAFE_WRITER, 
"safe", 0);
 
   service->blame = dconf_blame_get ();
   if (service->blame)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/service/dconf-writer-common.c 
new/dconf-51.0/service/dconf-writer-common.c
--- old/dconf-0.49.0/service/dconf-writer-common.c      1970-01-01 
01:00:00.000000000 +0100
+++ new/dconf-51.0/service/dconf-writer-common.c        2026-09-14 
17:37:59.000000000 +0200
@@ -0,0 +1,83 @@
+/*
+ * 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
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * 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
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include "dconf-writer-common.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gio/gio.h>
+
+gint
+dconf_lock_file_lock (const gchar  *lock_filename,
+                      GError      **error)
+{
+  gint lock_fd;
+
+  lock_fd = open (lock_filename, O_RDWR | O_CREAT, 0666);
+  if (lock_fd == -1)
+    {
+      gchar *dirname;
+
+      /* Maybe it failed because the directory doesn't exist.  Try
+       * again, after mkdir().
+       */
+      dirname = g_path_get_dirname (lock_filename);
+      g_mkdir_with_parents (dirname, 0700);
+      g_free (dirname);
+
+      lock_fd = open (lock_filename, O_RDWR | O_CREAT, 0666);
+      if (lock_fd == -1)
+        {
+          gint saved_errno = errno;
+
+          g_set_error (error, G_FILE_ERROR, g_file_error_from_errno 
(saved_errno),
+                       "%s: %s", lock_filename, g_strerror (saved_errno));
+
+          return lock_fd;
+        }
+    }
+
+  while (TRUE)
+    {
+      struct flock lock = { 0 };
+
+      lock.l_type = F_WRLCK;
+      lock.l_whence = 0;
+      lock.l_start = 0;
+      lock.l_len = 0; /* lock all bytes */
+
+      if (fcntl (lock_fd, F_SETLKW, &lock) == 0)
+        break;
+
+      if (errno != EINTR)
+        {
+          gint saved_errno = errno;
+
+          g_set_error (error, G_FILE_ERROR, g_file_error_from_errno 
(saved_errno),
+                       "%s: unable to fcntl(F_SETLKW): %s", lock_filename, 
g_strerror (saved_errno));
+          close (lock_fd);
+          lock_fd = -1;
+          return lock_fd;
+        }
+
+      /* it was EINTR.  loop again. */
+    }
+
+  return lock_fd;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/service/dconf-writer-common.h 
new/dconf-51.0/service/dconf-writer-common.h
--- old/dconf-0.49.0/service/dconf-writer-common.h      1970-01-01 
01:00:00.000000000 +0100
+++ new/dconf-51.0/service/dconf-writer-common.h        2026-09-14 
17:37:59.000000000 +0200
@@ -0,0 +1,26 @@
+/*
+ * 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
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * 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
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __dconf_writer_common_h__
+#define __dconf_writer_common_h__
+
+#include <glib.h>
+
+gint
+dconf_lock_file_lock (const gchar  *lock_filename,
+                      GError      **error);
+
+#endif /* __dconf_writer_common_h__ */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/service/dconf-writer.h 
new/dconf-51.0/service/dconf-writer.h
--- old/dconf-0.49.0/service/dconf-writer.h     2025-09-15 17:53:57.000000000 
+0200
+++ new/dconf-51.0/service/dconf-writer.h       2026-09-14 17:37:59.000000000 
+0200
@@ -86,5 +86,7 @@
 GType                   dconf_shm_writer_get_type                       (void);
 #define DCONF_TYPE_KEYFILE_WRITER                           
(dconf_keyfile_writer_get_type ())
 GType                   dconf_keyfile_writer_get_type                   (void);
+#define DCONF_TYPE_SAFE_WRITER                              
(dconf_safe_writer_get_type ())
+GType                   dconf_safe_writer_get_type                      (void);
 
 #endif /* __dconf_writer_h__ */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/service/meson.build 
new/dconf-51.0/service/meson.build
--- old/dconf-0.49.0/service/meson.build        2025-09-15 17:53:57.000000000 
+0200
+++ new/dconf-51.0/service/meson.build  2026-09-14 17:37:59.000000000 +0200
@@ -22,9 +22,11 @@
 lib_sources = [
   'dconf-blame.c',
   'dconf-keyfile-writer.c',
+  'dconf-safe-writer.c',
   'dconf-service.c',
   'dconf-shm-writer.c',
   'dconf-writer.c',
+  'dconf-writer-common.c',
 ]
 sources = [
   'main.c',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/tests/meson.build 
new/dconf-51.0/tests/meson.build
--- old/dconf-0.49.0/tests/meson.build  2025-09-15 17:53:57.000000000 +0200
+++ new/dconf-51.0/tests/meson.build    2026-09-14 17:37:59.000000000 +0200
@@ -10,6 +10,12 @@
   dependencies: [glib_dep, libgvdb_dep],
 )
 
+libdconf_mock_no_gvdb = static_library(
+  'dconf-mock-no-gvdb',
+  sources: files('dconf-mock-dbus.c', 'dconf-mock-shm.c'),
+  dependencies: [glib_dep, libgvdb_dep],
+)
+
 envs = test_env + [
   'G_TEST_SRCDIR=' + meson.current_source_dir(),
   'G_TEST_BUILDDIR=' + meson.current_build_dir(),
@@ -32,7 +38,7 @@
   ['gdbus-filter-leak', 'dbus-leak.c', '-DDBUS_BACKEND="/gdbus/filter"', 
[libdconf_client_dep, libdconf_gdbus_filter_dep], []],
   ['engine', 'engine.c', '-DSRCDIR="@0@"'.format(test_dir), [dl_dep, 
libdconf_engine_test_dep, m_dep], libdconf_mock],
   ['client', 'client.c', '-DSRCDIR="@0@"'.format(test_dir), 
[libdconf_client_dep, libdconf_engine_dep], libdconf_mock],
-  ['writer', 'writer.c', '-DSRCDIR="@0@"'.format(test_dir), [glib_dep, dl_dep, 
m_dep, libdconf_service_dep], [libdconf_mock]],
+  ['writer', 'writer.c', '-DSRCDIR="@0@"'.format(test_dir), [glib_dep, dl_dep, 
m_dep, libdconf_service_dep], [libdconf_mock_no_gvdb]],
 ]
 
 foreach unit_test: unit_tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dconf-0.49.0/tests/writer.c 
new/dconf-51.0/tests/writer.c
--- old/dconf-0.49.0/tests/writer.c     2025-09-15 17:53:57.000000000 +0200
+++ new/dconf-51.0/tests/writer.c       2026-09-14 17:37:59.000000000 +0200
@@ -23,8 +23,10 @@
 
 #include "service/dconf-generated.h"
 #include "service/dconf-writer.h"
+#include "common/dconf-gvdb-utils.h"
 
 static guint n_warnings = 0;
+static gchar *runtime_dir = NULL;
 
 static GLogWriterOutput
 log_writer_cb (GLogLevelFlags   log_level,
@@ -378,6 +380,141 @@
   g_assert_cmpint (g_unlink (db_filename), ==, 0);
 }
 
+static gchar *
+safe_runtime_db_filename (const gchar *db_name)
+{
+  return g_build_filename (runtime_dir, "dconf-service", "safe", db_name, 
NULL);
+}
+
+static DConfChangeset *
+read_db (const gchar *filename)
+{
+  g_autoptr(GError) error = NULL;
+  gboolean missing = FALSE;
+  DConfChangeset *db;
+
+  db = dconf_gvdb_utils_read_and_back_up_file (filename, &missing, &error);
+  g_assert_no_error (error);
+  g_assert_false (missing);
+  g_assert_nonnull (db);
+
+  return db;
+}
+
+static void
+test_safe_writer_persists_add_and_delete (Fixture       *fixture,
+                                          gconstpointer  test_data)
+{
+  const gchar *db_name = "safe-add-delete";
+  g_autoptr(DConfWriter) writer = NULL;
+  DConfWriterClass *writer_class;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *db_filename = g_build_filename (fixture->dconf_dir, 
db_name, NULL);
+
+  writer = DCONF_WRITER (dconf_writer_new (DCONF_TYPE_SAFE_WRITER, db_name));
+  writer_class = DCONF_WRITER_GET_CLASS (writer);
+
+  g_assert_true (writer_class->begin (writer, &error));
+  g_assert_no_error (error);
+
+  g_autoptr(DConfChangeset) add = dconf_changeset_new ();
+  dconf_changeset_set (add, "/key", g_variant_new ("(s)", "value"));
+  writer_class->change (writer, add, NULL);
+
+  g_assert_true (writer_class->commit (writer, &error));
+  g_assert_no_error (error);
+  writer_class->end (writer);
+
+  {
+    g_autoptr(DConfChangeset) db = read_db (db_filename);
+    g_autoptr(GVariant) value = NULL;
+
+    g_assert_true (dconf_changeset_get (db, "/key", &value));
+    g_assert_true (g_variant_equal (value, g_variant_new ("(s)", "value")));
+  }
+
+  g_assert_true (writer_class->begin (writer, &error));
+  g_assert_no_error (error);
+
+  g_autoptr(DConfChangeset) reset = dconf_changeset_new ();
+  dconf_changeset_set (reset, "/key", NULL);
+  writer_class->change (writer, reset, NULL);
+
+  g_assert_true (writer_class->commit (writer, &error));
+  g_assert_no_error (error);
+  writer_class->end (writer);
+
+  {
+    g_autoptr(DConfChangeset) db = read_db (db_filename);
+    g_assert_false (dconf_changeset_get (db, "/key", NULL));
+  }
+
+  g_unlink (db_filename);
+  g_autofree gchar *lock = g_build_filename (fixture->dconf_dir, 
".safe-add-delete.lock", NULL);
+  g_unlink (lock);
+  g_autofree gchar *rt = safe_runtime_db_filename (db_name);
+  g_unlink (rt);
+  g_autofree gchar *rt_safe_dir = g_build_filename (runtime_dir, 
"dconf-service", "safe", NULL);
+  g_rmdir (rt_safe_dir);
+  g_autofree gchar *rt_service_dir = g_build_filename (runtime_dir, 
"dconf-service", NULL);
+  g_rmdir (rt_service_dir);
+}
+
+static void
+test_safe_writer_rolls_back_persistent_db_on_runtime_commit_failure (Fixture   
    *fixture,
+                                                                     
gconstpointer  test_data)
+{
+  const gchar *db_name = "safe-rollback";
+  g_autoptr(DConfWriter) writer = NULL;
+  DConfWriterClass *writer_class;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *db_filename = g_build_filename (fixture->dconf_dir, 
db_name, NULL);
+  g_autofree gchar *runtime_db_filename = safe_runtime_db_filename (db_name);
+
+  g_autoptr(DConfChangeset) old_db = dconf_changeset_new_database (NULL);
+  dconf_changeset_set (old_db, "/key", g_variant_new ("(s)", "old"));
+  g_assert_true (dconf_gvdb_utils_write_file (db_filename, old_db, &error));
+  g_assert_no_error (error);
+
+  writer = DCONF_WRITER (dconf_writer_new (DCONF_TYPE_SAFE_WRITER, db_name));
+  writer_class = DCONF_WRITER_GET_CLASS (writer);
+
+  g_assert_true (writer_class->begin (writer, &error));
+  g_assert_no_error (error);
+
+  g_autoptr(DConfChangeset) change = dconf_changeset_new ();
+  dconf_changeset_set (change, "/key", g_variant_new ("(s)", "new"));
+  writer_class->change (writer, change, NULL);
+
+  /* Make the runtime DB path not writable as a file target after begin(),
+   * so the persistent write succeeds and the parent/runtime commit fails.
+   */
+  g_assert_cmpint (g_mkdir_with_parents (runtime_db_filename, 0700), ==, 0);
+
+  g_assert_false (writer_class->commit (writer, &error));
+  g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ISDIR);
+  g_clear_error (&error);
+
+  writer_class->end (writer);
+
+  {
+    g_autoptr(DConfChangeset) db = read_db (db_filename);
+    g_autoptr(GVariant) value = NULL;
+
+    g_assert_true (dconf_changeset_get (db, "/key", &value));
+    g_assert_true (g_variant_equal (value, g_variant_new ("(s)", "old")));
+  }
+
+  g_unlink (db_filename);
+  g_autofree gchar *lock = g_build_filename (fixture->dconf_dir, 
".safe-rollback.lock", NULL);
+  g_unlink (lock);
+  g_rmdir (runtime_db_filename);
+  g_autofree gchar *rt_safe_dir = g_build_filename (runtime_dir, 
"dconf-service", "safe", NULL);
+  g_rmdir (rt_safe_dir);
+  g_autofree gchar *rt_service_dir = g_build_filename (runtime_dir, 
"dconf-service", NULL);
+  g_rmdir (rt_service_dir);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -405,9 +542,13 @@
   g_assert_true (g_setenv ("XDG_CONFIG_HOME", config_dir, TRUE));
   g_test_message ("Using config directory: %s", config_dir);
 
+  runtime_dir = g_dir_make_tmp ("dconf-test-writer-runtime_XXXXXX", 
&local_error);
+  g_assert_no_error (local_error);
+  g_assert_true (g_setenv ("XDG_RUNTIME_DIR", runtime_dir, TRUE));
+  g_test_message ("Using runtime directory: %s", runtime_dir);
+
   /* Log handling so we don’t abort on the first g_warning(). */
   g_log_set_writer_func (log_writer_cb, NULL, NULL);
-
   g_test_add ("/writer/basic", Fixture, NULL, set_up,
               test_writer_basic, tear_down);
   g_test_add ("/writer/begin/missing", Fixture, NULL, set_up,
@@ -426,6 +567,10 @@
               test_writer_commit_empty_changes, tear_down);
   g_test_add ("/writer/commit/redundant_change/2", Fixture, NULL, set_up,
               test_writer_commit_real_changes, tear_down);
+  g_test_add ("/safe-writer/commit/persistence", Fixture, NULL, set_up,
+              test_safe_writer_persists_add_and_delete, tear_down);
+  g_test_add ("/safe-writer/commit/rollback", Fixture, NULL, set_up,
+              
test_safe_writer_rolls_back_persistent_db_on_runtime_commit_failure, tear_down);
 
   retval = g_test_run ();
 
@@ -434,5 +579,10 @@
   g_assert_cmpint (g_rmdir (config_dir), ==, 0);
   g_clear_pointer (&config_dir, g_free);
 
+  /* Clean up the runtime dir. */
+  g_unsetenv ("XDG_RUNTIME_DIR");
+  g_assert_cmpint (g_rmdir (runtime_dir), ==, 0);
+  g_clear_pointer (&runtime_dir, g_free);
+
   return retval;
 }

++++++ dconf.obsinfo ++++++
name: dconf
version: 51.0
mtime: 1789400279
commit: 264e6598863fc1d9ad6edb4c58d86f82ea669927

Reply via email to