diff options
author | Colin Walters <walters@verbum.org> | 2013-01-04 19:31:46 -0500 |
---|---|---|
committer | Colin Walters <walters@verbum.org> | 2013-01-04 19:31:46 -0500 |
commit | ce441a7a7bd987265588674a11d62965e81bdf94 (patch) | |
tree | 8032bab44f9c4c05cbfb7ea326e75d3fded05870 | |
parent | 6c736d9309d5ffbc60ed0a58e21f0f8ad609ba10 (diff) | |
download | libgsystem-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.c | 119 | ||||
-rw-r--r-- | gsystem-file-utils.h | 5 |
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, |