linux-cifs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] cifs: improve fallocate emulation
       [not found] <20210603053101.1229297-1-lsahlber@redhat.com>
@ 2021-06-03  5:31 ` Ronnie Sahlberg
  2021-06-05 20:59   ` Steve French
  0 siblings, 1 reply; 6+ messages in thread
From: Ronnie Sahlberg @ 2021-06-03  5:31 UTC (permalink / raw)
  To: linux-cifs; +Cc: Steve French

RHBZ: 1866684

We don't have a real fallocate in the SMB2 protocol so we used to emulate fallocate
by simply switching the file to become non-sparse. But as that could potantially
consume a lot more data than we intended to fallocate (large sparse file and fallocating a thin
slice in the middle) we would only do this IFF the fallocate request was for virtually the entire file.

This patch improves this and starts allowing us to fallocate smaller chunks of a file by
overwriting the region with 0, for the parts that are unallocated.

The method used is to first query the server for FSCTL_QUERY_ALLOCATED_RANGES to find what
is unallocated in teh fallocate range and then to only overwrite-with-zero the unallocated ranges to fill
in the holes.
As overwriting-with-zero is different from just allocating blocks, and potentially much more expensive,
we limit this to only allow fallocate ranges up to 1Mb in size.

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
---
 fs/cifs/smb2ops.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)

diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 21ef51d338e0..b68ba92893b6 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -3601,6 +3601,119 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
 	return rc;
 }
 
+static int smb3_simple_fallocate_write_range(unsigned int xid,
+					     struct cifs_tcon *tcon,
+					     struct cifsFileInfo *cfile,
+					     loff_t off, loff_t len,
+					     char *buf)
+{
+	struct cifs_io_parms io_parms = {0};
+	int nbytes;
+	struct kvec iov[2];
+
+	io_parms.netfid = cfile->fid.netfid;
+	io_parms.pid = current->tgid;
+	io_parms.tcon = tcon;
+	io_parms.persistent_fid = cfile->fid.persistent_fid;
+	io_parms.volatile_fid = cfile->fid.volatile_fid;
+	io_parms.offset = off;
+	io_parms.length = len;
+
+	/* iov[0] is reserved for smb header */
+	iov[1].iov_base = buf;
+	iov[1].iov_len = io_parms.length;
+	return SMB2_write(xid, &io_parms, &nbytes, iov, 1);
+}
+
+static int smb3_simple_fallocate_range(unsigned int xid,
+				       struct cifs_tcon *tcon,
+				       struct cifsFileInfo *cfile,
+				       loff_t off, loff_t len)
+{
+	struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data;
+	u32 out_data_len;
+	char *buf = NULL;
+	loff_t l;
+	int rc;
+
+	in_data.file_offset = cpu_to_le64(off);
+	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)
+		goto out;
+	/*
+	 * It is already all allocated
+	 */
+	if (out_data_len == 0)
+		goto out;
+
+	buf = kzalloc(1024 * 1024, GFP_KERNEL);
+	if (buf == NULL) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	tmp_data = out_data;
+	while (len) {
+		/*
+		 * The rest of the region is unmapped so write it all.
+		 */
+		if (out_data_len == 0) {
+			rc = smb3_simple_fallocate_write_range(xid, tcon,
+					       cfile, off, len, buf);
+			goto out;
+		}
+
+		if (out_data_len < sizeof(struct file_allocated_range_buffer)) {
+			rc = -EINVAL;
+			goto out;
+		}
+
+		if (off < le64_to_cpu(tmp_data->file_offset)) {
+			/*
+			 * We are at a hole. Write until the end of the region
+			 * or until the next allocated data,
+			 * whichever comes next.
+			 */
+			l = le64_to_cpu(tmp_data->file_offset) - off;
+			if (len < l)
+				l = len;
+			rc = smb3_simple_fallocate_write_range(xid, tcon,
+					       cfile, off, l, buf);
+			if (rc)
+				goto out;
+			off = off + l;
+			len = len - l;
+			if (len == 0)
+				goto out;
+		}
+		/*
+		 * We are at a section of allocated data, just skip forward
+		 * until the end of the data or the end of the region
+		 * we are supposed to fallocate, whichever comes first.
+		 */
+		l = le64_to_cpu(tmp_data->length);
+		if (len < l)
+			l = len;
+		off += l;
+		len -= l;
+
+		tmp_data = &tmp_data[1];
+		out_data_len -= sizeof(struct file_allocated_range_buffer);
+	}
+
+ out:
+	kfree(out_data);
+	kfree(buf);
+	return rc;
+}
+
+
 static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
 			    loff_t off, loff_t len, bool keep_size)
 {
@@ -3661,6 +3774,26 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
 	}
 
 	if ((keep_size == true) || (i_size_read(inode) >= off + len)) {
+		/*
+		 * At this point, we are trying to fallocate an internal
+		 * regions of a sparse file. Since smb2 does not have a
+		 * fallocate command we have two otions on how to emulate this.
+		 * We can either turn the entire file to become non-sparse
+		 * which we only do if the fallocate is for virtually
+		 * the whole file,  or we can overwrite the region with zeroes
+		 * using SMB2_write, which could be prohibitevly expensive
+		 * if len is large.
+		 */
+		/*
+		 * We are only trying to fallocate a small region so
+		 * just write it with zero.
+		 */
+		if (len <= 1024 * 1024) {
+			rc = smb3_simple_fallocate_range(xid, tcon, cfile,
+							 off, len);
+			goto out;
+		}
+
 		/*
 		 * Check if falloc starts within first few pages of file
 		 * and ends within a few pages of the end of file to
-- 
2.30.2


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] cifs: improve fallocate emulation
  2021-06-03  5:31 ` [PATCH] cifs: improve fallocate emulation Ronnie Sahlberg
@ 2021-06-05 20:59   ` Steve French
  0 siblings, 0 replies; 6+ messages in thread
From: Steve French @ 2021-06-05 20:59 UTC (permalink / raw)
  To: Ronnie Sahlberg; +Cc: linux-cifs

corrected some minor formatting in the description and merged into
cifs-2.6.git for-next

On Thu, Jun 3, 2021 at 12:31 AM Ronnie Sahlberg <lsahlber@redhat.com> wrote:
>
> RHBZ: 1866684
>
> We don't have a real fallocate in the SMB2 protocol so we used to emulate fallocate
> by simply switching the file to become non-sparse. But as that could potantially
> consume a lot more data than we intended to fallocate (large sparse file and fallocating a thin
> slice in the middle) we would only do this IFF the fallocate request was for virtually the entire file.
>
> This patch improves this and starts allowing us to fallocate smaller chunks of a file by
> overwriting the region with 0, for the parts that are unallocated.
>
> The method used is to first query the server for FSCTL_QUERY_ALLOCATED_RANGES to find what
> is unallocated in teh fallocate range and then to only overwrite-with-zero the unallocated ranges to fill
> in the holes.
> As overwriting-with-zero is different from just allocating blocks, and potentially much more expensive,
> we limit this to only allow fallocate ranges up to 1Mb in size.
>
> Reported-by: kernel test robot <lkp@intel.com>
> Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
> ---
>  fs/cifs/smb2ops.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 133 insertions(+)
>
> diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
> index 21ef51d338e0..b68ba92893b6 100644
> --- a/fs/cifs/smb2ops.c
> +++ b/fs/cifs/smb2ops.c
> @@ -3601,6 +3601,119 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
>         return rc;
>  }
>
> +static int smb3_simple_fallocate_write_range(unsigned int xid,
> +                                            struct cifs_tcon *tcon,
> +                                            struct cifsFileInfo *cfile,
> +                                            loff_t off, loff_t len,
> +                                            char *buf)
> +{
> +       struct cifs_io_parms io_parms = {0};
> +       int nbytes;
> +       struct kvec iov[2];
> +
> +       io_parms.netfid = cfile->fid.netfid;
> +       io_parms.pid = current->tgid;
> +       io_parms.tcon = tcon;
> +       io_parms.persistent_fid = cfile->fid.persistent_fid;
> +       io_parms.volatile_fid = cfile->fid.volatile_fid;
> +       io_parms.offset = off;
> +       io_parms.length = len;
> +
> +       /* iov[0] is reserved for smb header */
> +       iov[1].iov_base = buf;
> +       iov[1].iov_len = io_parms.length;
> +       return SMB2_write(xid, &io_parms, &nbytes, iov, 1);
> +}
> +
> +static int smb3_simple_fallocate_range(unsigned int xid,
> +                                      struct cifs_tcon *tcon,
> +                                      struct cifsFileInfo *cfile,
> +                                      loff_t off, loff_t len)
> +{
> +       struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data;
> +       u32 out_data_len;
> +       char *buf = NULL;
> +       loff_t l;
> +       int rc;
> +
> +       in_data.file_offset = cpu_to_le64(off);
> +       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)
> +               goto out;
> +       /*
> +        * It is already all allocated
> +        */
> +       if (out_data_len == 0)
> +               goto out;
> +
> +       buf = kzalloc(1024 * 1024, GFP_KERNEL);
> +       if (buf == NULL) {
> +               rc = -ENOMEM;
> +               goto out;
> +       }
> +
> +       tmp_data = out_data;
> +       while (len) {
> +               /*
> +                * The rest of the region is unmapped so write it all.
> +                */
> +               if (out_data_len == 0) {
> +                       rc = smb3_simple_fallocate_write_range(xid, tcon,
> +                                              cfile, off, len, buf);
> +                       goto out;
> +               }
> +
> +               if (out_data_len < sizeof(struct file_allocated_range_buffer)) {
> +                       rc = -EINVAL;
> +                       goto out;
> +               }
> +
> +               if (off < le64_to_cpu(tmp_data->file_offset)) {
> +                       /*
> +                        * We are at a hole. Write until the end of the region
> +                        * or until the next allocated data,
> +                        * whichever comes next.
> +                        */
> +                       l = le64_to_cpu(tmp_data->file_offset) - off;
> +                       if (len < l)
> +                               l = len;
> +                       rc = smb3_simple_fallocate_write_range(xid, tcon,
> +                                              cfile, off, l, buf);
> +                       if (rc)
> +                               goto out;
> +                       off = off + l;
> +                       len = len - l;
> +                       if (len == 0)
> +                               goto out;
> +               }
> +               /*
> +                * We are at a section of allocated data, just skip forward
> +                * until the end of the data or the end of the region
> +                * we are supposed to fallocate, whichever comes first.
> +                */
> +               l = le64_to_cpu(tmp_data->length);
> +               if (len < l)
> +                       l = len;
> +               off += l;
> +               len -= l;
> +
> +               tmp_data = &tmp_data[1];
> +               out_data_len -= sizeof(struct file_allocated_range_buffer);
> +       }
> +
> + out:
> +       kfree(out_data);
> +       kfree(buf);
> +       return rc;
> +}
> +
> +
>  static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
>                             loff_t off, loff_t len, bool keep_size)
>  {
> @@ -3661,6 +3774,26 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
>         }
>
>         if ((keep_size == true) || (i_size_read(inode) >= off + len)) {
> +               /*
> +                * At this point, we are trying to fallocate an internal
> +                * regions of a sparse file. Since smb2 does not have a
> +                * fallocate command we have two otions on how to emulate this.
> +                * We can either turn the entire file to become non-sparse
> +                * which we only do if the fallocate is for virtually
> +                * the whole file,  or we can overwrite the region with zeroes
> +                * using SMB2_write, which could be prohibitevly expensive
> +                * if len is large.
> +                */
> +               /*
> +                * We are only trying to fallocate a small region so
> +                * just write it with zero.
> +                */
> +               if (len <= 1024 * 1024) {
> +                       rc = smb3_simple_fallocate_range(xid, tcon, cfile,
> +                                                        off, len);
> +                       goto out;
> +               }
> +
>                 /*
>                  * Check if falloc starts within first few pages of file
>                  * and ends within a few pages of the end of file to
> --
> 2.30.2
>


-- 
Thanks,

Steve

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] cifs: improve fallocate emulation
  2021-04-08 22:40 Ronnie Sahlberg
@ 2021-04-09  3:55 ` Steve French
  0 siblings, 0 replies; 6+ messages in thread
From: Steve French @ 2021-04-09  3:55 UTC (permalink / raw)
  To: Ronnie Sahlberg; +Cc: linux-cifs

tentatively merged into cifs-2.6.git for-next pending more testing

On Thu, Apr 8, 2021 at 5:41 PM Ronnie Sahlberg <lsahlber@redhat.com> wrote:
>
> RHBZ: 1866684
>
> We don't have a real fallocate in the SMB2 protocol so we used to emulate fallocate
> by simply switching the file to become non-sparse. But as that could potantially
> consume a lot more data than we intended to fallocate (large sparse file and fallocating a thin
> slice in the middle) we would only do this IFF the fallocate request was for virtually the entire file.
>
> This patch improves this and starts allowing us to fallocate smaller chunks of a file by
> overwriting the region with 0, for the parts that are unallocated.
>
> The method used is to first query the server for FSCTL_QUERY_ALLOCATED_RANGES to find what
> is unallocated in teh fallocate range and then to only overwrite-with-zero the unallocated ranges to fill
> in the holes.
> As overwriting-with-zero is different from just allocating blocks, and potentially much more expensive,
> we limit this to only allow fallocate ranges up to 1Mb in size.
>
> Reported-by: kernel test robot <lkp@intel.com>
> Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
> ---
>  fs/cifs/smb2ops.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 133 insertions(+)
>
> diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
> index f703204fb185..1eecaeb4beb4 100644
> --- a/fs/cifs/smb2ops.c
> +++ b/fs/cifs/smb2ops.c
> @@ -3563,6 +3563,119 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
>         return rc;
>  }
>
> +static int smb3_simple_fallocate_write_range(unsigned int xid,
> +                                            struct cifs_tcon *tcon,
> +                                            struct cifsFileInfo *cfile,
> +                                            loff_t off, loff_t len,
> +                                            char *buf)
> +{
> +       struct cifs_io_parms io_parms = {0};
> +       int nbytes;
> +       struct kvec iov[2];
> +
> +       io_parms.netfid = cfile->fid.netfid;
> +       io_parms.pid = current->tgid;
> +       io_parms.tcon = tcon;
> +       io_parms.persistent_fid = cfile->fid.persistent_fid;
> +       io_parms.volatile_fid = cfile->fid.volatile_fid;
> +       io_parms.offset = off;
> +       io_parms.length = len;
> +
> +       /* iov[0] is reserved for smb header */
> +       iov[1].iov_base = buf;
> +       iov[1].iov_len = io_parms.length;
> +       return SMB2_write(xid, &io_parms, &nbytes, iov, 1);
> +}
> +
> +static int smb3_simple_fallocate_range(unsigned int xid,
> +                                      struct cifs_tcon *tcon,
> +                                      struct cifsFileInfo *cfile,
> +                                      loff_t off, loff_t len)
> +{
> +       struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data;
> +       u32 out_data_len;
> +       char *buf = NULL;
> +       loff_t l;
> +       int rc;
> +
> +       in_data.file_offset = cpu_to_le64(off);
> +       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)
> +               goto out;
> +       /*
> +        * It is already all allocated
> +        */
> +       if (out_data_len == 0)
> +               goto out;
> +
> +       buf = kzalloc(1024 * 1024, GFP_KERNEL);
> +       if (buf == NULL) {
> +               rc = -ENOMEM;
> +               goto out;
> +       }
> +
> +       tmp_data = out_data;
> +       while (len) {
> +               /*
> +                * The rest of the region is unmapped so write it all.
> +                */
> +               if (out_data_len == 0) {
> +                       rc = smb3_simple_fallocate_write_range(xid, tcon,
> +                                              cfile, off, len, buf);
> +                       goto out;
> +               }
> +
> +               if (out_data_len < sizeof(struct file_allocated_range_buffer)) {
> +                       rc = -EINVAL;
> +                       goto out;
> +               }
> +
> +               if (off < le64_to_cpu(tmp_data->file_offset)) {
> +                       /*
> +                        * We are at a hole. Write until the end of the region
> +                        * or until the next allocated data,
> +                        * whichever comes next.
> +                        */
> +                       l = le64_to_cpu(tmp_data->file_offset) - off;
> +                       if (len < l)
> +                               l = len;
> +                       rc = smb3_simple_fallocate_write_range(xid, tcon,
> +                                              cfile, off, l, buf);
> +                       if (rc)
> +                               goto out;
> +                       off = off + l;
> +                       len = len - l;
> +                       if (len == 0)
> +                               goto out;
> +               }
> +               /*
> +                * We are at a section of allocated data, just skip forward
> +                * until the end of the data or the end of the region
> +                * we are supposed to fallocate, whichever comes first.
> +                */
> +               l = le64_to_cpu(tmp_data->length);
> +               if (len < l)
> +                       l = len;
> +               off += l;
> +               len -= l;
> +
> +               tmp_data = &tmp_data[1];
> +               out_data_len -= sizeof(struct file_allocated_range_buffer);
> +       }
> +
> + out:
> +       kfree(out_data);
> +       kfree(buf);
> +       return rc;
> +}
> +
> +
>  static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
>                             loff_t off, loff_t len, bool keep_size)
>  {
> @@ -3623,6 +3736,26 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
>         }
>
>         if ((keep_size == true) || (i_size_read(inode) >= off + len)) {
> +               /*
> +                * At this point, we are trying to fallocate an internal
> +                * regions of a sparse file. Since smb2 does not have a
> +                * fallocate command we have two otions on how to emulate this.
> +                * We can either turn the entire file to become non-sparse
> +                * which we only do if the fallocate is for virtually
> +                * the whole file,  or we can overwrite the region with zeroes
> +                * using SMB2_write, which could be prohibitevly expensive
> +                * if len is large.
> +                */
> +               /*
> +                * We are only trying to fallocate a small region so
> +                * just write it with zero.
> +                */
> +               if (len <= 1024 * 1024) {
> +                       rc = smb3_simple_fallocate_range(xid, tcon, cfile,
> +                                                        off, len);
> +                       goto out;
> +               }
> +
>                 /*
>                  * Check if falloc starts within first few pages of file
>                  * and ends within a few pages of the end of file to
> --
> 2.30.2
>


-- 
Thanks,

Steve

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH] cifs: improve fallocate emulation
@ 2021-04-08 22:40 Ronnie Sahlberg
  2021-04-09  3:55 ` Steve French
  0 siblings, 1 reply; 6+ messages in thread
From: Ronnie Sahlberg @ 2021-04-08 22:40 UTC (permalink / raw)
  To: linux-cifs; +Cc: Steve French

RHBZ: 1866684

We don't have a real fallocate in the SMB2 protocol so we used to emulate fallocate
by simply switching the file to become non-sparse. But as that could potantially
consume a lot more data than we intended to fallocate (large sparse file and fallocating a thin
slice in the middle) we would only do this IFF the fallocate request was for virtually the entire file.

This patch improves this and starts allowing us to fallocate smaller chunks of a file by
overwriting the region with 0, for the parts that are unallocated.

The method used is to first query the server for FSCTL_QUERY_ALLOCATED_RANGES to find what
is unallocated in teh fallocate range and then to only overwrite-with-zero the unallocated ranges to fill
in the holes.
As overwriting-with-zero is different from just allocating blocks, and potentially much more expensive,
we limit this to only allow fallocate ranges up to 1Mb in size.

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
---
 fs/cifs/smb2ops.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)

diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index f703204fb185..1eecaeb4beb4 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -3563,6 +3563,119 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
 	return rc;
 }
 
+static int smb3_simple_fallocate_write_range(unsigned int xid,
+					     struct cifs_tcon *tcon,
+					     struct cifsFileInfo *cfile,
+					     loff_t off, loff_t len,
+					     char *buf)
+{
+	struct cifs_io_parms io_parms = {0};
+	int nbytes;
+	struct kvec iov[2];
+
+	io_parms.netfid = cfile->fid.netfid;
+	io_parms.pid = current->tgid;
+	io_parms.tcon = tcon;
+	io_parms.persistent_fid = cfile->fid.persistent_fid;
+	io_parms.volatile_fid = cfile->fid.volatile_fid;
+	io_parms.offset = off;
+	io_parms.length = len;
+
+	/* iov[0] is reserved for smb header */
+	iov[1].iov_base = buf;
+	iov[1].iov_len = io_parms.length;
+	return SMB2_write(xid, &io_parms, &nbytes, iov, 1);
+}
+
+static int smb3_simple_fallocate_range(unsigned int xid,
+				       struct cifs_tcon *tcon,
+				       struct cifsFileInfo *cfile,
+				       loff_t off, loff_t len)
+{
+	struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data;
+	u32 out_data_len;
+	char *buf = NULL;
+	loff_t l;
+	int rc;
+
+	in_data.file_offset = cpu_to_le64(off);
+	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)
+		goto out;
+	/*
+	 * It is already all allocated
+	 */
+	if (out_data_len == 0)
+		goto out;
+
+	buf = kzalloc(1024 * 1024, GFP_KERNEL);
+	if (buf == NULL) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	tmp_data = out_data;
+	while (len) {
+		/*
+		 * The rest of the region is unmapped so write it all.
+		 */
+		if (out_data_len == 0) {
+			rc = smb3_simple_fallocate_write_range(xid, tcon,
+					       cfile, off, len, buf);
+			goto out;
+		}
+
+		if (out_data_len < sizeof(struct file_allocated_range_buffer)) {
+			rc = -EINVAL;
+			goto out;
+		}
+
+		if (off < le64_to_cpu(tmp_data->file_offset)) {
+			/*
+			 * We are at a hole. Write until the end of the region
+			 * or until the next allocated data,
+			 * whichever comes next.
+			 */
+			l = le64_to_cpu(tmp_data->file_offset) - off;
+			if (len < l)
+				l = len;
+			rc = smb3_simple_fallocate_write_range(xid, tcon,
+					       cfile, off, l, buf);
+			if (rc)
+				goto out;
+			off = off + l;
+			len = len - l;
+			if (len == 0)
+				goto out;
+		}
+		/*
+		 * We are at a section of allocated data, just skip forward
+		 * until the end of the data or the end of the region
+		 * we are supposed to fallocate, whichever comes first.
+		 */
+		l = le64_to_cpu(tmp_data->length);
+		if (len < l)
+			l = len;
+		off += l;
+		len -= l;
+
+		tmp_data = &tmp_data[1];
+		out_data_len -= sizeof(struct file_allocated_range_buffer);
+	}
+
+ out:
+	kfree(out_data);
+	kfree(buf);
+	return rc;
+}
+
+
 static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
 			    loff_t off, loff_t len, bool keep_size)
 {
@@ -3623,6 +3736,26 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
 	}
 
 	if ((keep_size == true) || (i_size_read(inode) >= off + len)) {
+		/*
+		 * At this point, we are trying to fallocate an internal
+		 * regions of a sparse file. Since smb2 does not have a
+		 * fallocate command we have two otions on how to emulate this.
+		 * We can either turn the entire file to become non-sparse
+		 * which we only do if the fallocate is for virtually
+		 * the whole file,  or we can overwrite the region with zeroes
+		 * using SMB2_write, which could be prohibitevly expensive
+		 * if len is large.
+		 */
+		/*
+		 * We are only trying to fallocate a small region so
+		 * just write it with zero.
+		 */
+		if (len <= 1024 * 1024) {
+			rc = smb3_simple_fallocate_range(xid, tcon, cfile,
+							 off, len);
+			goto out;
+		}
+
 		/*
 		 * Check if falloc starts within first few pages of file
 		 * and ends within a few pages of the end of file to
-- 
2.30.2


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] cifs: improve fallocate emulation
  2021-04-08  7:46 Ronnie Sahlberg
@ 2021-04-08 10:36 ` kernel test robot
  0 siblings, 0 replies; 6+ messages in thread
From: kernel test robot @ 2021-04-08 10:36 UTC (permalink / raw)
  To: Ronnie Sahlberg, linux-cifs; +Cc: kbuild-all, Steve French

[-- Attachment #1: Type: text/plain, Size: 4641 bytes --]

Hi Ronnie,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on cifs/for-next]
[also build test WARNING on v5.12-rc6 next-20210407]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Ronnie-Sahlberg/cifs-improve-fallocate-emulation/20210408-154812
base:   git://git.samba.org/sfrench/cifs-2.6.git for-next
config: x86_64-randconfig-s032-20210408 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-22) 9.3.0
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.3-279-g6d5d9b42-dirty
        # https://github.com/0day-ci/linux/commit/4e8489f4555efd3340016e422828e29ae87d7f0b
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Ronnie-Sahlberg/cifs-improve-fallocate-emulation/20210408-154812
        git checkout 4e8489f4555efd3340016e422828e29ae87d7f0b
        # save the attached .config to linux build tree
        make W=1 C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)
>> fs/cifs/smb2ops.c:3639:35: sparse: sparse: restricted __le64 degrades to integer
   fs/cifs/smb2ops.c:3645:37: sparse: sparse: restricted __le64 degrades to integer
>> fs/cifs/smb2ops.c:3662:19: sparse: sparse: incorrect type in assignment (different base types) @@     expected long long [assigned] [usertype] l @@     got restricted __le64 [usertype] length @@
   fs/cifs/smb2ops.c:3662:19: sparse:     expected long long [assigned] [usertype] l
   fs/cifs/smb2ops.c:3662:19: sparse:     got restricted __le64 [usertype] length

vim +3639 fs/cifs/smb2ops.c

  3589	
  3590	static int smb3_simple_fallocate_range(unsigned int xid,
  3591					       struct cifs_tcon *tcon,
  3592					       struct cifsFileInfo *cfile,
  3593					       loff_t off, loff_t len)
  3594	{
  3595		struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data;
  3596		u32 out_data_len;
  3597		char *buf = NULL;
  3598		loff_t l;
  3599		int rc;
  3600	
  3601		in_data.file_offset = cpu_to_le64(off);
  3602		in_data.length = cpu_to_le64(len);
  3603		rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
  3604				cfile->fid.volatile_fid,
  3605				FSCTL_QUERY_ALLOCATED_RANGES, true,
  3606				(char *)&in_data, sizeof(in_data),
  3607				1024 * sizeof(struct file_allocated_range_buffer),
  3608				(char **)&out_data, &out_data_len);
  3609		if (rc)
  3610			goto out;
  3611		/*
  3612		 * It is already all allocated
  3613		 */
  3614		if (out_data_len == 0)
  3615			goto out;
  3616	
  3617		buf = kzalloc(1024 * 1024, GFP_KERNEL);
  3618		if (buf == NULL) {
  3619			rc = -ENOMEM;
  3620			goto out;
  3621		}
  3622	  
  3623		tmp_data = out_data;
  3624		while (len) {
  3625			/*
  3626			 * The rest of the region is unmapped so write it all.
  3627			 */
  3628			if (out_data_len == 0) {
  3629				rc = smb3_simple_fallocate_write_range(xid, tcon,
  3630						       cfile, off, len, buf);
  3631				goto out;
  3632			}
  3633			
  3634			if (out_data_len < sizeof(struct file_allocated_range_buffer)) {
  3635				rc = -EINVAL;
  3636				goto out;
  3637			}
  3638	
> 3639			if (off < tmp_data->file_offset) {
  3640				/*
  3641				 * We are at a hole. Write until the end of the region
  3642				 * or until the next allocated data,
  3643				 * whichever comes next.
  3644				 */
  3645				l = tmp_data->file_offset - off;
  3646				if (len < l)
  3647					l = len;
  3648				rc = smb3_simple_fallocate_write_range(xid, tcon,
  3649						       cfile, off, l, buf);
  3650				if (rc)
  3651					goto out;
  3652				off = off + l;
  3653				len = len - l;
  3654				if (len == 0)
  3655					goto out;
  3656			}
  3657			/*
  3658			 * We are at a section of allocated data, just skip forward
  3659			 * until the end of the data or the end of the region
  3660			 * we are supposed to fallocate, whichever comes first.
  3661			 */
> 3662			l = tmp_data->length;
  3663			if (len < l)
  3664				l = len;
  3665			off += l;
  3666			len -= l;
  3667			
  3668			tmp_data = &tmp_data[1];
  3669			out_data_len -= sizeof(struct file_allocated_range_buffer);
  3670		}
  3671		
  3672	 out:
  3673		kfree(out_data);
  3674		kfree(buf);
  3675		return rc;
  3676	}
  3677	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 34191 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH] cifs: improve fallocate emulation
@ 2021-04-08  7:46 Ronnie Sahlberg
  2021-04-08 10:36 ` kernel test robot
  0 siblings, 1 reply; 6+ messages in thread
From: Ronnie Sahlberg @ 2021-04-08  7:46 UTC (permalink / raw)
  To: linux-cifs; +Cc: Steve French

RHBZ: 1866684

We don't have a real fallocate in the SMB2 protocol so we used to emulate fallocate
by simply switching the file to become non-sparse. But as that could potantially
consume a lot more data than we intended to fallocate (large sparse file and fallocating a thin
slice in the middle) we would only do this IFF the fallocate request was for virtually the entire file.

This patch improves this and starts allowing us to fallocate smaller chunks of a file by
overwriting the region with 0, for the parts that are unallocated.

The method used is to first query the server for FSCTL_QUERY_ALLOCATED_RANGES to find what
is unallocated in teh fallocate range and then to only overwrite-with-zero the unallocated ranges to fill
in the holes.
As overwriting-with-zero is different from just allocating blocks, and potentially much more expensive,
we limit this to only allow fallocate ranges up to 1Mb in size.

Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
---
 fs/cifs/smb2ops.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)

diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index f703204fb185..2433cc396531 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -3563,6 +3563,119 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
 	return rc;
 }
 
+static int smb3_simple_fallocate_write_range(unsigned int xid,
+					     struct cifs_tcon *tcon,
+					     struct cifsFileInfo *cfile,
+					     loff_t off, loff_t len,
+					     char *buf)
+{
+	struct cifs_io_parms io_parms = {0};
+	int nbytes;
+	struct kvec iov[2];
+
+	io_parms.netfid = cfile->fid.netfid;
+	io_parms.pid = current->tgid;
+	io_parms.tcon = tcon;
+	io_parms.persistent_fid = cfile->fid.persistent_fid;
+	io_parms.volatile_fid = cfile->fid.volatile_fid;
+	io_parms.offset = off;
+	io_parms.length = len;
+
+	/* iov[0] is reserved for smb header */
+	iov[1].iov_base = buf;
+	iov[1].iov_len = io_parms.length;
+	return SMB2_write(xid, &io_parms, &nbytes, iov, 1);
+}
+
+static int smb3_simple_fallocate_range(unsigned int xid,
+				       struct cifs_tcon *tcon,
+				       struct cifsFileInfo *cfile,
+				       loff_t off, loff_t len)
+{
+	struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data;
+	u32 out_data_len;
+	char *buf = NULL;
+	loff_t l;
+	int rc;
+
+	in_data.file_offset = cpu_to_le64(off);
+	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)
+		goto out;
+	/*
+	 * It is already all allocated
+	 */
+	if (out_data_len == 0)
+		goto out;
+
+	buf = kzalloc(1024 * 1024, GFP_KERNEL);
+	if (buf == NULL) {
+		rc = -ENOMEM;
+		goto out;
+	}
+  
+	tmp_data = out_data;
+	while (len) {
+		/*
+		 * The rest of the region is unmapped so write it all.
+		 */
+		if (out_data_len == 0) {
+			rc = smb3_simple_fallocate_write_range(xid, tcon,
+					       cfile, off, len, buf);
+			goto out;
+		}
+		
+		if (out_data_len < sizeof(struct file_allocated_range_buffer)) {
+			rc = -EINVAL;
+			goto out;
+		}
+
+		if (off < tmp_data->file_offset) {
+			/*
+			 * We are at a hole. Write until the end of the region
+			 * or until the next allocated data,
+			 * whichever comes next.
+			 */
+			l = tmp_data->file_offset - off;
+			if (len < l)
+				l = len;
+			rc = smb3_simple_fallocate_write_range(xid, tcon,
+					       cfile, off, l, buf);
+			if (rc)
+				goto out;
+			off = off + l;
+			len = len - l;
+			if (len == 0)
+				goto out;
+		}
+		/*
+		 * We are at a section of allocated data, just skip forward
+		 * until the end of the data or the end of the region
+		 * we are supposed to fallocate, whichever comes first.
+		 */
+		l = tmp_data->length;
+		if (len < l)
+			l = len;
+		off += l;
+		len -= l;
+		
+		tmp_data = &tmp_data[1];
+		out_data_len -= sizeof(struct file_allocated_range_buffer);
+	}
+	
+ out:
+	kfree(out_data);
+	kfree(buf);
+	return rc;
+}
+
+
 static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
 			    loff_t off, loff_t len, bool keep_size)
 {
@@ -3623,6 +3736,26 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
 	}
 
 	if ((keep_size == true) || (i_size_read(inode) >= off + len)) {
+		/*
+		 * At this point, we are trying to fallocate an internal
+		 * regions of a sparse file. Since smb2 does not have a
+		 * fallocate command we have two otions on how to emulate this.
+		 * We can either turn the entire file to become non-sparse
+		 * which we only do if the fallocate is for virtually
+		 * the whole file,  or we can overwrite the region with zeroes
+		 * using SMB2_write, which could be prohibitevly expensive
+		 * if len is large.
+		 */
+		/*
+		 * We are only trying to fallocate a small region so
+		 * just write it with zero.
+		 */
+		if (len <= 1024 * 1024) {
+			rc = smb3_simple_fallocate_range(xid, tcon, cfile,
+							 off, len);
+			goto out;
+		}
+
 		/*
 		 * Check if falloc starts within first few pages of file
 		 * and ends within a few pages of the end of file to
-- 
2.30.2


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2021-06-05 20:59 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20210603053101.1229297-1-lsahlber@redhat.com>
2021-06-03  5:31 ` [PATCH] cifs: improve fallocate emulation Ronnie Sahlberg
2021-06-05 20:59   ` Steve French
2021-04-08 22:40 Ronnie Sahlberg
2021-04-09  3:55 ` Steve French
  -- strict thread matches above, loose matches on Subject: below --
2021-04-08  7:46 Ronnie Sahlberg
2021-04-08 10:36 ` kernel test robot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).