cifs: add fiemap support
authorRonnie Sahlberg <lsahlber@redhat.com>
Thu, 25 Apr 2019 06:45:29 +0000 (16:45 +1000)
committerSteve French <stfrench@microsoft.com>
Wed, 8 May 2019 04:24:55 +0000 (23:24 -0500)
Useful for improved copy performance as well as for
applications which query allocated ranges of sparse
files.

Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/cifsfs.c
fs/cifs/cifsfs.h
fs/cifs/cifsglob.h
fs/cifs/inode.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c
fs/cifs/smb2pdu.h
fs/cifs/smbfsctl.h

index 54c065ada4de7818eb736df6970f6f63bc53a165..b1a5fcfa3ce126fd1877c02683ee4a4a2f8a23f3 100644 (file)
@@ -986,6 +986,7 @@ const struct inode_operations cifs_file_inode_ops = {
        .getattr = cifs_getattr,
        .permission = cifs_permission,
        .listxattr = cifs_listxattr,
+       .fiemap = cifs_fiemap,
 };
 
 const struct inode_operations cifs_symlink_inode_ops = {
index 5c0298b9998fc326795c435f6050c3563bdf09f4..c47d93d74d75c1a21985261009d1975e75472101 100644 (file)
@@ -84,6 +84,8 @@ extern int cifs_revalidate_mapping(struct inode *inode);
 extern int cifs_zap_mapping(struct inode *inode);
 extern int cifs_getattr(const struct path *, struct kstat *, u32, unsigned int);
 extern int cifs_setattr(struct dentry *, struct iattr *);
+extern int cifs_fiemap(struct inode *, struct fiemap_extent_info *, u64 start,
+                      u64 len);
 
 extern const struct inode_operations cifs_file_inode_ops;
 extern const struct inode_operations cifs_symlink_inode_ops;
index 0dc55f4e69291a2a8b78d874e074eb3361c51c9e..5ffe0e538cecb26d3895b665e032e524ad55c7f0 100644 (file)
@@ -493,6 +493,9 @@ struct smb_version_operations {
                         char *full_path,
                         umode_t mode,
                         dev_t device_number);
+       /* version specific fiemap implementation */
+       int (*fiemap)(struct cifs_tcon *tcon, struct cifsFileInfo *,
+                     struct fiemap_extent_info *, u64, u64);
 };
 
 struct smb_version_values {
index 538fd7d807e476f9998820b2abc93ae7f6a7c127..d7cc622526343dd6af9ec9ff7a83238a54b568dc 100644 (file)
@@ -2116,6 +2116,43 @@ int cifs_getattr(const struct path *path, struct kstat *stat,
        return rc;
 }
 
+int cifs_fiemap(struct inode *inode, struct fiemap_extent_info *fei, u64 start,
+               u64 len)
+{
+       struct cifsInodeInfo *cifs_i = CIFS_I(inode);
+       struct cifs_sb_info *cifs_sb = CIFS_SB(cifs_i->vfs_inode.i_sb);
+       struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+       struct TCP_Server_Info *server = tcon->ses->server;
+       struct cifsFileInfo *cfile;
+       int rc;
+
+       /*
+        * We need to be sure that all dirty pages are written as they
+        * might fill holes on the server.
+        */
+       if (!CIFS_CACHE_READ(CIFS_I(inode)) && inode->i_mapping &&
+           inode->i_mapping->nrpages != 0) {
+               rc = filemap_fdatawait(inode->i_mapping);
+               if (rc) {
+                       mapping_set_error(inode->i_mapping, rc);
+                       return rc;
+               }
+       }
+
+       cfile = find_readable_file(cifs_i, false);
+       if (cfile == NULL)
+               return -EINVAL;
+
+       if (server->ops->fiemap) {
+               rc = server->ops->fiemap(tcon, cfile, fei, start, len);
+               cifsFileInfo_put(cfile);
+               return rc;
+       }
+
+       cifsFileInfo_put(cfile);
+       return -ENOTSUPP;
+}
+
 static int cifs_truncate_page(struct address_space *mapping, loff_t from)
 {
        pgoff_t index = from >> PAGE_SHIFT;
index 4002e1433ccbada803e808fa74918706a19fa64c..78bca7d46eac4f4ab2f1697e46a9ade26b2c8ff4 100644 (file)
@@ -2886,6 +2886,79 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
        return rc;
 }
 
+static int smb3_fiemap(struct cifs_tcon *tcon,
+                      struct cifsFileInfo *cfile,
+                      struct fiemap_extent_info *fei, u64 start, u64 len)
+{
+       unsigned int xid;
+       struct file_allocated_range_buffer in_data, *out_data;
+       u32 out_data_len;
+       int i, num, rc, flags, last_blob;
+       u64 next;
+
+       if (fiemap_check_flags(fei, FIEMAP_FLAG_SYNC))
+               return -EBADR;
+
+       xid = get_xid();
+ again:
+       in_data.file_offset = cpu_to_le64(start);
+       in_data.length = cpu_to_le64(len);
+
+       rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
+                       cfile->fid.volatile_fid,
+                       FSCTL_QUERY_ALLOCATED_RANGES, true,
+                       (char *)&in_data, sizeof(in_data),
+                       1024 * sizeof(struct file_allocated_range_buffer),
+                       (char **)&out_data, &out_data_len);
+       if (rc == -E2BIG) {
+               last_blob = 0;
+               rc = 0;
+       } else
+               last_blob = 1;
+       if (rc)
+               goto out;
+
+       if (out_data_len < sizeof(struct file_allocated_range_buffer)) {
+               rc = -EINVAL;
+               goto out;
+       }
+       if (out_data_len % sizeof(struct file_allocated_range_buffer)) {
+               rc = -EINVAL;
+               goto out;
+       }
+
+       num = out_data_len / sizeof(struct file_allocated_range_buffer);
+       for (i = 0; i < num; i++) {
+               flags = 0;
+               if (i == num - 1 && last_blob)
+                       flags |= FIEMAP_EXTENT_LAST;
+
+               rc = fiemap_fill_next_extent(fei,
+                               le64_to_cpu(out_data[i].file_offset),
+                               le64_to_cpu(out_data[i].file_offset),
+                               le64_to_cpu(out_data[i].length),
+                               flags);
+               if (rc < 0)
+                       goto out;
+               if (rc == 1) {
+                       rc = 0;
+                       goto out;
+               }
+       }
+
+       if (!last_blob) {
+               next = le64_to_cpu(out_data[num - 1].file_offset) +
+                 le64_to_cpu(out_data[num - 1].length);
+               len = len - (next - start);
+               start = next;
+               goto again;
+       }
+
+ out:
+       free_xid(xid);
+       kfree(out_data);
+       return rc;
+}
 
 static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode,
                           loff_t off, loff_t len)
@@ -4054,6 +4127,7 @@ struct smb_version_operations smb20_operations = {
        .next_header = smb2_next_header,
        .ioctl_query_info = smb2_ioctl_query_info,
        .make_node = smb2_make_node,
+       .fiemap = smb3_fiemap,
 };
 
 struct smb_version_operations smb21_operations = {
@@ -4153,6 +4227,7 @@ struct smb_version_operations smb21_operations = {
        .next_header = smb2_next_header,
        .ioctl_query_info = smb2_ioctl_query_info,
        .make_node = smb2_make_node,
+       .fiemap = smb3_fiemap,
 };
 
 struct smb_version_operations smb30_operations = {
@@ -4261,6 +4336,7 @@ struct smb_version_operations smb30_operations = {
        .next_header = smb2_next_header,
        .ioctl_query_info = smb2_ioctl_query_info,
        .make_node = smb2_make_node,
+       .fiemap = smb3_fiemap,
 };
 
 struct smb_version_operations smb311_operations = {
@@ -4370,6 +4446,7 @@ struct smb_version_operations smb311_operations = {
        .next_header = smb2_next_header,
        .ioctl_query_info = smb2_ioctl_query_info,
        .make_node = smb2_make_node,
+       .fiemap = smb3_fiemap,
 };
 
 struct smb_version_values smb20_values = {
index a932dccafc5b8b36c80c8db4a7af0fd8a41ffd38..634800c0bc0687f9572939c4708858cd3d0f14f2 100644 (file)
@@ -2622,7 +2622,7 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
                trace_smb3_fsctl_err(xid, persistent_fid, tcon->tid,
                                ses->Suid, 0, opcode, rc);
 
-       if ((rc != 0) && (rc != -EINVAL)) {
+       if ((rc != 0) && (rc != -EINVAL) && (rc != -E2BIG)) {
                cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE);
                goto ioctl_exit;
        } else if (rc == -EINVAL) {
@@ -2631,6 +2631,11 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
                        cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE);
                        goto ioctl_exit;
                }
+       } else if (rc == -E2BIG) {
+               if (opcode != FSCTL_QUERY_ALLOCATED_RANGES) {
+                       cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE);
+                       goto ioctl_exit;
+               }
        }
 
        /* check if caller wants to look at return data or just return rc */
index 58158e91b9d1105a710a994952c3a478e25f07a1..82686e9d9e05b5275414298065f9bf644e56850c 100644 (file)
@@ -868,6 +868,11 @@ struct fsctl_get_integrity_information_rsp {
        __le32  ClusterSizeInBytes;
 } __packed;
 
+struct file_allocated_range_buffer {
+       __le64  file_offset;
+       __le64  length;
+} __packed;
+
 /* Integrity ChecksumAlgorithm choices for above */
 #define        CHECKSUM_TYPE_NONE      0x0000
 #define        CHECKSUM_TYPE_CRC64     0x0002
index 9b3459b9a5ce79b9f73b764eba6aebbf17de5aea..08628e6a42aca986d74f017e43d6d43aa7e61533 100644 (file)
 #define FSCTL_SET_ZERO_ON_DEALLOC    0x00090194 /* BB add struct */
 #define FSCTL_SET_SHORT_NAME_BEHAVIOR 0x000901B4 /* BB add struct */
 #define FSCTL_GET_INTEGRITY_INFORMATION 0x0009027C
-#define FSCTL_QUERY_ALLOCATED_RANGES 0x000940CF /* BB add struct */
+#define FSCTL_QUERY_ALLOCATED_RANGES 0x000940CF
 #define FSCTL_SET_DEFECT_MANAGEMENT  0x00098134 /* BB add struct */
 #define FSCTL_FILE_LEVEL_TRIM        0x00098208 /* BB add struct */
 #define FSCTL_DUPLICATE_EXTENTS_TO_FILE 0x00098344