mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-08-10 11:14:05 +02: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:
@@ -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)
|
||||||
|
181
gio/gfile.c
181
gio/gfile.c
@@ -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);
|
||||||
|
Reference in New Issue
Block a user