summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Walters <walters@verbum.org>2013-01-04 19:31:46 -0500
committerColin Walters <walters@verbum.org>2013-01-04 19:31:46 -0500
commitce441a7a7bd987265588674a11d62965e81bdf94 (patch)
tree8032bab44f9c4c05cbfb7ea326e75d3fded05870
parent6c736d9309d5ffbc60ed0a58e21f0f8ad609ba10 (diff)
downloadlibgsystem-ce441a7a7bd987265588674a11d62965e81bdf94.tar.gz
fileutils: Add gs_file_linkcopy_sync_data()
It often occurs in OSTree that I generate a temporary file, and then want to move it into place, ensuring it's fdatasync()'d.
-rw-r--r--gsystem-file-utils.c119
-rw-r--r--gsystem-file-utils.h5
2 files changed, 124 insertions, 0 deletions
diff --git a/gsystem-file-utils.c b/gsystem-file-utils.c
index 83b542e..2fafbf4 100644
--- a/gsystem-file-utils.c
+++ b/gsystem-file-utils.c
@@ -25,6 +25,8 @@
#define _GNU_SOURCE
#endif
+#include <string.h>
+
#include "libgsystem.h"
#include <glib/gstdio.h>
#include <gio/gunixinputstream.h>
@@ -230,6 +232,123 @@ gs_file_sync_data (GFile *file,
return ret;
}
+static const char *
+get_default_tmp_prefix (void)
+{
+ static char *tmpprefix = NULL;
+
+ if (g_once_init_enter (&tmpprefix))
+ {
+ const char *prgname = g_get_prgname ();
+ const char *p;
+ char *prefix;
+
+ p = strrchr (prgname, '/');
+ if (p)
+ prgname = p + 1;
+
+ prefix = g_strdup_printf ("tmp-%s%u-", prgname, getuid ());
+
+ g_once_init_leave (&tmpprefix, prefix);
+ }
+
+ return tmpprefix;
+}
+static char *
+gen_tmp_name (const char *prefix,
+ const char *suffix)
+{
+ static const char table[] = "ABCEDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789";
+ GString *str = g_string_new ("");
+ guint i;
+
+ if (!prefix)
+ prefix = get_default_tmp_prefix ();
+ if (!suffix)
+ suffix = "tmp";
+
+ g_string_append (str, prefix);
+ for (i = 0; i < 8; i++)
+ {
+ int offset = g_random_int_range (0, sizeof (table) - 1);
+ g_string_append_c (str, (guint8)table[offset]);
+ }
+ g_string_append_c (str, '.');
+ g_string_append (str, suffix);
+
+ return g_string_free (str, FALSE);
+}
+
+/**
+ * gs_file_linkcopy_sync_data:
+ * @src: Source file
+ * @dest: Destination file
+ * @cancellable:
+ * @error:
+ *
+ * Copy the file @src to @dest, using gs_file_sync_data() to ensure
+ * that @dest is in stable storage. As an optimization, this function
+ * will first try the UNIX link() call, but if the files are on
+ * separate devices, it will fall back to copying.
+ */
+gboolean
+gs_file_linkcopy_sync_data (GFile *src,
+ GFile *dest,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int i;
+ gs_unref_object GFile *dest_parent = NULL;
+
+ dest_parent = g_file_get_parent (dest);
+
+ /* 128 attempts seems reasonable... */
+ for (i = 0; i < 128; i++)
+ {
+ int res;
+ gs_free char *tmp_name = NULL;
+ gs_unref_object GFile *tmp_dest = NULL;
+
+ tmp_name = gen_tmp_name (NULL, NULL);
+ tmp_dest = g_file_get_child (dest_parent, tmp_name);
+
+ res = link (gs_file_get_path_cached (src), gs_file_get_path_cached (tmp_dest));
+ if (res == -1)
+ {
+ if (errno == EEXIST)
+ continue;
+ else if (errno == EXDEV || errno == EMLINK || errno == EPERM)
+ {
+ if (!g_file_copy (src, tmp_dest,
+ G_FILE_COPY_OVERWRITE | G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS,
+ cancellable, NULL, NULL, error))
+ goto out;
+ }
+ else
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
+ g_strerror (errsv));
+ goto out;
+ }
+ }
+
+ /* Now, we need to fdatasync */
+ if (!gs_file_sync_data (tmp_dest, cancellable, error))
+ goto out;
+
+ if (!gs_file_rename (tmp_dest, dest, cancellable, error))
+ goto out;
+
+ break;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
/**
* gs_file_get_path_cached:
*
diff --git a/gsystem-file-utils.h b/gsystem-file-utils.h
index 768cc0c..abd8e7c 100644
--- a/gsystem-file-utils.h
+++ b/gsystem-file-utils.h
@@ -44,6 +44,11 @@ gboolean gs_file_sync_data (GFile *file,
GCancellable *cancellable,
GError **error);
+gboolean gs_file_linkcopy_sync_data (GFile *src,
+ GFile *dest,
+ GCancellable *cancellable,
+ GError **error);
+
gboolean gs_file_rename (GFile *from,
GFile *to,
GCancellable *cancellable,