mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-12 23:46:17 +01:00
Merge branch '2863-copy-file-range' into 'main'
gfile: Support copy_file_range() for file copies Closes #2863 See merge request GNOME/glib!3328
This commit is contained in:
commit
20964ad4ab
163
gio/gfile.c
163
gio/gfile.c
@ -2807,6 +2807,11 @@ g_file_build_attribute_list_for_copy (GFile *file,
|
|||||||
first = TRUE;
|
first = TRUE;
|
||||||
s = g_string_new ("");
|
s = g_string_new ("");
|
||||||
|
|
||||||
|
/* Always query the source file size, even though we can’t set that on the
|
||||||
|
* destination. This is useful for the copy functions. */
|
||||||
|
first = FALSE;
|
||||||
|
g_string_append (s, G_FILE_ATTRIBUTE_STANDARD_SIZE);
|
||||||
|
|
||||||
if (attributes)
|
if (attributes)
|
||||||
{
|
{
|
||||||
for (i = 0; i < attributes->n_infos; i++)
|
for (i = 0; i < attributes->n_infos; i++)
|
||||||
@ -3002,6 +3007,122 @@ copy_stream_with_progress (GInputStream *in,
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_COPY_FILE_RANGE
|
||||||
|
static gboolean
|
||||||
|
do_copy_file_range (int fd_in,
|
||||||
|
loff_t *off_in,
|
||||||
|
int fd_out,
|
||||||
|
loff_t *off_out,
|
||||||
|
size_t len,
|
||||||
|
size_t *bytes_transferred,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
ssize_t result;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
result = copy_file_range (fd_in, off_in, fd_out, off_out, len, 0);
|
||||||
|
|
||||||
|
if (result == -1)
|
||||||
|
{
|
||||||
|
int errsv = errno;
|
||||||
|
|
||||||
|
if (errsv == EINTR)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (errsv == ENOSYS || errsv == EINVAL || errsv == EOPNOTSUPP || errsv == EXDEV)
|
||||||
|
{
|
||||||
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||||
|
_("Copy file range not supported"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR,
|
||||||
|
g_io_error_from_errno (errsv),
|
||||||
|
_("Error splicing file: %s"),
|
||||||
|
g_strerror (errsv));
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} while (result == -1);
|
||||||
|
|
||||||
|
g_assert (result >= 0);
|
||||||
|
*bytes_transferred = result;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
copy_file_range_with_progress (GInputStream *in,
|
||||||
|
GFileInfo *in_info,
|
||||||
|
GOutputStream *out,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GFileProgressCallback progress_callback,
|
||||||
|
gpointer progress_callback_data,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
goffset total_size, last_notified_size;
|
||||||
|
size_t copy_len;
|
||||||
|
loff_t offset_in;
|
||||||
|
loff_t offset_out;
|
||||||
|
int fd_in, fd_out;
|
||||||
|
|
||||||
|
fd_in = g_file_descriptor_based_get_fd (G_FILE_DESCRIPTOR_BASED (in));
|
||||||
|
fd_out = g_file_descriptor_based_get_fd (G_FILE_DESCRIPTOR_BASED (out));
|
||||||
|
|
||||||
|
g_assert (g_file_info_has_attribute (in_info, G_FILE_ATTRIBUTE_STANDARD_SIZE));
|
||||||
|
total_size = g_file_info_get_size (in_info);
|
||||||
|
|
||||||
|
/* Bail out if the reported size of the file is zero. It might be zero, but it
|
||||||
|
* might also just be a kernel file in /proc. They report their file size as
|
||||||
|
* zero, but then have data when you start reading. Go to the fallback code
|
||||||
|
* path for those. */
|
||||||
|
if (total_size == 0)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||||
|
_("Copy file range not supported"));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_in = offset_out = 0;
|
||||||
|
copy_len = total_size;
|
||||||
|
last_notified_size = 0;
|
||||||
|
|
||||||
|
/* Call copy_file_range() in a loop until the whole contents are copied. For
|
||||||
|
* smaller files, this loop will iterate only once. For larger files, the
|
||||||
|
* kernel (at least, kernel 6.1.6) will return after 2GB anyway, so that gives
|
||||||
|
* us more loop iterations and more progress reporting. */
|
||||||
|
while (copy_len > 0)
|
||||||
|
{
|
||||||
|
size_t n_copied;
|
||||||
|
|
||||||
|
if (g_cancellable_set_error_if_cancelled (cancellable, error) ||
|
||||||
|
!do_copy_file_range (fd_in, &offset_in, fd_out, &offset_out, copy_len, &n_copied, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (n_copied == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
g_assert (n_copied <= copy_len);
|
||||||
|
copy_len -= n_copied;
|
||||||
|
|
||||||
|
if (progress_callback)
|
||||||
|
{
|
||||||
|
progress_callback (offset_in, total_size, progress_callback_data);
|
||||||
|
last_notified_size = total_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure we send full copied size */
|
||||||
|
if (progress_callback && last_notified_size != total_size)
|
||||||
|
progress_callback (offset_in, total_size, progress_callback_data);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_COPY_FILE_RANGE */
|
||||||
|
|
||||||
#ifdef HAVE_SPLICE
|
#ifdef HAVE_SPLICE
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@ -3042,6 +3163,7 @@ retry:
|
|||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
splice_stream_with_progress (GInputStream *in,
|
splice_stream_with_progress (GInputStream *in,
|
||||||
|
GFileInfo *in_info,
|
||||||
GOutputStream *out,
|
GOutputStream *out,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GFileProgressCallback progress_callback,
|
GFileProgressCallback progress_callback,
|
||||||
@ -3085,10 +3207,8 @@ splice_stream_with_progress (GInputStream *in,
|
|||||||
/* avoid performance impact of querying total size when it's not needed */
|
/* avoid performance impact of querying total size when it's not needed */
|
||||||
if (progress_callback)
|
if (progress_callback)
|
||||||
{
|
{
|
||||||
struct stat sbuf;
|
g_assert (g_file_info_has_attribute (in_info, G_FILE_ATTRIBUTE_STANDARD_SIZE));
|
||||||
|
total_size = g_file_info_get_size (in_info);
|
||||||
if (fstat (fd_in, &sbuf) == 0)
|
|
||||||
total_size = sbuf.st_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total_size == -1)
|
if (total_size == -1)
|
||||||
@ -3151,6 +3271,7 @@ splice_stream_with_progress (GInputStream *in,
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
static gboolean
|
static gboolean
|
||||||
btrfs_reflink_with_progress (GInputStream *in,
|
btrfs_reflink_with_progress (GInputStream *in,
|
||||||
|
GFileInfo *in_info,
|
||||||
GOutputStream *out,
|
GOutputStream *out,
|
||||||
GFileInfo *info,
|
GFileInfo *info,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
@ -3169,10 +3290,8 @@ btrfs_reflink_with_progress (GInputStream *in,
|
|||||||
/* avoid performance impact of querying total size when it's not needed */
|
/* avoid performance impact of querying total size when it's not needed */
|
||||||
if (progress_callback)
|
if (progress_callback)
|
||||||
{
|
{
|
||||||
struct stat sbuf;
|
g_assert (g_file_info_has_attribute (in_info, G_FILE_ATTRIBUTE_STANDARD_SIZE));
|
||||||
|
total_size = g_file_info_get_size (in_info);
|
||||||
if (fstat (fd_in, &sbuf) == 0)
|
|
||||||
total_size = sbuf.st_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total_size == -1)
|
if (total_size == -1)
|
||||||
@ -3376,7 +3495,7 @@ file_copy_fallback (GFile *source,
|
|||||||
{
|
{
|
||||||
GError *reflink_err = NULL;
|
GError *reflink_err = NULL;
|
||||||
|
|
||||||
if (!btrfs_reflink_with_progress (in, out, info, cancellable,
|
if (!btrfs_reflink_with_progress (in, info, out, info, cancellable,
|
||||||
progress_callback, progress_callback_data,
|
progress_callback, progress_callback_data,
|
||||||
&reflink_err))
|
&reflink_err))
|
||||||
{
|
{
|
||||||
@ -3398,12 +3517,36 @@ file_copy_fallback (GFile *source,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_COPY_FILE_RANGE
|
||||||
|
if (G_IS_FILE_DESCRIPTOR_BASED (in) && G_IS_FILE_DESCRIPTOR_BASED (out))
|
||||||
|
{
|
||||||
|
GError *copy_file_range_error = NULL;
|
||||||
|
|
||||||
|
if (copy_file_range_with_progress (in, info, out, cancellable,
|
||||||
|
progress_callback, progress_callback_data,
|
||||||
|
©_file_range_error))
|
||||||
|
{
|
||||||
|
ret = TRUE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
else if (!g_error_matches (copy_file_range_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
|
||||||
|
{
|
||||||
|
g_propagate_error (error, g_steal_pointer (©_file_range_error));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_clear_error (©_file_range_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* HAVE_COPY_FILE_RANGE */
|
||||||
|
|
||||||
#ifdef HAVE_SPLICE
|
#ifdef HAVE_SPLICE
|
||||||
if (G_IS_FILE_DESCRIPTOR_BASED (in) && G_IS_FILE_DESCRIPTOR_BASED (out))
|
if (G_IS_FILE_DESCRIPTOR_BASED (in) && G_IS_FILE_DESCRIPTOR_BASED (out))
|
||||||
{
|
{
|
||||||
GError *splice_err = NULL;
|
GError *splice_err = NULL;
|
||||||
|
|
||||||
if (!splice_stream_with_progress (in, out, cancellable,
|
if (!splice_stream_with_progress (in, info, out, cancellable,
|
||||||
progress_callback, progress_callback_data,
|
progress_callback, progress_callback_data,
|
||||||
&splice_err))
|
&splice_err))
|
||||||
{
|
{
|
||||||
|
@ -2513,6 +2513,80 @@ test_copy_preserve_mode (void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
goffset current_num_bytes;
|
||||||
|
goffset total_num_bytes;
|
||||||
|
} CopyProgressData;
|
||||||
|
|
||||||
|
static void
|
||||||
|
file_copy_progress_cb (goffset current_num_bytes,
|
||||||
|
goffset total_num_bytes,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
CopyProgressData *prev_data = user_data;
|
||||||
|
|
||||||
|
g_assert_cmpuint (total_num_bytes, ==, prev_data->total_num_bytes);
|
||||||
|
g_assert_cmpuint (current_num_bytes, >=, prev_data->current_num_bytes);
|
||||||
|
|
||||||
|
/* Update it for the next callback. */
|
||||||
|
prev_data->current_num_bytes = current_num_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_copy_progress (void)
|
||||||
|
{
|
||||||
|
GFile *src_tmpfile = NULL;
|
||||||
|
GFile *dest_tmpfile = NULL;
|
||||||
|
GFileIOStream *iostream;
|
||||||
|
GOutputStream *ostream;
|
||||||
|
GError *local_error = NULL;
|
||||||
|
const guint8 buffer[] = { 1, 2, 3, 4, 5 };
|
||||||
|
CopyProgressData progress_data;
|
||||||
|
|
||||||
|
src_tmpfile = g_file_new_tmp ("tmp-copy-progressXXXXXX",
|
||||||
|
&iostream, &local_error);
|
||||||
|
g_assert_no_error (local_error);
|
||||||
|
|
||||||
|
/* Write some content to the file for testing. */
|
||||||
|
ostream = g_io_stream_get_output_stream (G_IO_STREAM (iostream));
|
||||||
|
g_output_stream_write (ostream, buffer, sizeof (buffer), NULL, &local_error);
|
||||||
|
g_assert_no_error (local_error);
|
||||||
|
|
||||||
|
g_io_stream_close ((GIOStream *) iostream, NULL, &local_error);
|
||||||
|
g_assert_no_error (local_error);
|
||||||
|
g_clear_object (&iostream);
|
||||||
|
|
||||||
|
/* Grab a unique destination filename. */
|
||||||
|
dest_tmpfile = g_file_new_tmp ("tmp-copy-progressXXXXXX",
|
||||||
|
&iostream, &local_error);
|
||||||
|
g_assert_no_error (local_error);
|
||||||
|
g_io_stream_close ((GIOStream *) iostream, NULL, &local_error);
|
||||||
|
g_assert_no_error (local_error);
|
||||||
|
g_clear_object (&iostream);
|
||||||
|
|
||||||
|
/* Set the progress data to an initial offset of zero. The callback will
|
||||||
|
* assert that progress is non-decreasing and reaches the total length of
|
||||||
|
* the file. */
|
||||||
|
progress_data.current_num_bytes = 0;
|
||||||
|
progress_data.total_num_bytes = sizeof (buffer);
|
||||||
|
|
||||||
|
/* Copy the file with progress reporting. */
|
||||||
|
g_file_copy (src_tmpfile, dest_tmpfile, G_FILE_COPY_OVERWRITE,
|
||||||
|
NULL, file_copy_progress_cb, &progress_data, &local_error);
|
||||||
|
g_assert_no_error (local_error);
|
||||||
|
|
||||||
|
g_assert_cmpuint (progress_data.current_num_bytes, ==, progress_data.total_num_bytes);
|
||||||
|
g_assert_cmpuint (progress_data.total_num_bytes, ==, sizeof (buffer));
|
||||||
|
|
||||||
|
/* Clean up. */
|
||||||
|
(void) g_file_delete (src_tmpfile, NULL, NULL);
|
||||||
|
(void) g_file_delete (dest_tmpfile, NULL, NULL);
|
||||||
|
|
||||||
|
g_clear_object (&src_tmpfile);
|
||||||
|
g_clear_object (&dest_tmpfile);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_measure (void)
|
test_measure (void)
|
||||||
{
|
{
|
||||||
@ -3844,6 +3918,7 @@ main (int argc, char *argv[])
|
|||||||
g_test_add_func ("/file/async-delete", test_async_delete);
|
g_test_add_func ("/file/async-delete", test_async_delete);
|
||||||
g_test_add_func ("/file/async-make-symlink", test_async_make_symlink);
|
g_test_add_func ("/file/async-make-symlink", test_async_make_symlink);
|
||||||
g_test_add_func ("/file/copy-preserve-mode", test_copy_preserve_mode);
|
g_test_add_func ("/file/copy-preserve-mode", test_copy_preserve_mode);
|
||||||
|
g_test_add_func ("/file/copy/progress", test_copy_progress);
|
||||||
g_test_add_func ("/file/measure", test_measure);
|
g_test_add_func ("/file/measure", test_measure);
|
||||||
g_test_add_func ("/file/measure-async", test_measure_async);
|
g_test_add_func ("/file/measure-async", test_measure_async);
|
||||||
g_test_add_func ("/file/load-bytes", test_load_bytes);
|
g_test_add_func ("/file/load-bytes", test_load_bytes);
|
||||||
@ -3869,3 +3944,4 @@ main (int argc, char *argv[])
|
|||||||
|
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,6 +611,7 @@ endif
|
|||||||
functions = [
|
functions = [
|
||||||
'accept4',
|
'accept4',
|
||||||
'close_range',
|
'close_range',
|
||||||
|
'copy_file_range',
|
||||||
'endmntent',
|
'endmntent',
|
||||||
'endservent',
|
'endservent',
|
||||||
'epoll_create',
|
'epoll_create',
|
||||||
|
Loading…
Reference in New Issue
Block a user