From 01fcbd7c2efe29a3563c3c15aa4c1f50dd24a4ae Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 10 Aug 2010 06:52:49 +0530 Subject: [PATCH] [gio] Add btrfs clone ioctl support to GFile * Falls back gracefully if local filesystem is not btrfs --- configure.ac | 1 + gio/glocalfile.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 0 deletions(-) diff --git a/configure.ac b/configure.ac index 8d41107..bd7df2c 100644 --- a/configure.ac +++ b/configure.ac @@ -865,6 +865,7 @@ AC_CHECK_HEADERS([sys/select.h sys/types.h stdint.h inttypes.h sched.h malloc.h] AC_CHECK_HEADERS([sys/vfs.h sys/mount.h sys/vmount.h sys/statfs.h sys/statvfs.h]) AC_CHECK_HEADERS([mntent.h sys/mnttab.h sys/vfstab.h sys/mntctl.h sys/sysctl.h fstab.h]) AC_CHECK_HEADERS([sys/uio.h]) +AC_CHECK_HEADERS([sys/ioctl.h]) # check for structure fields AC_CHECK_MEMBERS([struct stat.st_mtimensec, struct stat.st_mtim.tv_nsec, struct stat.st_atimensec, struct stat.st_atim.tv_nsec, struct stat.st_ctimensec, struct stat.st_ctim.tv_nsec]) diff --git a/gio/glocalfile.c b/gio/glocalfile.c index 4641f2e..150ddff 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -46,6 +46,12 @@ #include #endif +#if HAVE_SYS_IOCTL_H +#include +#define BTRFS_IOCTL_MAGIC 0x94 +#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) +#endif + #ifndef O_BINARY #define O_BINARY 0 #endif @@ -2186,6 +2192,175 @@ g_local_file_copy (GFile *source, gpointer progress_callback_data, GError **error) { +#if HAVE_SYS_IOCTL_H + GLocalFile *local_source, *local_destination; + GStatBuf statbuf; + gboolean source_is_dir, destination_exist; + char *backup_name; + int source_fd, destination_fd; + int res; + off_t source_size; + + if (!G_IS_LOCAL_FILE (source) || + !G_IS_LOCAL_FILE (destination)) + goto fallback; + + local_source = G_LOCAL_FILE (source); + local_destination = G_LOCAL_FILE (destination); + + /* Checks on source and destination are mostly copied from g_local_file_move */ + res = g_lstat (local_source->filename, &statbuf); + if (res == -1) + { + int errsv = errno; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error copying file: %s"), + g_strerror (errsv)); + return FALSE; + } + + source_is_dir = S_ISDIR (statbuf.st_mode); + source_size = statbuf.st_size; + if (source_is_dir) + { + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("Can't copy directories")); + return FALSE; + } + else if (!S_ISREG (statbuf.st_mode) && !S_ISLNK (statbuf.st_mode)) + { + /* This will cause fallback code to run */ + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Can't copy special files")); + return FALSE; + } + /* Btrfs clone ioctl follows symlinks */ + else if (S_ISLNK (statbuf.st_mode) && (flags & G_FILE_COPY_NOFOLLOW_SYMLINKS)) + goto fallback; + + destination_exist = FALSE; + res = g_lstat (local_destination->filename, &statbuf); + if (res == 0) + { + destination_exist = TRUE; /* Target file exists */ + + if (flags & G_FILE_COPY_OVERWRITE) + { + /* Always fail on dirs, even with overwrite */ + if (S_ISDIR (statbuf.st_mode)) + { + if (source_is_dir) + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_WOULD_MERGE, + _("Can't copy directory over directory")); + else + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("Can't copy over directory")); + return FALSE; + } + } + else + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file exists")); + return FALSE; + } + } + + if (destination_exist && (flags & G_FILE_COPY_BACKUP)) + { + backup_name = g_strconcat (local_destination->filename, "~", NULL); + if (g_rename (local_destination->filename, backup_name) == -1) + { + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + g_free (backup_name); + return FALSE; + } + g_free (backup_name); + destination_exist = FALSE; /* It did, but no more */ + } + + if (destination_exist && (flags & G_FILE_COPY_OVERWRITE)) + /* Destination exists (and is not a dir, because that would have failed + * earlier), and we're overwriting. Manually remove the target so we can + * do the clone ioctl. This also takes care of the symlink-following + * behaviour of the clone ioctl if the destination is a symlink. */ + if (g_unlink (local_destination->filename) == -1) + { + int errsv = errno; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error removing target file: %s"), + g_strerror (errsv)); + return FALSE; + } + + source_fd = g_open (local_source->filename, O_RDONLY); + if (source_fd < 0) + { + int errsv = errno; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error copying file: %s"), + g_strerror (errsv)); + return FALSE; + } + + destination_fd = g_creat (local_destination->filename, 0666); + if (destination_fd < 0) + { + int errsv = errno; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error copying file: %s"), + g_strerror (errsv)); + return FALSE; + } + + res = ioctl (destination_fd, BTRFS_IOC_CLONE, source_fd); + close (source_fd); + close (destination_fd); + + if (res < 0) + { + if (errno == EXDEV) + /* This will cause the fallback code to run */ + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Copy (reflink/clone) between mounts not supported")); + else + /* Most probably something odd happened, + * or this is not a btrfs filesystem */ + goto fallback; + return FALSE; + } + + /* Ignore errors here. Failure to copy metadata is not a hard error */ + g_file_copy_attributes (source, destination, + flags, cancellable, NULL); + + /* Make sure we send full copied size */ + if (progress_callback) + progress_callback (source_size, source_size, progress_callback_data); + + return TRUE; + +fallback: +#endif /* Fall back to default copy */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported"); return FALSE; -- 1.7.1