[ostree] sysroot: Write symlinks before calling fsync(), then rename after



commit 723705b803480dfd9c4090000ffc69fdafbb5c82
Author: Colin Walters <walters verbum org>
Date:   Mon Oct 12 21:33:23 2015 -0400

    sysroot: Write symlinks before calling fsync(), then rename after
    
    There might be a race here in that we create new symlink files *after*
    calling `syncfs`, and they are not guaranteed to end up on disk.
    
    Rework the code so that we create symlinks before, and then only
    rename them after (and `fsync()` the directory for good measure).
    
    Additional-fixes-by: Giuseppe Scrivano <gscrivan redhat com>
    Tested-by: Giuseppe Scrivano <gscrivan redhat com>
    
    This still needs verification that we're fixing a real bug; but I'm
    fairly confident this won't make the fsync situation worse.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=755595

 src/libostree/ostree-sysroot-deploy.c |  236 +++++++++++++++++++++++++++-----
 src/libotutil/ot-gio-utils.c          |   56 --------
 src/libotutil/ot-gio-utils.h          |    5 -
 3 files changed, 199 insertions(+), 98 deletions(-)
---
diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
index 3aeee1b..f7afe3d 100644
--- a/src/libostree/ostree-sysroot-deploy.c
+++ b/src/libostree/ostree-sysroot-deploy.c
@@ -33,6 +33,53 @@
 #define OSTREE_CONFIGMERGE_ID         "d3863baec13e4449ab0384684a8af3a7"
 #define OSTREE_DEPLOYMENT_COMPLETE_ID "dd440e3e549083b63d0efc7dc15255f1"
 
+/*
+ * Like symlinkat() but overwrites (atomically) an existing
+ * symlink.
+ */
+static gboolean
+symlink_at_replace (const char    *oldpath,
+                    int            parent_dfd,
+                    const char    *newpath,
+                    GCancellable  *cancellable,
+                    GError       **error)
+{
+  gboolean ret = FALSE;
+  int res;
+  /* Possibly in the future generate a temporary random name here,
+   * would need to move "generate a temporary name" code into
+   * libglnx or glib?
+   */
+  g_autofree char *temppath = g_strconcat (newpath, ".tmp", NULL);
+
+  /* Clean up any stale temporary links */ 
+  (void) unlinkat (parent_dfd, temppath, 0);
+
+  /* Create the temp link */ 
+  do
+    res = symlinkat (oldpath, parent_dfd, temppath);
+  while (G_UNLIKELY (res == -1 && errno == EINTR));
+  if (res == -1)
+    {
+      glnx_set_error_from_errno (error);
+      goto out;
+    }
+
+  /* Rename it into place */ 
+  do
+    res = renameat (parent_dfd, temppath, parent_dfd, newpath);
+  while (G_UNLIKELY (res == -1 && errno == EINTR));
+  if (res == -1)
+    {
+      glnx_set_error_from_errno (error);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
 static gboolean
 dirfd_copy_attributes_and_xattrs (int            src_parent_dfd,
                                   const char    *src_name,
@@ -1046,21 +1093,25 @@ full_system_sync (OstreeSysroot     *self,
 }
 
 static gboolean
-swap_bootlinks (OstreeSysroot *self,
-                int            bootversion,
-                GPtrArray    *new_deployments,
-                GCancellable *cancellable,
-                GError      **error)
+create_new_bootlinks (OstreeSysroot *self,
+                      int            bootversion,
+                      GPtrArray     *new_deployments,
+                       GCancellable  *cancellable,
+                      GError       **error)
 {
   gboolean ret = FALSE;
   guint i;
   int old_subbootversion;
   int new_subbootversion;
-  g_autoptr(GFile) ostree_dir = g_file_get_child (self->path, "ostree");
-  g_autofree char *ostree_bootdir_name = g_strdup_printf ("boot.%d", bootversion);
-  g_autoptr(GFile) ostree_bootdir = g_file_resolve_relative_path (ostree_dir, ostree_bootdir_name);
+  glnx_fd_close int ostree_dfd = -1;
+  glnx_fd_close int ostree_subbootdir_dfd = -1;
+  g_autofree char *ostree_bootdir_name = NULL;
   g_autofree char *ostree_subbootdir_name = NULL;
-  g_autoptr(GFile) ostree_subbootdir = NULL;
+
+  if (!glnx_opendirat (self->sysroot_fd, "ostree", TRUE, &ostree_dfd, error))
+    goto out;
+
+  ostree_bootdir_name = g_strdup_printf ("boot.%d", bootversion);
 
   if (bootversion != self->bootversion)
     {
@@ -1073,40 +1124,79 @@ swap_bootlinks (OstreeSysroot *self,
 
   new_subbootversion = old_subbootversion == 0 ? 1 : 0;
 
+  /* Create the "subbootdir", which is a directory holding a symlink farm pointing to
+   * deployments per-osname.
+   */
   ostree_subbootdir_name = g_strdup_printf ("boot.%d.%d", bootversion, new_subbootversion);
-  ostree_subbootdir = g_file_resolve_relative_path (ostree_dir, ostree_subbootdir_name);
-
-  if (!gs_shutil_rm_rf (ostree_subbootdir, cancellable, error))
+  if (!glnx_shutil_rm_rf_at (ostree_dfd, ostree_subbootdir_name, cancellable, error))
     goto out;
-
-  if (!ot_util_ensure_directory_and_fsync (ostree_subbootdir, cancellable, error))
+  if (!glnx_shutil_mkdir_p_at (ostree_dfd, ostree_subbootdir_name, 0755, cancellable, error))
+    goto out;
+  if (!glnx_opendirat (ostree_dfd, ostree_subbootdir_name, FALSE, &ostree_subbootdir_dfd, error))
     goto out;
 
   for (i = 0; i < new_deployments->len; i++)
     {
       OstreeDeployment *deployment = new_deployments->pdata[i];
-      g_autofree char *bootlink_pathname = g_strdup_printf ("%s/%s/%d",
-                                                         ostree_deployment_get_osname (deployment),
-                                                         ostree_deployment_get_bootcsum (deployment),
-                                                         ostree_deployment_get_bootserial (deployment));
+      g_autofree char *bootlink_parent = g_strconcat (ostree_deployment_get_osname (deployment),
+                                                      "/",
+                                                      ostree_deployment_get_bootcsum (deployment),
+                                                      NULL);
+      g_autofree char *bootlink_pathname = g_strdup_printf ("%s/%d", bootlink_parent, 
ostree_deployment_get_bootserial (deployment));
       g_autofree char *bootlink_target = g_strdup_printf ("../../../deploy/%s/deploy/%s.%d",
-                                                       ostree_deployment_get_osname (deployment),
-                                                       ostree_deployment_get_csum (deployment),
-                                                       ostree_deployment_get_deployserial (deployment));
-      g_autoptr(GFile) linkname = g_file_get_child (ostree_subbootdir, bootlink_pathname);
-      g_autoptr(GFile) linkname_parent = g_file_get_parent (linkname);
+                                                          ostree_deployment_get_osname (deployment),
+                                                          ostree_deployment_get_csum (deployment),
+                                                          ostree_deployment_get_deployserial (deployment));
 
-      if (!ot_util_ensure_directory_and_fsync (linkname_parent, cancellable, error))
+      if (!glnx_shutil_mkdir_p_at (ostree_subbootdir_dfd, bootlink_parent, 0755, cancellable, error))
         goto out;
-      
-      if (!g_file_make_symbolic_link (linkname, bootlink_target, cancellable, error))
+
+      if (!symlink_at_replace (bootlink_target, ostree_subbootdir_dfd, bootlink_pathname,
+                               cancellable, error))
         goto out;
     }
 
-  if (!ot_gfile_atomic_symlink_swap (ostree_bootdir, ostree_subbootdir_name,
-                                     cancellable, error))
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+swap_bootlinks (OstreeSysroot *self,
+                int            bootversion,
+                GPtrArray     *new_deployments,
+                GCancellable  *cancellable,
+                GError       **error)
+{
+  gboolean ret = FALSE;
+  int old_subbootversion, new_subbootversion;
+  glnx_fd_close int ostree_dfd = -1;
+  glnx_fd_close int ostree_subbootdir_dfd = -1;
+  g_autofree char *ostree_bootdir_name = NULL;
+  g_autofree char *ostree_subbootdir_name = NULL;
+
+  if (!glnx_opendirat (self->sysroot_fd, "ostree", TRUE, &ostree_dfd, error))
     goto out;
 
+  ostree_bootdir_name = g_strdup_printf ("boot.%d", bootversion);
+
+  if (bootversion != self->bootversion)
+    {
+      if (!_ostree_sysroot_read_current_subbootversion (self, bootversion, &old_subbootversion,
+                                                        cancellable, error))
+        goto out;
+    }
+  else
+    old_subbootversion = self->subbootversion;
+
+  new_subbootversion = old_subbootversion == 0 ? 1 : 0;
+
+  ostree_subbootdir_name = g_strdup_printf ("boot.%d.%d", bootversion, new_subbootversion);
+
+  if (!symlink_at_replace (ostree_subbootdir_name, ostree_dfd, ostree_bootdir_name,
+                           cancellable, error))
+    goto out;
+  
   ret = TRUE;
  out:
   return ret;
@@ -1375,6 +1465,32 @@ install_deployment_kernel (OstreeSysroot   *sysroot,
 }
 
 static gboolean
+prepare_new_bootloader_link (OstreeSysroot  *sysroot,
+                             int             current_bootversion,
+                             int             new_bootversion,
+                             GCancellable   *cancellable,
+                             GError        **error)
+{
+  gboolean ret = FALSE;
+  g_autofree char *new_target = NULL;
+
+  g_assert ((current_bootversion == 0 && new_bootversion == 1) ||
+            (current_bootversion == 1 && new_bootversion == 0));
+
+  new_target = g_strdup_printf ("loader.%d", new_bootversion);
+
+  /* We shouldn't actually need to replace but it's easier to reuse
+     that code */
+  if (!symlink_at_replace (new_target, sysroot->sysroot_fd, "boot/loader.tmp",
+                           cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
 swap_bootloader (OstreeSysroot  *sysroot,
                  int             current_bootversion,
                  int             new_bootversion,
@@ -1382,19 +1498,43 @@ swap_bootloader (OstreeSysroot  *sysroot,
                  GError        **error)
 {
   gboolean ret = FALSE;
-  g_autoptr(GFile) boot_loader_link = NULL;
-  g_autofree char *new_target = NULL;
+  glnx_fd_close int boot_dfd = -1;
+  int res;
 
   g_assert ((current_bootversion == 0 && new_bootversion == 1) ||
             (current_bootversion == 1 && new_bootversion == 0));
 
-  boot_loader_link = g_file_resolve_relative_path (sysroot->path, "boot/loader");
-  new_target = g_strdup_printf ("loader.%d", new_bootversion);
-
-  if (!ot_gfile_atomic_symlink_swap (boot_loader_link, new_target,
-                                     cancellable, error))
+  if (!glnx_opendirat (sysroot->sysroot_fd, "boot", TRUE, &boot_dfd, error))
     goto out;
 
+  /* The symlink was already written, and we used syncfs() to ensure
+   * its data is in place.  Renaming now should give us atomic semantics;
+   * see https://bugzilla.gnome.org/show_bug.cgi?id=755595
+   */
+  do
+    res = renameat (boot_dfd, "loader.tmp", boot_dfd, "loader");
+  while (G_UNLIKELY (res == -1 && errno == EINTR));
+  if (res == -1)
+    {
+      glnx_set_error_from_errno (error);
+      goto out;
+    }
+
+  /* Now we explicitly fsync this directory, even though it
+   * isn't required for atomicity, for two reasons:
+   *  - It should be very cheap as we're just syncing whatever
+   *    data was written since the last sync which was hopefully
+   *    less than a second ago.
+   *  - It should be sync'd before shutdown as that could crash
+   *    for whatever reason, and we wouldn't want to confuse the
+   *    admin by going back to the previous session.
+   */
+  if (fsync (boot_dfd) != 0)
+    {
+      glnx_set_error_from_errno (error);
+      goto out;
+    }
+
   ret = TRUE;
  out:
   return ret;
@@ -1565,6 +1705,14 @@ ostree_sysroot_write_deployments (OstreeSysroot     *self,
 
   if (!requires_new_bootversion)
     {
+      if (!create_new_bootlinks (self, self->bootversion,
+                                 new_deployments,
+                                 cancellable, error))
+        {
+          g_prefix_error (error, "Creating new current bootlinks: ");
+          goto out;
+        }
+      
       if (!full_system_sync (self, cancellable, error))
         {
           g_prefix_error (error, "Full sync: ");
@@ -1633,11 +1781,18 @@ ostree_sysroot_write_deployments (OstreeSysroot     *self,
             }
         }
 
-      /* Swap bootlinks for *new* version */
+      /* Create and swap bootlinks for *new* version */
+      if (!create_new_bootlinks (self, new_bootversion,
+                                 new_deployments,
+                                 cancellable, error))
+        {
+          g_prefix_error (error, "Creating new version bootlinks: ");
+          goto out;
+        }
       if (!swap_bootlinks (self, new_bootversion, new_deployments,
                            cancellable, error))
         {
-          g_prefix_error (error, "Generating new bootlinks: ");
+          g_prefix_error (error, "Swapping new version bootlinks: ");
           goto out;
         }
 
@@ -1657,6 +1812,13 @@ ostree_sysroot_write_deployments (OstreeSysroot     *self,
             }
         }
 
+      if (!prepare_new_bootloader_link (self, self->bootversion, new_bootversion,
+                                        cancellable, error))
+        {
+          g_prefix_error (error, "Preparing final bootloader swap: ");
+          goto out;
+        }
+
       if (!full_system_sync (self, cancellable, error))
         {
           g_prefix_error (error, "Full sync: ");
diff --git a/src/libotutil/ot-gio-utils.c b/src/libotutil/ot-gio-utils.c
index 1143ca9..b10c04c 100644
--- a/src/libotutil/ot-gio-utils.c
+++ b/src/libotutil/ot-gio-utils.c
@@ -508,59 +508,3 @@ ot_util_ensure_directory_and_fsync (GFile         *dir,
     (void) close (parentfd);
   return ret;
 }
-
-/**
- * ot_gfile_atomic_symlink_swap:
- * @path: Replace the contents of this symbolic link
- * @target: New symbolic link target
- * @cancellable:
- * @error
- *
- * Create a new temporary symbolic link, then use the Unix rename()
- * function to atomically replace @path with the new symbolic link.
- * Do not use this function inside directories such as /tmp as it uses
- * a predicatable file name.
- */
-gboolean
-ot_gfile_atomic_symlink_swap (GFile          *path,
-                              const char     *target,
-                              GCancellable   *cancellable,
-                              GError        **error)
-{
-  gboolean ret = FALSE;
-  g_autoptr(GFile) parent = g_file_get_parent (path);
-  g_autofree char *tmpname = g_strconcat (gs_file_get_basename_cached (path), ".tmp", NULL);
-  g_autoptr(GFile) tmppath = g_file_get_child (parent, tmpname);
-  int parent_dfd = -1;
-
-  if (!ot_gfile_ensure_unlinked (tmppath, cancellable, error))
-    goto out;
-  
-  if (!g_file_make_symbolic_link (tmppath, target, cancellable, error))
-    goto out;
-
-  if (!gs_file_open_dir_fd (parent, &parent_dfd, cancellable, error))
-    goto out;
-
-  /* Ensure the link has hit disk */
-  if (fsync (parent_dfd) != 0)
-    {
-      gs_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  if (!gs_file_rename (tmppath, path, cancellable, error))
-    goto out;
-
-  /* And sync again for good measure */
-  if (fsync (parent_dfd) != 0)
-    {
-      gs_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  ret = TRUE;
- out:
-  if (parent_dfd != -1) (void) close (parent_dfd);
-  return ret;
-}
diff --git a/src/libotutil/ot-gio-utils.h b/src/libotutil/ot-gio-utils.h
index bd0ef5c..355073a 100644
--- a/src/libotutil/ot-gio-utils.h
+++ b/src/libotutil/ot-gio-utils.h
@@ -85,11 +85,6 @@ gboolean ot_gfile_ensure_unlinked (GFile         *path,
                                    GCancellable  *cancellable,
                                    GError       **error);
 
-gboolean ot_gfile_atomic_symlink_swap (GFile          *path,
-                                       const char     *target,
-                                       GCancellable   *cancellable,
-                                       GError        **error);
-
 gboolean ot_util_ensure_directory_and_fsync (GFile         *dir,
                                              GCancellable  *cancellable,
                                              GError       **error);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]