mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-12 23:46:17 +01:00
GFile: Support for splice(2) in copy_fallback
The (linux specific) system call splice can be used to transfer data between file descriptors whitout copying them into user space. See bug #604086 for additional details.
This commit is contained in:
parent
28f90db1ed
commit
bb4f63d639
@ -969,6 +969,7 @@ AC_CHECK_FUNCS(chown lchmod lchown fchmod fchown link statvfs statfs utimes getg
|
||||
AC_CHECK_FUNCS(getmntent_r setmntent endmntent hasmntopt getmntinfo)
|
||||
# Check for high-resolution sleep functions
|
||||
AC_CHECK_FUNCS(nanosleep nsleep)
|
||||
AC_CHECK_FUNCS(splice)
|
||||
|
||||
AC_CHECK_HEADERS(crt_externs.h)
|
||||
AC_CHECK_FUNCS(_NSGetEnviron)
|
||||
|
189
gio/gfile.c
189
gio/gfile.c
@ -23,6 +23,13 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#ifdef HAVE_SPLICE
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef HAVE_PWD_H
|
||||
@ -33,6 +40,7 @@
|
||||
#include "gioscheduler.h"
|
||||
#include "gsimpleasyncresult.h"
|
||||
#include "gfileattribute-priv.h"
|
||||
#include "gfiledescriptorbased.h"
|
||||
#include "gpollfilemonitor.h"
|
||||
#include "gappinfo.h"
|
||||
#include "gfileinputstream.h"
|
||||
@ -2120,7 +2128,7 @@ g_file_replace_finish (GFile *file,
|
||||
if (g_simple_async_result_propagate_error (simple, error))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
iface = G_FILE_GET_IFACE (file);
|
||||
return (* iface->replace_finish) (file, res, error);
|
||||
}
|
||||
@ -2628,7 +2636,6 @@ g_file_copy_attributes (GFile *source,
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Closes the streams */
|
||||
static gboolean
|
||||
copy_stream_with_progress (GInputStream *in,
|
||||
GOutputStream *out,
|
||||
@ -2714,26 +2721,134 @@ copy_stream_with_progress (GInputStream *in,
|
||||
progress_callback (current_size, total_size, progress_callback_data);
|
||||
}
|
||||
|
||||
if (!res)
|
||||
error = NULL; /* Ignore further errors */
|
||||
|
||||
/* Make sure we send full copied size */
|
||||
if (progress_callback)
|
||||
progress_callback (current_size, total_size, progress_callback_data);
|
||||
|
||||
/* Don't care about errors in source here */
|
||||
g_input_stream_close (in, cancellable, NULL);
|
||||
|
||||
/* But write errors on close are bad! */
|
||||
if (!g_output_stream_close (out, cancellable, error))
|
||||
res = FALSE;
|
||||
|
||||
g_object_unref (in);
|
||||
g_object_unref (out);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SPLICE
|
||||
|
||||
static gboolean
|
||||
do_splice (int fd_in,
|
||||
loff_t *off_in,
|
||||
int fd_out,
|
||||
loff_t *off_out,
|
||||
size_t len,
|
||||
long *bytes_transferd,
|
||||
GError **error)
|
||||
{
|
||||
long result;
|
||||
|
||||
retry:
|
||||
result = splice (fd_in, off_in, fd_out, off_out, len, SPLICE_F_MORE);
|
||||
|
||||
if (result == -1)
|
||||
{
|
||||
int errsv = errno;
|
||||
|
||||
if (errsv == EINTR)
|
||||
goto retry;
|
||||
else if (errsv == ENOSYS || errsv == EINVAL)
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"Splice 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;
|
||||
}
|
||||
|
||||
*bytes_transferd = result;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
splice_stream_with_progress (GInputStream *in,
|
||||
GOutputStream *out,
|
||||
GCancellable *cancellable,
|
||||
GFileProgressCallback progress_callback,
|
||||
gpointer progress_callback_data,
|
||||
GError **error)
|
||||
{
|
||||
int buffer[2];
|
||||
gboolean res;
|
||||
goffset total_size;
|
||||
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));
|
||||
|
||||
if (pipe (buffer) != 0)
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"Pipe creation failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
total_size = -1;
|
||||
/* avoid performance impact of querying total size when it's not needed */
|
||||
if (progress_callback)
|
||||
{
|
||||
struct stat sbuf;
|
||||
|
||||
if (fstat (fd_in, &sbuf) == 0)
|
||||
total_size = sbuf.st_size;
|
||||
}
|
||||
|
||||
if (total_size == -1)
|
||||
total_size = 0;
|
||||
|
||||
offset_in = offset_out = 0;
|
||||
res = FALSE;
|
||||
while (TRUE)
|
||||
{
|
||||
long n_read;
|
||||
long n_written;
|
||||
|
||||
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
||||
break;
|
||||
|
||||
if (!do_splice (fd_in, &offset_in, buffer[1], NULL, 1024*64, &n_read, error))
|
||||
break;
|
||||
|
||||
if (n_read == 0)
|
||||
{
|
||||
res = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
while (n_read > 0)
|
||||
{
|
||||
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
||||
break;
|
||||
|
||||
if (!do_splice (buffer[0], NULL, fd_out, &offset_out, n_read, &n_written, error))
|
||||
break;
|
||||
|
||||
n_read -= n_written;
|
||||
}
|
||||
|
||||
if (progress_callback)
|
||||
progress_callback (offset_in, total_size, progress_callback_data);
|
||||
}
|
||||
|
||||
/* Make sure we send full copied size */
|
||||
if (progress_callback)
|
||||
progress_callback (offset_in, total_size, progress_callback_data);
|
||||
|
||||
close (buffer[0]);
|
||||
close (buffer[1]);
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
file_copy_fallback (GFile *source,
|
||||
GFile *destination,
|
||||
@ -2747,6 +2862,10 @@ file_copy_fallback (GFile *source,
|
||||
GOutputStream *out;
|
||||
GFileInfo *info;
|
||||
const char *target;
|
||||
gboolean result;
|
||||
#ifdef HAVE_SPLICE
|
||||
gboolean fallback = TRUE;
|
||||
#endif
|
||||
|
||||
/* need to know the file type */
|
||||
info = g_file_query_info (source,
|
||||
@ -2814,13 +2933,45 @@ file_copy_fallback (GFile *source,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!copy_stream_with_progress (in, out, source, cancellable,
|
||||
progress_callback, progress_callback_data,
|
||||
error))
|
||||
#ifdef HAVE_SPLICE
|
||||
if (G_IS_FILE_DESCRIPTOR_BASED (in) && G_IS_FILE_DESCRIPTOR_BASED (out))
|
||||
{
|
||||
GError *splice_err = NULL;
|
||||
|
||||
result = splice_stream_with_progress (in, out, cancellable,
|
||||
progress_callback, progress_callback_data,
|
||||
&splice_err);
|
||||
|
||||
if (result || !g_error_matches (splice_err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
|
||||
{
|
||||
fallback = FALSE;
|
||||
if (!result)
|
||||
g_propagate_error (error, splice_err);
|
||||
}
|
||||
else
|
||||
g_clear_error (&splice_err);
|
||||
}
|
||||
|
||||
if (fallback)
|
||||
#endif
|
||||
result = copy_stream_with_progress (in, out, source, cancellable,
|
||||
progress_callback, progress_callback_data,
|
||||
error);
|
||||
|
||||
/* Don't care about errors in source here */
|
||||
g_input_stream_close (in, cancellable, NULL);
|
||||
|
||||
/* But write errors on close are bad! */
|
||||
if (!g_output_stream_close (out, cancellable, result ? error : NULL))
|
||||
result = FALSE;
|
||||
|
||||
g_object_unref (in);
|
||||
g_object_unref (out);
|
||||
|
||||
if (result == FALSE)
|
||||
return FALSE;
|
||||
|
||||
copied_file:
|
||||
|
||||
/* Ignore errors here. Failure to copy metadata is not a hard error */
|
||||
g_file_copy_attributes (source, destination,
|
||||
flags, cancellable, NULL);
|
||||
|
Loading…
Reference in New Issue
Block a user