Skip to content

Commit

Permalink
libfuse: add copy_file_range() support
Browse files Browse the repository at this point in the history
Add support for the relatively new copy_file_range() syscall. Backend
filesystems can now implement an efficient way of cloning/duplicating
data ranges within files. See 'man 2 copy_file_range' for more details.
  • Loading branch information
nixpanic authored and Nikratio committed Nov 19, 2018
1 parent 4c699e7 commit fe4f942
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 45 deletions.
22 changes: 22 additions & 0 deletions include/fuse.h
Expand Up @@ -750,6 +750,23 @@ struct fuse_operations {
*/
int (*fallocate) (const char *, int, off_t, off_t,
struct fuse_file_info *);

/**
* Copy a range of data from one file to another
*
* Performs an optimized copy between two file descriptors without the
* additional cost of transferring data through the FUSE kernel module
* to user space (glibc) and then back into the FUSE filesystem again.
*
* In case this method is not implemented, glibc falls back to reading
* data from the source and writing to the destination. Effectively
* doing an inefficient copy of the data.
*/
ssize_t (*copy_file_range) (const char *path_in,
struct fuse_file_info *fi_in,
off_t offset_in, const char *path_out,
struct fuse_file_info *fi_out,
off_t offset_out, size_t size, int flags);
};

/** Extra context that may be needed by some filesystems
Expand Down Expand Up @@ -1165,6 +1182,11 @@ int fuse_fs_poll(struct fuse_fs *fs, const char *path,
unsigned *reventsp);
int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
off_t offset, off_t length, struct fuse_file_info *fi);
ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
struct fuse_file_info *fi_in, off_t off_in,
const char *path_out,
struct fuse_file_info *fi_out, off_t off_out,
size_t len, int flags);
void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
struct fuse_config *cfg);
void fuse_fs_destroy(struct fuse_fs *fs);
Expand Down
104 changes: 59 additions & 45 deletions include/fuse_kernel.h
Expand Up @@ -116,6 +116,9 @@
*
* 7.27
* - add FUSE_ABORT_ERROR
*
* 7.28
* - add FUSE_COPY_FILE_RANGE
*/

#ifndef _LINUX_FUSE_H
Expand Down Expand Up @@ -337,53 +340,54 @@ struct fuse_file_lock {
#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)

enum fuse_opcode {
FUSE_LOOKUP = 1,
FUSE_FORGET = 2, /* no reply */
FUSE_GETATTR = 3,
FUSE_SETATTR = 4,
FUSE_READLINK = 5,
FUSE_SYMLINK = 6,
FUSE_MKNOD = 8,
FUSE_MKDIR = 9,
FUSE_UNLINK = 10,
FUSE_RMDIR = 11,
FUSE_RENAME = 12,
FUSE_LINK = 13,
FUSE_OPEN = 14,
FUSE_READ = 15,
FUSE_WRITE = 16,
FUSE_STATFS = 17,
FUSE_RELEASE = 18,
FUSE_FSYNC = 20,
FUSE_SETXATTR = 21,
FUSE_GETXATTR = 22,
FUSE_LISTXATTR = 23,
FUSE_REMOVEXATTR = 24,
FUSE_FLUSH = 25,
FUSE_INIT = 26,
FUSE_OPENDIR = 27,
FUSE_READDIR = 28,
FUSE_RELEASEDIR = 29,
FUSE_FSYNCDIR = 30,
FUSE_GETLK = 31,
FUSE_SETLK = 32,
FUSE_SETLKW = 33,
FUSE_ACCESS = 34,
FUSE_CREATE = 35,
FUSE_INTERRUPT = 36,
FUSE_BMAP = 37,
FUSE_DESTROY = 38,
FUSE_IOCTL = 39,
FUSE_POLL = 40,
FUSE_NOTIFY_REPLY = 41,
FUSE_BATCH_FORGET = 42,
FUSE_FALLOCATE = 43,
FUSE_READDIRPLUS = 44,
FUSE_RENAME2 = 45,
FUSE_LSEEK = 46,
FUSE_LOOKUP = 1,
FUSE_FORGET = 2, /* no reply */
FUSE_GETATTR = 3,
FUSE_SETATTR = 4,
FUSE_READLINK = 5,
FUSE_SYMLINK = 6,
FUSE_MKNOD = 8,
FUSE_MKDIR = 9,
FUSE_UNLINK = 10,
FUSE_RMDIR = 11,
FUSE_RENAME = 12,
FUSE_LINK = 13,
FUSE_OPEN = 14,
FUSE_READ = 15,
FUSE_WRITE = 16,
FUSE_STATFS = 17,
FUSE_RELEASE = 18,
FUSE_FSYNC = 20,
FUSE_SETXATTR = 21,
FUSE_GETXATTR = 22,
FUSE_LISTXATTR = 23,
FUSE_REMOVEXATTR = 24,
FUSE_FLUSH = 25,
FUSE_INIT = 26,
FUSE_OPENDIR = 27,
FUSE_READDIR = 28,
FUSE_RELEASEDIR = 29,
FUSE_FSYNCDIR = 30,
FUSE_GETLK = 31,
FUSE_SETLK = 32,
FUSE_SETLKW = 33,
FUSE_ACCESS = 34,
FUSE_CREATE = 35,
FUSE_INTERRUPT = 36,
FUSE_BMAP = 37,
FUSE_DESTROY = 38,
FUSE_IOCTL = 39,
FUSE_POLL = 40,
FUSE_NOTIFY_REPLY = 41,
FUSE_BATCH_FORGET = 42,
FUSE_FALLOCATE = 43,
FUSE_READDIRPLUS = 44,
FUSE_RENAME2 = 45,
FUSE_LSEEK = 46,
FUSE_COPY_FILE_RANGE = 47,

/* CUSE specific operations */
CUSE_INIT = 4096,
CUSE_INIT = 4096,
};

enum fuse_notify_code {
Expand Down Expand Up @@ -792,4 +796,14 @@ struct fuse_lseek_out {
uint64_t offset;
};

struct fuse_copy_file_range_in {
uint64_t fh_in;
uint64_t off_in;
uint64_t nodeid_out;
uint64_t fh_out;
uint64_t off_out;
uint64_t len;
uint64_t flags;
};

#endif /* _LINUX_FUSE_H */
36 changes: 36 additions & 0 deletions include/fuse_lowlevel.h
Expand Up @@ -1160,6 +1160,42 @@ struct fuse_lowlevel_ops {
*/
void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
struct fuse_file_info *fi);

/**
* Copy a range of data from one file to another
*
* Performs an optimized copy between two file descriptors without the
* additional cost of transferring data through the FUSE kernel module
* to user space (glibc) and then back into the FUSE filesystem again.
*
* In case this method is not implemented, glibc falls back to reading
* data from the source and writing to the destination. Effectively
* doing an inefficient copy of the data.
*
* If this request is answered with an error code of ENOSYS, this is
* treated as a permanent failure with error code EOPNOTSUPP, i.e. all
* future copy_file_range() requests will fail with EOPNOTSUPP without
* being send to the filesystem process.
*
* Valid replies:
* fuse_reply_write
* fuse_reply_err
*
* @param req request handle
* @param ino_in the inode number or the source file
* @param off_in starting point from were the data should be read
* @param fi_in file information of the source file
* @param ino_out the inode number or the destination file
* @param off_out starting point where the data should be written
* @param fi_out file information of the destination file
* @param len maximum size of the data to copy
* @param flags passed along with the copy_file_range() syscall
*/
void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in,
off_t off_in, struct fuse_file_info *fi_in,
fuse_ino_t ino_out, off_t off_out,
struct fuse_file_info *fi_out, size_t len,
int flags);
};

/**
Expand Down
63 changes: 63 additions & 0 deletions lib/fuse.c
Expand Up @@ -2359,6 +2359,29 @@ int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
return -ENOSYS;
}

ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
struct fuse_file_info *fi_in, off_t off_in,
const char *path_out,
struct fuse_file_info *fi_out, off_t off_out,
size_t len, int flags)
{
fuse_get_context()->private_data = fs->user_data;
if (fs->op.copy_file_range) {
if (fs->debug)
fprintf(stderr, "copy_file_range from %s:%llu to "
"%s:%llu, length: %llu\n",
path_in,
(unsigned long long) off_in,
path_out,
(unsigned long long) off_out,
(unsigned long long) len);

return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
fi_out, off_out, len, flags);
} else
return -ENOSYS;
}

static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
{
struct node *node;
Expand Down Expand Up @@ -4290,6 +4313,45 @@ static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
reply_err(req, err);
}

static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
off_t off_in, struct fuse_file_info *fi_in,
fuse_ino_t nodeid_out, off_t off_out,
struct fuse_file_info *fi_out, size_t len,
int flags)
{
struct fuse *f = req_fuse_prepare(req);
struct fuse_intr_data d;
char *path_in, *path_out;
int err;
ssize_t res;

err = get_path_nullok(f, nodeid_in, &path_in);
if (err) {
reply_err(req, err);
return;
}

err = get_path_nullok(f, nodeid_out, &path_out);
if (err) {
free_path(f, nodeid_in, path_in);
reply_err(req, err);
return;
}

fuse_prepare_interrupt(f, req, &d);
res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
fi_out, off_out, len, flags);
fuse_finish_interrupt(f, req, &d);

if (res >= 0)
fuse_reply_write(req, res);
else
reply_err(req, res);

free_path(f, nodeid_in, path_in);
free_path(f, nodeid_out, path_out);
}

static int clean_delay(struct fuse *f)
{
/*
Expand Down Expand Up @@ -4386,6 +4448,7 @@ static struct fuse_lowlevel_ops fuse_path_ops = {
.ioctl = fuse_lib_ioctl,
.poll = fuse_lib_poll,
.fallocate = fuse_lib_fallocate,
.copy_file_range = fuse_lib_copy_file_range,
};

int fuse_notify_poll(struct fuse_pollhandle *ph)
Expand Down
22 changes: 22 additions & 0 deletions lib/fuse_lowlevel.c
Expand Up @@ -1810,6 +1810,27 @@ static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
fuse_reply_err(req, ENOSYS);
}

static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg)
{
struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg;
struct fuse_file_info fi_in, fi_out;

memset(&fi_in, 0, sizeof(fi_in));
fi_in.fh = arg->fh_in;

memset(&fi_out, 0, sizeof(fi_out));
fi_out.fh = arg->fh_out;


if (req->se->op.copy_file_range)
req->se->op.copy_file_range(req, nodeid_in, arg->off_in,
&fi_in, arg->nodeid_out,
arg->off_out, &fi_out, arg->len,
arg->flags);
else
fuse_reply_err(req, ENOSYS);
}

static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
{
struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
Expand Down Expand Up @@ -2395,6 +2416,7 @@ static struct {
[FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
[FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
[FUSE_RENAME2] = { do_rename2, "RENAME2" },
[FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
[CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
};

Expand Down
6 changes: 6 additions & 0 deletions lib/fuse_versionscript
Expand Up @@ -153,6 +153,12 @@ FUSE_3.3 {
fuse_open_channel;
} FUSE_3.2;

FUSE_3.4 {
global:
fuse_fs_copy_file_range;
fuse_reply_copy_file_range;
} FUSE_3.3;

# Local Variables:
# indent-tabs-mode: t
# End:

0 comments on commit fe4f942

Please sign in to comment.