summaryrefslogtreecommitdiff
path: root/gsystem-shutil.c
diff options
context:
space:
mode:
Diffstat (limited to 'gsystem-shutil.c')
-rw-r--r--gsystem-shutil.c221
1 files changed, 131 insertions, 90 deletions
diff --git a/gsystem-shutil.c b/gsystem-shutil.c
index 8416033..095bedd 100644
--- a/gsystem-shutil.c
+++ b/gsystem-shutil.c
@@ -40,107 +40,57 @@ union dirent_storage {
((NAME_MAX + 1 + sizeof(long)) & ~(sizeof(long) - 1))];
};
-static gboolean
-cp_internal (GFile *src,
- GFile *dest,
- gboolean use_hardlinks,
- GCancellable *cancellable,
- GError **error);
+static inline void
+_set_error_from_errno (GError **error)
+{
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+}
static gboolean
-cp_internal_one_item (GFile *src,
- GFile *dest,
- GFileInfo *file_info,
- gboolean use_hardlinks,
- GCancellable *cancellable,
- GError **error)
+copy_xattrs_from_file_to_fd (GFile *src,
+ int dest_fd,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
- const char *name = g_file_info_get_name (file_info);
- GFile *src_child = g_file_get_child (src, name);
- GFile *dest_child = g_file_get_child (dest, name);
-
- if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
- {
- if (!gs_file_ensure_directory (dest_child, FALSE, cancellable, error))
- goto out;
-
- /* Can't do this even though we'd like to; it fails with an error about
- * setting standard::type not being supported =/
- *
- if (!g_file_set_attributes_from_info (dest_child, file_info, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, error))
- goto out;
- */
- if (chmod (gs_file_get_path_cached (dest_child),
- g_file_info_get_attribute_uint32 (file_info, "unix::mode")) == -1)
- {
- int errsv = errno;
- g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
- g_strerror (errsv));
- goto out;
- }
+ GVariant *src_xattrs = NULL;
- if (chown (gs_file_get_path_cached (dest_child),
- g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
- g_file_info_get_attribute_uint32 (file_info, "unix::gid")) == -1)
- {
- int errsv = errno;
- g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
- g_strerror (errsv));
- goto out;
- }
+ if (!gs_file_get_all_xattrs (src, &src_xattrs, cancellable, error))
+ goto out;
- if (!cp_internal (src_child, dest_child, use_hardlinks, cancellable, error))
- goto out;
- }
- else
+ if (src_xattrs)
{
- gboolean did_link = FALSE;
- (void) unlink (gs_file_get_path_cached (dest_child));
- if (use_hardlinks)
- {
- if (link (gs_file_get_path_cached (src_child), gs_file_get_path_cached (dest_child)) == -1)
- {
- if (!(errno == EMLINK || errno == EXDEV))
- {
- int errsv = errno;
- g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
- g_strerror (errsv));
- goto out;
- }
- use_hardlinks = FALSE;
- }
- else
- did_link = TRUE;
- }
- if (!did_link)
- {
- if (!g_file_copy (src_child, dest_child,
- G_FILE_COPY_OVERWRITE | G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS,
- cancellable, NULL, NULL, error))
- goto out;
- }
+ if (!gs_fd_set_all_xattrs (dest_fd, src_xattrs, cancellable, error))
+ goto out;
}
ret = TRUE;
out:
- g_clear_object (&src_child);
- g_clear_object (&dest_child);
+ g_clear_pointer (&src_xattrs, g_variant_unref);
return ret;
}
+typedef enum {
+ GS_CP_MODE_NONE,
+ GS_CP_MODE_HARDLINK,
+ GS_CP_MODE_COPY_ALL
+} GsCpMode;
+
static gboolean
cp_internal (GFile *src,
GFile *dest,
- gboolean use_hardlinks,
+ GsCpMode mode,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
GFileEnumerator *enumerator = NULL;
- GFileInfo *file_info = NULL;
- GError *temp_error = NULL;
+ GFileInfo *src_info = NULL;
+ GFile *dest_child = NULL;
+ int dest_dfd = -1;
+ int r;
enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
@@ -148,26 +98,115 @@ cp_internal (GFile *src,
if (!enumerator)
goto out;
- if (!gs_file_ensure_directory (dest, FALSE, cancellable, error))
+ src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \
+ "time::modified,time::modified-usec,time::access,time::access-usec",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (!src_info)
goto out;
- while ((file_info = g_file_enumerator_next_file (enumerator, cancellable, &temp_error)) != NULL)
+ do
+ r = mkdir (gs_file_get_path_cached (dest), 0755);
+ while (G_UNLIKELY (r == -1 && errno == EINTR));
+ if (r == -1)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ if (mode != GS_CP_MODE_NONE)
{
- if (!cp_internal_one_item (src, dest, file_info, use_hardlinks,
- cancellable, error))
+ if (!gs_file_open_dir_fd (dest, &dest_dfd,
+ cancellable, error))
+ goto out;
+
+ do
+ r = fchown (dest_dfd,
+ g_file_info_get_attribute_uint32 (src_info, "unix::uid"),
+ g_file_info_get_attribute_uint32 (src_info, "unix::gid"));
+ while (G_UNLIKELY (r == -1 && errno == EINTR));
+ if (r == -1)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ do
+ r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode"));
+ while (G_UNLIKELY (r == -1 && errno == EINTR));
+
+ if (!copy_xattrs_from_file_to_fd (src, dest_dfd, cancellable, error))
goto out;
- g_clear_object (&file_info);
+
+ if (dest_dfd != -1)
+ {
+ (void) close (dest_dfd);
+ dest_dfd = -1;
+ }
}
- if (temp_error)
+
+ while (TRUE)
{
- g_propagate_error (error, temp_error);
- goto out;
+ GFileInfo *file_info = NULL;
+ GFile *src_child = NULL;
+
+ if (!gs_file_enumerator_iterate (enumerator, &file_info, &src_child,
+ cancellable, error))
+ goto out;
+ if (!file_info)
+ break;
+
+ if (dest_child) g_object_unref (dest_child);
+ dest_child = g_file_get_child (dest, g_file_info_get_name (file_info));
+
+ if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+ {
+ if (!cp_internal (src_child, dest_child, mode,
+ cancellable, error))
+ goto out;
+ }
+ else
+ {
+ gboolean did_link = FALSE;
+ (void) unlink (gs_file_get_path_cached (dest_child));
+ if (mode == GS_CP_MODE_HARDLINK)
+ {
+ if (link (gs_file_get_path_cached (src_child), gs_file_get_path_cached (dest_child)) == -1)
+ {
+ if (!(errno == EMLINK || errno == EXDEV))
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ /* We failed to hardlink; fall back to copying all; this will
+ * affect subsequent directory copies too.
+ */
+ mode = GS_CP_MODE_COPY_ALL;
+ }
+ else
+ did_link = TRUE;
+ }
+ if (!did_link)
+ {
+ GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS;
+ if (mode == GS_CP_MODE_COPY_ALL)
+ copyflags |= G_FILE_COPY_ALL_METADATA;
+ if (!g_file_copy (src_child, dest_child, copyflags,
+ cancellable, NULL, NULL, error))
+ goto out;
+ }
+ }
}
ret = TRUE;
out:
+ if (dest_dfd != -1)
+ (void) close (dest_dfd);
+ g_clear_object (&src_info);
g_clear_object (&enumerator);
- g_clear_object (&file_info);
+ g_clear_object (&dest_child);
return ret;
}
@@ -191,7 +230,8 @@ gs_shutil_cp_al_or_fallback (GFile *src,
GCancellable *cancellable,
GError **error)
{
- return cp_internal (src, dest, TRUE, cancellable, error);
+ return cp_internal (src, dest, GS_CP_MODE_HARDLINK,
+ cancellable, error);
}
/**
@@ -212,7 +252,8 @@ gs_shutil_cp_a (GFile *src,
GCancellable *cancellable,
GError **error)
{
- return cp_internal (src, dest, FALSE, cancellable, error);
+ return cp_internal (src, dest, GS_CP_MODE_COPY_ALL,
+ cancellable, error);
}
static unsigned char