diff mbox series

cifs: add SEEK_HOLE/SEEK_DATA support

Message ID 20190417063410.6371-1-lsahlber@redhat.com (mailing list archive)
State New, archived
Headers show
Series cifs: add SEEK_HOLE/SEEK_DATA support | expand

Commit Message

Ronnie Sahlberg April 17, 2019, 6:34 a.m. UTC
Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
---
 fs/cifs/cifsfs.c   |  9 ++++++++
 fs/cifs/cifsglob.h |  2 ++
 fs/cifs/smb2ops.c  | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/cifs/smb2pdu.c  |  7 +++++-
 fs/cifs/smb2pdu.h  |  5 +++++
 fs/cifs/smbfsctl.h |  2 +-
 6 files changed, 88 insertions(+), 2 deletions(-)

Comments

Pavel Shilovsky April 17, 2019, 9:41 p.m. UTC | #1
--
Best regards,
Pavel Shilovsky

вт, 16 апр. 2019 г. в 23:35, Ronnie Sahlberg <lsahlber@redhat.com>:
>
> Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
> ---
>  fs/cifs/cifsfs.c   |  9 ++++++++
>  fs/cifs/cifsglob.h |  2 ++
>  fs/cifs/smb2ops.c  | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  fs/cifs/smb2pdu.c  |  7 +++++-
>  fs/cifs/smb2pdu.h  |  5 +++++
>  fs/cifs/smbfsctl.h |  2 +-
>  6 files changed, 88 insertions(+), 2 deletions(-)
>
> diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
> index 07fdf1771add..a00ef6c6a988 100644
> --- a/fs/cifs/cifsfs.c
> +++ b/fs/cifs/cifsfs.c
> @@ -884,6 +884,9 @@ static ssize_t cifs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
>
>  static loff_t cifs_llseek(struct file *file, loff_t offset, int whence)
>  {
> +       struct cifsFileInfo *cfile = file->private_data;
> +       struct cifs_tcon *tcon;
> +
>         /*
>          * whence == SEEK_END || SEEK_DATA || SEEK_HOLE => we must revalidate
>          * the cached file length
> @@ -915,6 +918,12 @@ static loff_t cifs_llseek(struct file *file, loff_t offset, int whence)
>                 if (rc < 0)
>                         return (loff_t)rc;
>         }
> +       if (cfile && cfile->tlink) {
> +               tcon = tlink_tcon(cfile->tlink);
> +               if (tcon->ses->server->ops->llseek)
> +                       return tcon->ses->server->ops->llseek(file, tcon,
> +                                                             offset, whence);
> +       }
>         return generic_file_llseek(file, offset, whence);
>  }
>
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index 0dc55f4e6929..828772da5fa4 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -493,6 +493,8 @@ struct smb_version_operations {
>                          char *full_path,
>                          umode_t mode,
>                          dev_t device_number);
> +       /* version specific llseek implementation */
> +       loff_t (*llseek)(struct file *, struct cifs_tcon *, loff_t, int);
>  };
>
>  struct smb_version_values {
> diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
> index 2824b97c5869..dc36316b9bd5 100644
> --- a/fs/cifs/smb2ops.c
> +++ b/fs/cifs/smb2ops.c
> @@ -2872,6 +2872,69 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
>         return rc;
>  }
>
> +static loff_t smb3_llseek(struct file *file, struct cifs_tcon *tcon, loff_t offset, int whence)
> +{
> +       struct cifsFileInfo *cfile = file->private_data;
> +       struct cifsInodeInfo *cifsi;
> +       struct inode *inode;
> +       int rc = 0;
> +       struct fsctl_query_allocate_ranges in_data, *out_data = NULL;
> +       u32 out_data_len;
> +       unsigned int xid;
> +
> +       if (whence != SEEK_HOLE && whence != SEEK_DATA)
> +               return generic_file_llseek(file, offset, whence);
> +
> +       inode = d_inode(cfile->dentry);
> +       cifsi = CIFS_I(inode);
> +
> +       if (offset < 0 || offset > i_size_read(inode))
> +               return -ENXIO;
> +
> +       if (!(cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)) {
> +               if (whence == SEEK_HOLE)
> +                       offset = i_size_read(inode);
> +               else
> +                       offset = offset;
> +               goto lseek_exit;
> +       }
> +
> +       in_data.file_offset = cpu_to_le64(offset);
> +       in_data.length = cpu_to_le64(i_size_read(inode));
> +
> +       xid = get_xid();
> +       rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
> +                       cfile->fid.volatile_fid,
> +                       FSCTL_QUERY_ALLOCATED_RANGES, true,
> +                       (char *)&in_data, sizeof(in_data),
> +                       sizeof(struct fsctl_query_allocate_ranges),
> +                       (char **)&out_data, &out_data_len);
> +       free_xid(xid);
> +       if (rc == -E2BIG)
> +               rc = 0;
> +       if (rc)
> +               goto lseek_exit;
> +
> +       if (out_data_len < sizeof(struct fsctl_query_allocate_ranges)) {
> +               rc = -EINVAL;
> +               goto lseek_exit;
> +       }
> +       if (whence == SEEK_DATA) {
> +               offset = le64_to_cpu(out_data->file_offset);
> +               goto lseek_exit;
> +       }
> +       if (offset < le64_to_cpu(out_data->file_offset))
> +               goto lseek_exit;
> +
> +       offset = le64_to_cpu(out_data->file_offset) + le64_to_cpu(out_data->length);
> +
> + lseek_exit:
> +       kfree(out_data);
> +       if (!rc)
> +               return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
> +       else
> +               return rc;
> +}
>
>  static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode,
>                            loff_t off, loff_t len)
> @@ -4247,6 +4310,7 @@ struct smb_version_operations smb30_operations = {
>         .next_header = smb2_next_header,
>         .ioctl_query_info = smb2_ioctl_query_info,
>         .make_node = smb2_make_node,
> +       .llseek = smb3_llseek,
>  };
>
>  struct smb_version_operations smb311_operations = {
> @@ -4356,6 +4420,7 @@ struct smb_version_operations smb311_operations = {
>         .next_header = smb2_next_header,
>         .ioctl_query_info = smb2_ioctl_query_info,
>         .make_node = smb2_make_node,
> +       .llseek = smb3_llseek,
>  };
>
>  struct smb_version_values smb20_values = {
> diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
> index 99a015e980d2..ac92d066c724 100644
> --- a/fs/cifs/smb2pdu.c
> +++ b/fs/cifs/smb2pdu.c
> @@ -2625,7 +2625,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) {
> @@ -2634,6 +2634,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 */
> diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h
> index ee8977688e21..3a29db740fe6 100644
> --- a/fs/cifs/smb2pdu.h
> +++ b/fs/cifs/smb2pdu.h
> @@ -842,6 +842,11 @@ struct fsctl_get_integrity_information_rsp {
>         __le32  ClusterSizeInBytes;
>  } __packed;
>
> +struct fsctl_query_allocate_ranges {

This describes one range, so should be fsctl_query_allocate_range or
even file_allocated_range_buffer as specified here:

https://docs.microsoft.com/en-us/windows/desktop/api/winioctl/ns-winioctl-_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
> diff --git a/fs/cifs/smbfsctl.h b/fs/cifs/smbfsctl.h
> index 9b3459b9a5ce..08628e6a42ac 100644
> --- a/fs/cifs/smbfsctl.h
> +++ b/fs/cifs/smbfsctl.h
> @@ -103,7 +103,7 @@
>  #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
> --
> 2.13.6
>
Ronnie Sahlberg April 18, 2019, 12:40 a.m. UTC | #2
----- Original Message -----
> From: "Pavel Shilovsky" <piastryyy@gmail.com>
> To: "Ronnie Sahlberg" <lsahlber@redhat.com>
> Cc: "linux-cifs" <linux-cifs@vger.kernel.org>, "Steve French" <smfrench@gmail.com>
> Sent: Thursday, 18 April, 2019 7:41:44 AM
> Subject: Re: [PATCH] cifs: add SEEK_HOLE/SEEK_DATA support
> 
> --
> Best regards,
> Pavel Shilovsky
> 
> вт, 16 апр. 2019 г. в 23:35, Ronnie Sahlberg <lsahlber@redhat.com>:
> >
> > Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
> > ---
> >  fs/cifs/cifsfs.c   |  9 ++++++++
> >  fs/cifs/cifsglob.h |  2 ++
> >  fs/cifs/smb2ops.c  | 65
> >  ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  fs/cifs/smb2pdu.c  |  7 +++++-
> >  fs/cifs/smb2pdu.h  |  5 +++++
> >  fs/cifs/smbfsctl.h |  2 +-
> >  6 files changed, 88 insertions(+), 2 deletions(-)
> >
> > diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
> > index 07fdf1771add..a00ef6c6a988 100644
> > --- a/fs/cifs/cifsfs.c
> > +++ b/fs/cifs/cifsfs.c
> > @@ -884,6 +884,9 @@ static ssize_t cifs_file_write_iter(struct kiocb *iocb,
> > struct iov_iter *from)
> >
> >  static loff_t cifs_llseek(struct file *file, loff_t offset, int whence)
> >  {
> > +       struct cifsFileInfo *cfile = file->private_data;
> > +       struct cifs_tcon *tcon;
> > +
> >         /*
> >          * whence == SEEK_END || SEEK_DATA || SEEK_HOLE => we must
> >          revalidate
> >          * the cached file length
> > @@ -915,6 +918,12 @@ static loff_t cifs_llseek(struct file *file, loff_t
> > offset, int whence)
> >                 if (rc < 0)
> >                         return (loff_t)rc;
> >         }
> > +       if (cfile && cfile->tlink) {
> > +               tcon = tlink_tcon(cfile->tlink);
> > +               if (tcon->ses->server->ops->llseek)
> > +                       return tcon->ses->server->ops->llseek(file, tcon,
> > +                                                             offset,
> > whence);
> > +       }
> >         return generic_file_llseek(file, offset, whence);
> >  }
> >
> > diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> > index 0dc55f4e6929..828772da5fa4 100644
> > --- a/fs/cifs/cifsglob.h
> > +++ b/fs/cifs/cifsglob.h
> > @@ -493,6 +493,8 @@ struct smb_version_operations {
> >                          char *full_path,
> >                          umode_t mode,
> >                          dev_t device_number);
> > +       /* version specific llseek implementation */
> > +       loff_t (*llseek)(struct file *, struct cifs_tcon *, loff_t, int);
> >  };
> >
> >  struct smb_version_values {
> > diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
> > index 2824b97c5869..dc36316b9bd5 100644
> > --- a/fs/cifs/smb2ops.c
> > +++ b/fs/cifs/smb2ops.c
> > @@ -2872,6 +2872,69 @@ static long smb3_simple_falloc(struct file *file,
> > struct cifs_tcon *tcon,
> >         return rc;
> >  }
> >
> > +static loff_t smb3_llseek(struct file *file, struct cifs_tcon *tcon,
> > loff_t offset, int whence)
> > +{
> > +       struct cifsFileInfo *cfile = file->private_data;
> > +       struct cifsInodeInfo *cifsi;
> > +       struct inode *inode;
> > +       int rc = 0;
> > +       struct fsctl_query_allocate_ranges in_data, *out_data = NULL;
> > +       u32 out_data_len;
> > +       unsigned int xid;
> > +
> > +       if (whence != SEEK_HOLE && whence != SEEK_DATA)
> > +               return generic_file_llseek(file, offset, whence);
> > +
> > +       inode = d_inode(cfile->dentry);
> > +       cifsi = CIFS_I(inode);
> > +
> > +       if (offset < 0 || offset > i_size_read(inode))
> > +               return -ENXIO;
> > +
> > +       if (!(cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)) {
> > +               if (whence == SEEK_HOLE)
> > +                       offset = i_size_read(inode);
> > +               else
> > +                       offset = offset;
> > +               goto lseek_exit;
> > +       }
> > +
> > +       in_data.file_offset = cpu_to_le64(offset);
> > +       in_data.length = cpu_to_le64(i_size_read(inode));
> > +
> > +       xid = get_xid();
> > +       rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
> > +                       cfile->fid.volatile_fid,
> > +                       FSCTL_QUERY_ALLOCATED_RANGES, true,
> > +                       (char *)&in_data, sizeof(in_data),
> > +                       sizeof(struct fsctl_query_allocate_ranges),
> > +                       (char **)&out_data, &out_data_len);
> > +       free_xid(xid);
> > +       if (rc == -E2BIG)
> > +               rc = 0;
> > +       if (rc)
> > +               goto lseek_exit;
> > +
> > +       if (out_data_len < sizeof(struct fsctl_query_allocate_ranges)) {
> > +               rc = -EINVAL;
> > +               goto lseek_exit;
> > +       }
> > +       if (whence == SEEK_DATA) {
> > +               offset = le64_to_cpu(out_data->file_offset);
> > +               goto lseek_exit;
> > +       }
> > +       if (offset < le64_to_cpu(out_data->file_offset))
> > +               goto lseek_exit;
> > +
> > +       offset = le64_to_cpu(out_data->file_offset) +
> > le64_to_cpu(out_data->length);
> > +
> > + lseek_exit:
> > +       kfree(out_data);
> > +       if (!rc)
> > +               return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
> > +       else
> > +               return rc;
> > +}
> >
> >  static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int
> >  mode,
> >                            loff_t off, loff_t len)
> > @@ -4247,6 +4310,7 @@ struct smb_version_operations smb30_operations = {
> >         .next_header = smb2_next_header,
> >         .ioctl_query_info = smb2_ioctl_query_info,
> >         .make_node = smb2_make_node,
> > +       .llseek = smb3_llseek,
> >  };
> >
> >  struct smb_version_operations smb311_operations = {
> > @@ -4356,6 +4420,7 @@ struct smb_version_operations smb311_operations = {
> >         .next_header = smb2_next_header,
> >         .ioctl_query_info = smb2_ioctl_query_info,
> >         .make_node = smb2_make_node,
> > +       .llseek = smb3_llseek,
> >  };
> >
> >  struct smb_version_values smb20_values = {
> > diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
> > index 99a015e980d2..ac92d066c724 100644
> > --- a/fs/cifs/smb2pdu.c
> > +++ b/fs/cifs/smb2pdu.c
> > @@ -2625,7 +2625,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) {
> > @@ -2634,6 +2634,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
> >         */
> > diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h
> > index ee8977688e21..3a29db740fe6 100644
> > --- a/fs/cifs/smb2pdu.h
> > +++ b/fs/cifs/smb2pdu.h
> > @@ -842,6 +842,11 @@ struct fsctl_get_integrity_information_rsp {
> >         __le32  ClusterSizeInBytes;
> >  } __packed;
> >
> > +struct fsctl_query_allocate_ranges {
> 
> This describes one range, so should be fsctl_query_allocate_range or
> even file_allocated_range_buffer as specified here:
> 
> https://docs.microsoft.com/en-us/windows/desktop/api/winioctl/ns-winioctl-_file_allocated_range_buffer

Thanks.
Will resend with this change.

> 
> > +       __le64  file_offset;
> > +       __le64  length;
> > +} __packed;
> > +
> >  /* Integrity ChecksumAlgorithm choices for above */
> >  #define        CHECKSUM_TYPE_NONE      0x0000
> >  #define        CHECKSUM_TYPE_CRC64     0x0002
> > diff --git a/fs/cifs/smbfsctl.h b/fs/cifs/smbfsctl.h
> > index 9b3459b9a5ce..08628e6a42ac 100644
> > --- a/fs/cifs/smbfsctl.h
> > +++ b/fs/cifs/smbfsctl.h
> > @@ -103,7 +103,7 @@
> >  #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
> > --
> > 2.13.6
> >
>
diff mbox series

Patch

diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index 07fdf1771add..a00ef6c6a988 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -884,6 +884,9 @@  static ssize_t cifs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 
 static loff_t cifs_llseek(struct file *file, loff_t offset, int whence)
 {
+	struct cifsFileInfo *cfile = file->private_data;
+	struct cifs_tcon *tcon;
+
 	/*
 	 * whence == SEEK_END || SEEK_DATA || SEEK_HOLE => we must revalidate
 	 * the cached file length
@@ -915,6 +918,12 @@  static loff_t cifs_llseek(struct file *file, loff_t offset, int whence)
 		if (rc < 0)
 			return (loff_t)rc;
 	}
+	if (cfile && cfile->tlink) {
+		tcon = tlink_tcon(cfile->tlink);
+		if (tcon->ses->server->ops->llseek)
+			return tcon->ses->server->ops->llseek(file, tcon,
+							      offset, whence);
+	}
 	return generic_file_llseek(file, offset, whence);
 }
 
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 0dc55f4e6929..828772da5fa4 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -493,6 +493,8 @@  struct smb_version_operations {
 			 char *full_path,
 			 umode_t mode,
 			 dev_t device_number);
+	/* version specific llseek implementation */
+	loff_t (*llseek)(struct file *, struct cifs_tcon *, loff_t, int);
 };
 
 struct smb_version_values {
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 2824b97c5869..dc36316b9bd5 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -2872,6 +2872,69 @@  static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
 	return rc;
 }
 
+static loff_t smb3_llseek(struct file *file, struct cifs_tcon *tcon, loff_t offset, int whence)
+{
+	struct cifsFileInfo *cfile = file->private_data;
+	struct cifsInodeInfo *cifsi;
+	struct inode *inode;
+	int rc = 0;
+	struct fsctl_query_allocate_ranges in_data, *out_data = NULL;
+	u32 out_data_len;
+	unsigned int xid;
+
+	if (whence != SEEK_HOLE && whence != SEEK_DATA)
+		return generic_file_llseek(file, offset, whence);
+
+	inode = d_inode(cfile->dentry);
+	cifsi = CIFS_I(inode);
+
+	if (offset < 0 || offset > i_size_read(inode))
+		return -ENXIO;
+
+	if (!(cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)) {
+		if (whence == SEEK_HOLE)
+			offset = i_size_read(inode);
+		else
+			offset = offset;
+		goto lseek_exit;
+	}
+
+	in_data.file_offset = cpu_to_le64(offset);
+	in_data.length = cpu_to_le64(i_size_read(inode));
+
+	xid = get_xid();
+	rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
+			cfile->fid.volatile_fid,
+			FSCTL_QUERY_ALLOCATED_RANGES, true,
+			(char *)&in_data, sizeof(in_data),
+			sizeof(struct fsctl_query_allocate_ranges),
+			(char **)&out_data, &out_data_len);
+	free_xid(xid);
+	if (rc == -E2BIG)
+		rc = 0;
+	if (rc)
+		goto lseek_exit;
+
+	if (out_data_len < sizeof(struct fsctl_query_allocate_ranges)) {
+		rc = -EINVAL;
+		goto lseek_exit;
+	}
+	if (whence == SEEK_DATA) {
+		offset = le64_to_cpu(out_data->file_offset);
+		goto lseek_exit;
+	}
+	if (offset < le64_to_cpu(out_data->file_offset))
+		goto lseek_exit;
+
+	offset = le64_to_cpu(out_data->file_offset) + le64_to_cpu(out_data->length);
+
+ lseek_exit:
+	kfree(out_data);
+	if (!rc)
+		return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
+	else
+		return rc;
+}
 
 static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode,
 			   loff_t off, loff_t len)
@@ -4247,6 +4310,7 @@  struct smb_version_operations smb30_operations = {
 	.next_header = smb2_next_header,
 	.ioctl_query_info = smb2_ioctl_query_info,
 	.make_node = smb2_make_node,
+	.llseek = smb3_llseek,
 };
 
 struct smb_version_operations smb311_operations = {
@@ -4356,6 +4420,7 @@  struct smb_version_operations smb311_operations = {
 	.next_header = smb2_next_header,
 	.ioctl_query_info = smb2_ioctl_query_info,
 	.make_node = smb2_make_node,
+	.llseek = smb3_llseek,
 };
 
 struct smb_version_values smb20_values = {
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index 99a015e980d2..ac92d066c724 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -2625,7 +2625,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) {
@@ -2634,6 +2634,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 */
diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h
index ee8977688e21..3a29db740fe6 100644
--- a/fs/cifs/smb2pdu.h
+++ b/fs/cifs/smb2pdu.h
@@ -842,6 +842,11 @@  struct fsctl_get_integrity_information_rsp {
 	__le32	ClusterSizeInBytes;
 } __packed;
 
+struct fsctl_query_allocate_ranges {
+	__le64	file_offset;
+	__le64	length;
+} __packed;
+
 /* Integrity ChecksumAlgorithm choices for above */
 #define	CHECKSUM_TYPE_NONE	0x0000
 #define	CHECKSUM_TYPE_CRC64	0x0002
diff --git a/fs/cifs/smbfsctl.h b/fs/cifs/smbfsctl.h
index 9b3459b9a5ce..08628e6a42ac 100644
--- a/fs/cifs/smbfsctl.h
+++ b/fs/cifs/smbfsctl.h
@@ -103,7 +103,7 @@ 
 #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