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:
Christian Kellner
2010-02-07 17:23:38 +01:00
parent 28f90db1ed
commit bb4f63d639
2 changed files with 171 additions and 19 deletions

View File

@@ -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) AC_CHECK_FUNCS(getmntent_r setmntent endmntent hasmntopt getmntinfo)
# Check for high-resolution sleep functions # Check for high-resolution sleep functions
AC_CHECK_FUNCS(nanosleep nsleep) AC_CHECK_FUNCS(nanosleep nsleep)
AC_CHECK_FUNCS(splice)
AC_CHECK_HEADERS(crt_externs.h) AC_CHECK_HEADERS(crt_externs.h)
AC_CHECK_FUNCS(_NSGetEnviron) AC_CHECK_FUNCS(_NSGetEnviron)

View File

@@ -23,6 +23,13 @@
*/ */
#include "config.h" #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 <string.h>
#include <sys/types.h> #include <sys/types.h>
#ifdef HAVE_PWD_H #ifdef HAVE_PWD_H
@@ -33,6 +40,7 @@
#include "gioscheduler.h" #include "gioscheduler.h"
#include "gsimpleasyncresult.h" #include "gsimpleasyncresult.h"
#include "gfileattribute-priv.h" #include "gfileattribute-priv.h"
#include "gfiledescriptorbased.h"
#include "gpollfilemonitor.h" #include "gpollfilemonitor.h"
#include "gappinfo.h" #include "gappinfo.h"
#include "gfileinputstream.h" #include "gfileinputstream.h"
@@ -2628,7 +2636,6 @@ g_file_copy_attributes (GFile *source,
return res; return res;
} }
/* Closes the streams */
static gboolean static gboolean
copy_stream_with_progress (GInputStream *in, copy_stream_with_progress (GInputStream *in,
GOutputStream *out, GOutputStream *out,
@@ -2714,25 +2721,133 @@ copy_stream_with_progress (GInputStream *in,
progress_callback (current_size, total_size, progress_callback_data); progress_callback (current_size, total_size, progress_callback_data);
} }
if (!res)
error = NULL; /* Ignore further errors */
/* Make sure we send full copied size */ /* Make sure we send full copied size */
if (progress_callback) if (progress_callback)
progress_callback (current_size, total_size, progress_callback_data); progress_callback (current_size, total_size, progress_callback_data);
/* Don't care about errors in source here */ return res;
g_input_stream_close (in, cancellable, NULL); }
/* But write errors on close are bad! */ #ifdef HAVE_SPLICE
if (!g_output_stream_close (out, cancellable, error))
res = FALSE;
g_object_unref (in); static gboolean
g_object_unref (out); 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; return res;
} }
#endif
static gboolean static gboolean
file_copy_fallback (GFile *source, file_copy_fallback (GFile *source,
@@ -2747,6 +2862,10 @@ file_copy_fallback (GFile *source,
GOutputStream *out; GOutputStream *out;
GFileInfo *info; GFileInfo *info;
const char *target; const char *target;
gboolean result;
#ifdef HAVE_SPLICE
gboolean fallback = TRUE;
#endif
/* need to know the file type */ /* need to know the file type */
info = g_file_query_info (source, info = g_file_query_info (source,
@@ -2814,13 +2933,45 @@ file_copy_fallback (GFile *source,
return FALSE; return FALSE;
} }
if (!copy_stream_with_progress (in, out, source, cancellable, #ifdef HAVE_SPLICE
progress_callback, progress_callback_data, if (G_IS_FILE_DESCRIPTOR_BASED (in) && G_IS_FILE_DESCRIPTOR_BASED (out))
error)) {
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; return FALSE;
copied_file: copied_file:
/* Ignore errors here. Failure to copy metadata is not a hard error */ /* Ignore errors here. Failure to copy metadata is not a hard error */
g_file_copy_attributes (source, destination, g_file_copy_attributes (source, destination,
flags, cancellable, NULL); flags, cancellable, NULL);