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)
# 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)

View File

@ -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);