diff options
| -rw-r--r-- | fs/cifs/cifsglob.h | 3 | ||||
| -rw-r--r-- | fs/cifs/ioctl.c | 111 | ||||
| -rw-r--r-- | fs/cifs/smb2ops.c | 82 | ||||
| -rw-r--r-- | fs/cifs/smb2pdu.h | 15 |
4 files changed, 210 insertions, 1 deletions
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index cddb807addde..50a6ca1bb521 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h | |||
| @@ -381,6 +381,9 @@ struct smb_version_operations { | |||
| 381 | char * (*create_lease_buf)(u8 *, u8); | 381 | char * (*create_lease_buf)(u8 *, u8); |
| 382 | /* parse lease context buffer and return oplock/epoch info */ | 382 | /* parse lease context buffer and return oplock/epoch info */ |
| 383 | __u8 (*parse_lease_buf)(void *, unsigned int *); | 383 | __u8 (*parse_lease_buf)(void *, unsigned int *); |
| 384 | int (*clone_range)(const unsigned int, struct cifsFileInfo *src_file, | ||
| 385 | struct cifsFileInfo *target_file, u64 src_off, u64 len, | ||
| 386 | u64 dest_off); | ||
| 384 | }; | 387 | }; |
| 385 | 388 | ||
| 386 | struct smb_version_values { | 389 | struct smb_version_values { |
diff --git a/fs/cifs/ioctl.c b/fs/cifs/ioctl.c index ba54bf6ab116..409b45eefe70 100644 --- a/fs/cifs/ioctl.c +++ b/fs/cifs/ioctl.c | |||
| @@ -22,12 +22,120 @@ | |||
| 22 | */ | 22 | */ |
| 23 | 23 | ||
| 24 | #include <linux/fs.h> | 24 | #include <linux/fs.h> |
| 25 | #include <linux/file.h> | ||
| 26 | #include <linux/mount.h> | ||
| 27 | #include <linux/mm.h> | ||
| 28 | #include <linux/pagemap.h> | ||
| 29 | #include <linux/btrfs.h> | ||
| 25 | #include "cifspdu.h" | 30 | #include "cifspdu.h" |
| 26 | #include "cifsglob.h" | 31 | #include "cifsglob.h" |
| 27 | #include "cifsproto.h" | 32 | #include "cifsproto.h" |
| 28 | #include "cifs_debug.h" | 33 | #include "cifs_debug.h" |
| 29 | #include "cifsfs.h" | 34 | #include "cifsfs.h" |
| 30 | 35 | ||
| 36 | static long cifs_ioctl_clone(unsigned int xid, struct file *dst_file, | ||
| 37 | unsigned long srcfd, u64 off, u64 len, u64 destoff) | ||
| 38 | { | ||
| 39 | int rc; | ||
| 40 | struct cifsFileInfo *smb_file_target = dst_file->private_data; | ||
| 41 | struct inode *target_inode = file_inode(dst_file); | ||
| 42 | struct cifs_tcon *target_tcon; | ||
| 43 | struct fd src_file; | ||
| 44 | struct cifsFileInfo *smb_file_src; | ||
| 45 | struct inode *src_inode; | ||
| 46 | struct cifs_tcon *src_tcon; | ||
| 47 | |||
| 48 | cifs_dbg(FYI, "ioctl clone range\n"); | ||
| 49 | /* the destination must be opened for writing */ | ||
| 50 | if (!(dst_file->f_mode & FMODE_WRITE)) { | ||
| 51 | cifs_dbg(FYI, "file target not open for write\n"); | ||
| 52 | return -EINVAL; | ||
| 53 | } | ||
| 54 | |||
| 55 | /* check if target volume is readonly and take reference */ | ||
| 56 | rc = mnt_want_write_file(dst_file); | ||
| 57 | if (rc) { | ||
| 58 | cifs_dbg(FYI, "mnt_want_write failed with rc %d\n", rc); | ||
| 59 | return rc; | ||
| 60 | } | ||
| 61 | |||
| 62 | src_file = fdget(srcfd); | ||
| 63 | if (!src_file.file) { | ||
| 64 | rc = -EBADF; | ||
| 65 | goto out_drop_write; | ||
| 66 | } | ||
| 67 | |||
| 68 | if ((!src_file.file->private_data) || (!dst_file->private_data)) { | ||
| 69 | rc = -EBADF; | ||
| 70 | cifs_dbg(VFS, "missing cifsFileInfo on copy range src file\n"); | ||
| 71 | goto out_fput; | ||
| 72 | } | ||
| 73 | |||
| 74 | rc = -EXDEV; | ||
| 75 | smb_file_target = dst_file->private_data; | ||
| 76 | smb_file_src = src_file.file->private_data; | ||
| 77 | src_tcon = tlink_tcon(smb_file_src->tlink); | ||
| 78 | target_tcon = tlink_tcon(smb_file_target->tlink); | ||
| 79 | |||
| 80 | /* check if source and target are on same tree connection */ | ||
| 81 | if (src_tcon != target_tcon) { | ||
| 82 | cifs_dbg(VFS, "file copy src and target on different volume\n"); | ||
| 83 | goto out_fput; | ||
| 84 | } | ||
| 85 | |||
| 86 | src_inode = src_file.file->f_dentry->d_inode; | ||
| 87 | |||
| 88 | /* | ||
| 89 | * Note: cifs case is easier than btrfs since server responsible for | ||
| 90 | * checks for proper open modes and file type and if it wants | ||
| 91 | * server could even support copy of range where source = target | ||
| 92 | */ | ||
| 93 | |||
| 94 | /* so we do not deadlock racing two ioctls on same files */ | ||
| 95 | if (target_inode < src_inode) { | ||
| 96 | mutex_lock_nested(&target_inode->i_mutex, I_MUTEX_PARENT); | ||
| 97 | mutex_lock_nested(&src_inode->i_mutex, I_MUTEX_CHILD); | ||
| 98 | } else { | ||
| 99 | mutex_lock_nested(&src_inode->i_mutex, I_MUTEX_PARENT); | ||
| 100 | mutex_lock_nested(&target_inode->i_mutex, I_MUTEX_CHILD); | ||
| 101 | } | ||
| 102 | |||
| 103 | /* determine range to clone */ | ||
| 104 | rc = -EINVAL; | ||
| 105 | if (off + len > src_inode->i_size || off + len < off) | ||
| 106 | goto out_unlock; | ||
| 107 | if (len == 0) | ||
| 108 | len = src_inode->i_size - off; | ||
| 109 | |||
| 110 | cifs_dbg(FYI, "about to flush pages\n"); | ||
| 111 | /* should we flush first and last page first */ | ||
| 112 | truncate_inode_pages_range(&target_inode->i_data, destoff, | ||
| 113 | PAGE_CACHE_ALIGN(destoff + len)-1); | ||
| 114 | |||
| 115 | if (target_tcon->ses->server->ops->clone_range) | ||
| 116 | rc = target_tcon->ses->server->ops->clone_range(xid, | ||
| 117 | smb_file_src, smb_file_target, off, len, destoff); | ||
| 118 | |||
| 119 | /* force revalidate of size and timestamps of target file now | ||
| 120 | that target is updated on the server */ | ||
| 121 | CIFS_I(target_inode)->time = 0; | ||
| 122 | out_unlock: | ||
| 123 | /* although unlocking in the reverse order from locking is not | ||
| 124 | strictly necessary here it is a little cleaner to be consistent */ | ||
| 125 | if (target_inode < src_inode) { | ||
| 126 | mutex_unlock(&src_inode->i_mutex); | ||
| 127 | mutex_unlock(&target_inode->i_mutex); | ||
| 128 | } else { | ||
| 129 | mutex_unlock(&target_inode->i_mutex); | ||
| 130 | mutex_unlock(&src_inode->i_mutex); | ||
| 131 | } | ||
| 132 | out_fput: | ||
| 133 | fdput(src_file); | ||
| 134 | out_drop_write: | ||
| 135 | mnt_drop_write_file(dst_file); | ||
| 136 | return rc; | ||
| 137 | } | ||
| 138 | |||
| 31 | long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) | 139 | long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) |
| 32 | { | 140 | { |
| 33 | struct inode *inode = file_inode(filep); | 141 | struct inode *inode = file_inode(filep); |
| @@ -105,6 +213,9 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) | |||
| 105 | cifs_dbg(FYI, "set compress flag rc %d\n", rc); | 213 | cifs_dbg(FYI, "set compress flag rc %d\n", rc); |
| 106 | } | 214 | } |
| 107 | break; | 215 | break; |
| 216 | case BTRFS_IOC_CLONE: | ||
| 217 | rc = cifs_ioctl_clone(xid, filep, arg, 0, 0, 0); | ||
| 218 | break; | ||
| 108 | default: | 219 | default: |
| 109 | cifs_dbg(FYI, "unsupported ioctl\n"); | 220 | cifs_dbg(FYI, "unsupported ioctl\n"); |
| 110 | break; | 221 | break; |
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index c571be8cb76e..11dde4b24f8a 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c | |||
| @@ -494,6 +494,85 @@ smb2_close_file(const unsigned int xid, struct cifs_tcon *tcon, | |||
| 494 | } | 494 | } |
| 495 | 495 | ||
| 496 | static int | 496 | static int |
| 497 | SMB2_request_res_key(const unsigned int xid, struct cifs_tcon *tcon, | ||
| 498 | u64 persistent_fid, u64 volatile_fid, | ||
| 499 | struct copychunk_ioctl *pcchunk) | ||
| 500 | { | ||
| 501 | int rc; | ||
| 502 | unsigned int ret_data_len; | ||
| 503 | struct resume_key_req *res_key; | ||
| 504 | |||
| 505 | rc = SMB2_ioctl(xid, tcon, persistent_fid, volatile_fid, | ||
| 506 | FSCTL_SRV_REQUEST_RESUME_KEY, true /* is_fsctl */, | ||
| 507 | NULL, 0 /* no input */, | ||
| 508 | (char **)&res_key, &ret_data_len); | ||
| 509 | |||
| 510 | if (rc) { | ||
| 511 | cifs_dbg(VFS, "refcpy ioctl error %d getting resume key\n", rc); | ||
| 512 | goto req_res_key_exit; | ||
| 513 | } | ||
| 514 | if (ret_data_len < sizeof(struct resume_key_req)) { | ||
| 515 | cifs_dbg(VFS, "Invalid refcopy resume key length\n"); | ||
| 516 | rc = -EINVAL; | ||
| 517 | goto req_res_key_exit; | ||
| 518 | } | ||
| 519 | memcpy(pcchunk->SourceKey, res_key->ResumeKey, COPY_CHUNK_RES_KEY_SIZE); | ||
| 520 | |||
| 521 | req_res_key_exit: | ||
| 522 | kfree(res_key); | ||
| 523 | return rc; | ||
| 524 | } | ||
| 525 | |||
| 526 | static int | ||
| 527 | smb2_clone_range(const unsigned int xid, | ||
| 528 | struct cifsFileInfo *srcfile, | ||
| 529 | struct cifsFileInfo *trgtfile, u64 src_off, | ||
| 530 | u64 len, u64 dest_off) | ||
| 531 | { | ||
| 532 | int rc; | ||
| 533 | unsigned int ret_data_len; | ||
| 534 | struct copychunk_ioctl *pcchunk; | ||
| 535 | char *retbuf = NULL; | ||
| 536 | |||
| 537 | pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL); | ||
| 538 | |||
| 539 | if (pcchunk == NULL) | ||
| 540 | return -ENOMEM; | ||
| 541 | |||
| 542 | cifs_dbg(FYI, "in smb2_clone_range - about to call request res key\n"); | ||
| 543 | /* Request a key from the server to identify the source of the copy */ | ||
| 544 | rc = SMB2_request_res_key(xid, tlink_tcon(srcfile->tlink), | ||
| 545 | srcfile->fid.persistent_fid, | ||
| 546 | srcfile->fid.volatile_fid, pcchunk); | ||
| 547 | |||
| 548 | /* Note: request_res_key sets res_key null only if rc !=0 */ | ||
| 549 | if (rc) | ||
| 550 | return rc; | ||
| 551 | |||
| 552 | /* For now array only one chunk long, will make more flexible later */ | ||
| 553 | pcchunk->ChunkCount = __constant_cpu_to_le32(1); | ||
| 554 | pcchunk->Reserved = 0; | ||
| 555 | pcchunk->SourceOffset = cpu_to_le64(src_off); | ||
| 556 | pcchunk->TargetOffset = cpu_to_le64(dest_off); | ||
| 557 | pcchunk->Length = cpu_to_le32(len); | ||
| 558 | pcchunk->Reserved2 = 0; | ||
| 559 | |||
| 560 | /* Request that server copy to target from src file identified by key */ | ||
| 561 | rc = SMB2_ioctl(xid, tlink_tcon(trgtfile->tlink), | ||
| 562 | trgtfile->fid.persistent_fid, | ||
| 563 | trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE, | ||
| 564 | true /* is_fsctl */, (char *)pcchunk, | ||
| 565 | sizeof(struct copychunk_ioctl), &retbuf, &ret_data_len); | ||
| 566 | |||
| 567 | /* BB need to special case rc = EINVAL to alter chunk size */ | ||
| 568 | |||
| 569 | cifs_dbg(FYI, "rc %d data length out %d\n", rc, ret_data_len); | ||
| 570 | |||
| 571 | kfree(pcchunk); | ||
| 572 | return rc; | ||
| 573 | } | ||
| 574 | |||
| 575 | static int | ||
| 497 | smb2_flush_file(const unsigned int xid, struct cifs_tcon *tcon, | 576 | smb2_flush_file(const unsigned int xid, struct cifs_tcon *tcon, |
| 498 | struct cifs_fid *fid) | 577 | struct cifs_fid *fid) |
| 499 | { | 578 | { |
| @@ -1017,6 +1096,7 @@ struct smb_version_operations smb20_operations = { | |||
| 1017 | .set_oplock_level = smb2_set_oplock_level, | 1096 | .set_oplock_level = smb2_set_oplock_level, |
| 1018 | .create_lease_buf = smb2_create_lease_buf, | 1097 | .create_lease_buf = smb2_create_lease_buf, |
| 1019 | .parse_lease_buf = smb2_parse_lease_buf, | 1098 | .parse_lease_buf = smb2_parse_lease_buf, |
| 1099 | .clone_range = smb2_clone_range, | ||
| 1020 | }; | 1100 | }; |
| 1021 | 1101 | ||
| 1022 | struct smb_version_operations smb21_operations = { | 1102 | struct smb_version_operations smb21_operations = { |
| @@ -1090,6 +1170,7 @@ struct smb_version_operations smb21_operations = { | |||
| 1090 | .set_oplock_level = smb21_set_oplock_level, | 1170 | .set_oplock_level = smb21_set_oplock_level, |
| 1091 | .create_lease_buf = smb2_create_lease_buf, | 1171 | .create_lease_buf = smb2_create_lease_buf, |
| 1092 | .parse_lease_buf = smb2_parse_lease_buf, | 1172 | .parse_lease_buf = smb2_parse_lease_buf, |
| 1173 | .clone_range = smb2_clone_range, | ||
| 1093 | }; | 1174 | }; |
| 1094 | 1175 | ||
| 1095 | struct smb_version_operations smb30_operations = { | 1176 | struct smb_version_operations smb30_operations = { |
| @@ -1165,6 +1246,7 @@ struct smb_version_operations smb30_operations = { | |||
| 1165 | .set_oplock_level = smb3_set_oplock_level, | 1246 | .set_oplock_level = smb3_set_oplock_level, |
| 1166 | .create_lease_buf = smb3_create_lease_buf, | 1247 | .create_lease_buf = smb3_create_lease_buf, |
| 1167 | .parse_lease_buf = smb3_parse_lease_buf, | 1248 | .parse_lease_buf = smb3_parse_lease_buf, |
| 1249 | .clone_range = smb2_clone_range, | ||
| 1168 | }; | 1250 | }; |
| 1169 | 1251 | ||
| 1170 | struct smb_version_values smb20_values = { | 1252 | struct smb_version_values smb20_values = { |
diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h index 6183b1b7550f..b50a129572cd 100644 --- a/fs/cifs/smb2pdu.h +++ b/fs/cifs/smb2pdu.h | |||
| @@ -534,9 +534,16 @@ struct create_durable { | |||
| 534 | } Data; | 534 | } Data; |
| 535 | } __packed; | 535 | } __packed; |
| 536 | 536 | ||
| 537 | #define COPY_CHUNK_RES_KEY_SIZE 24 | ||
| 538 | struct resume_key_req { | ||
| 539 | char ResumeKey[COPY_CHUNK_RES_KEY_SIZE]; | ||
| 540 | __le32 ContextLength; /* MBZ */ | ||
| 541 | char Context[0]; /* ignored, Windows sets to 4 bytes of zero */ | ||
| 542 | } __packed; | ||
| 543 | |||
| 537 | /* this goes in the ioctl buffer when doing a copychunk request */ | 544 | /* this goes in the ioctl buffer when doing a copychunk request */ |
| 538 | struct copychunk_ioctl { | 545 | struct copychunk_ioctl { |
| 539 | char SourceKey[24]; | 546 | char SourceKey[COPY_CHUNK_RES_KEY_SIZE]; |
| 540 | __le32 ChunkCount; /* we are only sending 1 */ | 547 | __le32 ChunkCount; /* we are only sending 1 */ |
| 541 | __le32 Reserved; | 548 | __le32 Reserved; |
| 542 | /* array will only be one chunk long for us */ | 549 | /* array will only be one chunk long for us */ |
| @@ -546,6 +553,12 @@ struct copychunk_ioctl { | |||
| 546 | __u32 Reserved2; | 553 | __u32 Reserved2; |
| 547 | } __packed; | 554 | } __packed; |
| 548 | 555 | ||
| 556 | struct copychunk_ioctl_rsp { | ||
| 557 | __le32 ChunksWritten; | ||
| 558 | __le32 ChunkBytesWritten; | ||
| 559 | __le32 TotalBytesWritten; | ||
| 560 | } __packed; | ||
| 561 | |||
| 549 | /* Response and Request are the same format */ | 562 | /* Response and Request are the same format */ |
| 550 | struct validate_negotiate_info { | 563 | struct validate_negotiate_info { |
| 551 | __le32 Capabilities; | 564 | __le32 Capabilities; |
