/* * Module to make use of awesome Btrfs features * * Copyright (C) David Disseldorp 2011-2013 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include "includes.h" #include "system/filesys.h" #include "smbd/smbd.h" #include "../librpc/gen_ndr/smbXsrv.h" #include "lib/util/tevent_ntstatus.h" struct btrfs_ioctl_clone_range_args { int64_t src_fd; uint64_t src_offset; uint64_t src_length; uint64_t dest_offset; }; #define BTRFS_IOCTL_MAGIC 0x94 #define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \ struct btrfs_ioctl_clone_range_args) struct btrfs_cc_state { struct vfs_handle_struct *handle; off_t copied; struct tevent_req *subreq; /* non-null if passed to next VFS fn */ }; static void btrfs_copy_chunk_done(struct tevent_req *subreq); static struct tevent_req *btrfs_copy_chunk_send(struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct files_struct *src_fsp, off_t src_off, struct files_struct *dest_fsp, off_t dest_off, off_t num) { struct tevent_req *req; struct btrfs_cc_state *cc_state; struct btrfs_ioctl_clone_range_args cr_args; struct lock_struct src_lck; struct lock_struct dest_lck; int ret; NTSTATUS status; req = tevent_req_create(mem_ctx, &cc_state, struct btrfs_cc_state); if (req == NULL) { return NULL; } cc_state->handle = handle; if (num == 0) { /* * With a @src_length of zero, BTRFS_IOC_CLONE_RANGE clones * all data from @src_offset->EOF! This is certainly not what * the caller expects, and not what vfs_default does. */ cc_state->subreq = SMB_VFS_NEXT_COPY_CHUNK_SEND(handle, cc_state, ev, src_fsp, src_off, dest_fsp, dest_off, num); if (tevent_req_nomem(cc_state->subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(cc_state->subreq, btrfs_copy_chunk_done, req); return req; } status = vfs_stat_fsp(src_fsp); if (tevent_req_nterror(req, status)) { return tevent_req_post(req, ev); } if (src_fsp->fsp_name->st.st_ex_size < src_off + num) { /* [MS-SMB2] Handling a Server-Side Data Copy Request */ tevent_req_nterror(req, NT_STATUS_INVALID_VIEW_SIZE); return tevent_req_post(req, ev); } init_strict_lock_struct(src_fsp, src_fsp->op->global->open_persistent_id, src_off, num, READ_LOCK, &src_lck); init_strict_lock_struct(dest_fsp, dest_fsp->op->global->open_persistent_id, dest_off, num, WRITE_LOCK, &dest_lck); if (!SMB_VFS_STRICT_LOCK(src_fsp->conn, src_fsp, &src_lck)) { tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT); return tevent_req_post(req, ev); } if (!SMB_VFS_STRICT_LOCK(dest_fsp->conn, dest_fsp, &dest_lck)) { SMB_VFS_STRICT_UNLOCK(src_fsp->conn, src_fsp, &src_lck); tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT); return tevent_req_post(req, ev); } ZERO_STRUCT(cr_args); cr_args.src_fd = src_fsp->fh->fd; cr_args.src_offset = (uint64_t)src_off; cr_args.dest_offset = (uint64_t)dest_off; cr_args.src_length = (uint64_t)num; ret = ioctl(dest_fsp->fh->fd, BTRFS_IOC_CLONE_RANGE, &cr_args); SMB_VFS_STRICT_UNLOCK(src_fsp->conn, src_fsp, &dest_lck); SMB_VFS_STRICT_UNLOCK(src_fsp->conn, src_fsp, &src_lck); if (ret < 0) { /* * BTRFS_IOC_CLONE_RANGE only supports 'sectorsize' aligned * cloning. Which is 4096 by default, therefore fall back to * manual read/write on failure. */ DEBUG(5, ("BTRFS_IOC_CLONE_RANGE failed: %s, length %llu, " "src fd: %lld off: %llu, dest fd: %d off: %llu\n", strerror(errno), (unsigned long long)cr_args.src_length, (long long)cr_args.src_fd, (unsigned long long)cr_args.src_offset, dest_fsp->fh->fd, (unsigned long long)cr_args.dest_offset)); cc_state->subreq = SMB_VFS_NEXT_COPY_CHUNK_SEND(handle, cc_state, ev, src_fsp, src_off, dest_fsp, dest_off, num); if (tevent_req_nomem(cc_state->subreq, req)) { return tevent_req_post(req, ev); } /* wait for subreq completion */ tevent_req_set_callback(cc_state->subreq, btrfs_copy_chunk_done, req); return req; } DEBUG(5, ("BTRFS_IOC_CLONE_RANGE returned %d\n", ret)); /* BTRFS_IOC_CLONE_RANGE is all or nothing */ cc_state->copied = num; tevent_req_done(req); return tevent_req_post(req, ev); } /* only used if the request is passed through to next VFS module */ static void btrfs_copy_chunk_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct btrfs_cc_state *cc_state = tevent_req_data(req, struct btrfs_cc_state); NTSTATUS status; status = SMB_VFS_NEXT_COPY_CHUNK_RECV(cc_state->handle, cc_state->subreq, &cc_state->copied); if (tevent_req_nterror(req, status)) { return; } tevent_req_done(req); } static NTSTATUS btrfs_copy_chunk_recv(struct vfs_handle_struct *handle, struct tevent_req *req, off_t *copied) { NTSTATUS status; struct btrfs_cc_state *cc_state = tevent_req_data(req, struct btrfs_cc_state); if (tevent_req_is_nterror(req, &status)) { DEBUG(4, ("server side copy chunk failed: %s\n", nt_errstr(status))); tevent_req_received(req); return status; } DEBUG(10, ("server side copy chunk copied %llu\n", (unsigned long long)cc_state->copied)); *copied = cc_state->copied; tevent_req_received(req); return NT_STATUS_OK; } static struct vfs_fn_pointers btrfs_fns = { .copy_chunk_send_fn = btrfs_copy_chunk_send, .copy_chunk_recv_fn = btrfs_copy_chunk_recv, }; NTSTATUS vfs_btrfs_init(void); NTSTATUS vfs_btrfs_init(void) { return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "btrfs", &btrfs_fns); }