repo: Add _ostree_repo_allocate_tmpdir helper
authorAlexander Larsson <alexl@redhat.com>
Fri, 11 Dec 2015 17:43:05 +0000 (18:43 +0100)
committerAlexander Larsson <alexl@redhat.com>
Mon, 14 Dec 2015 07:37:55 +0000 (08:37 +0100)
This creates a subdirectory of the tmp dir with a selected prefix,
and takes a lockfile to ensure that nobody else is using the same directory.
However, if a directory with the same prefix already exists and is
not locked that is used instead.

The later is useful if you want to support some kind of resumed operation
on the tmpdir.

touch reused dirs

https://bugzilla.gnome.org/show_bug.cgi?id=757611

src/libostree/ostree-repo-private.h
src/libostree/ostree-repo.c

index ccb648f8b4ebbd472352c57bf672ab8d6e535623..8f57f95b2545b381fc306c08471a4030c53c33e3 100644 (file)
@@ -90,6 +90,16 @@ struct OstreeRepo {
   OstreeRepo *parent_repo;
 };
 
+gboolean
+_ostree_repo_allocate_tmpdir (int           tmpdir_dfd,
+                              const char   *tmpdir_prefix,
+                              char        **tmpdir_name_out,
+                              int          *tmpdir_fd_out,
+                              GLnxLockFile *file_lock_out,
+                              gboolean *    reusing_dir_out,
+                              GCancellable *cancellable,
+                              GError      **error);
+
 gboolean
 _ostree_repo_ensure_loose_objdir_at (int             dfd,
                                      const char     *loose_path,
index 1f1cba29efaf5625dc233ddcbec1424a0c9f4cce..bc03719a6b5942b953dda2b2641b6c3bb19ff005 100644 (file)
@@ -4617,3 +4617,134 @@ ostree_repo_regenerate_summary (OstreeRepo     *self,
     g_list_free (ordered_keys);
   return ret;
 }
+
+
+/* This allocates and locks a subdir of the repo tmp dir, using an existing
+ * one with the same prefix if it is not in use already. */
+gboolean
+_ostree_repo_allocate_tmpdir (int tmpdir_dfd,
+                              const char *tmpdir_prefix,
+                              char **tmpdir_name_out,
+                              int *tmpdir_fd_out,
+                              GLnxLockFile *file_lock_out,
+                              gboolean *reusing_dir_out,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+  gboolean reusing_dir = FALSE;
+  g_autofree char *tmpdir_name = NULL;
+  glnx_fd_close int tmpdir_fd = -1;
+  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
+
+  /* Look for existing tmpdir (with same prefix) to reuse */
+  if (!glnx_dirfd_iterator_init_at (tmpdir_dfd, ".", FALSE, &dfd_iter, error))
+    return FALSE;
+
+  while (TRUE)
+    {
+      gs_dirfd_iterator_cleanup GSDirFdIterator child_dfd_iter = { 0, };
+      struct dirent *dent;
+      glnx_fd_close int existing_tmpdir_fd = -1;
+      g_autoptr(GError) local_error = NULL;
+      g_autofree char *lock_name = NULL;
+
+      if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
+        return FALSE;
+
+      if (dent == NULL)
+        break;
+
+      if (!g_str_has_prefix (dent->d_name, tmpdir_prefix))
+        continue;
+
+      /* Quickly skip non-dirs, if unknown we ignore ENOTDIR when opening instead */
+      if (dent->d_type != DT_UNKNOWN &&
+          dent->d_type != DT_DIR)
+        continue;
+
+      if (!glnx_opendirat (dfd_iter.fd, dent->d_name, FALSE,
+                           &existing_tmpdir_fd, &local_error))
+        {
+          if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
+            continue;
+          else
+            {
+              g_propagate_error (error, g_steal_pointer (&local_error));
+              return FALSE;
+            }
+        }
+
+      lock_name = g_strconcat (dent->d_name, "-lock", NULL);
+
+      /* We put the lock outside the dir, so we can hold the lock
+       * until the directory is fully removed */
+      if (!glnx_make_lock_file (dfd_iter.fd, lock_name, LOCK_EX | LOCK_NB,
+                                file_lock_out, &local_error))
+        {
+          if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+            continue;
+          else
+            {
+              g_propagate_error (error, g_steal_pointer (&local_error));
+              return FALSE;
+            }
+        }
+
+      /* Touch the reused directory so that we don't accidentally
+       *   remove it due to being old when cleaning up the tmpdir
+       */
+      (void)futimens (existing_tmpdir_fd, NULL);
+
+      /* We found an existing tmpdir which we managed to lock */
+      tmpdir_name = g_strdup (dent->d_name);
+      tmpdir_fd = glnx_steal_fd (&existing_tmpdir_fd);
+      reusing_dir = TRUE;
+    }
+
+  while (tmpdir_name == NULL)
+    {
+      g_autofree char *tmpdir_name_template = g_strconcat (tmpdir_prefix, "XXXXXX", NULL);
+      glnx_fd_close int new_tmpdir_fd = -1;
+      g_autoptr(GError) local_error = NULL;
+      g_autofree char *lock_name = NULL;
+
+      /* No existing tmpdir found, create a new */
+
+      if (!glnx_mkdtempat (tmpdir_dfd, tmpdir_name_template, 0777, error))
+        return FALSE;
+
+      if (!glnx_opendirat (tmpdir_dfd, tmpdir_name_template, FALSE,
+                           &new_tmpdir_fd, error))
+        return FALSE;
+
+      lock_name = g_strconcat (tmpdir_name_template, "-lock", NULL);
+
+      /* Note, at this point we can race with another process that picks up this
+       * new directory. If that happens we need to retry, making a new directory. */
+      if (!glnx_make_lock_file (tmpdir_dfd, lock_name, LOCK_EX | LOCK_NB,
+                                file_lock_out, &local_error))
+        {
+          if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+            continue;
+          else
+            {
+              g_propagate_error (error, g_steal_pointer (&local_error));
+              return FALSE;
+            }
+        }
+
+      tmpdir_name = g_steal_pointer (&tmpdir_name_template);
+      tmpdir_fd = glnx_steal_fd (&new_tmpdir_fd);
+    }
+
+  if (tmpdir_name_out)
+    *tmpdir_name_out = g_steal_pointer (&tmpdir_name);
+
+  if (tmpdir_fd_out)
+    *tmpdir_fd_out = glnx_steal_fd (&tmpdir_fd);
+
+  if (reusing_dir_out)
+    *reusing_dir_out = reusing_dir;
+
+  return TRUE;
+}