Skip to contentSkip to conversation

btrfs: return errno instead of -1 from compression

PATCH Superseded Zach Brown 4 participants 12 messages May 8, 2014 – May 14, 2014 Btrfs file system

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore

Zach Brown

author
#0 lore raw

PATCH 1/3 btrfs: return errno instead of -1 from compression

The compression layer seems to have been built to return -1 and have callers make up errors that make sense. This isn't great because there are different classes of errors that originate down in the compression layer. Allocation failure and corrupt compressed data to name two.

So let's return real negative errnos from the compression layer so that callers can pass on the error without having to guess what happened.

This helps a future path return errors from btrfs_decompress().

---
 fs/btrfs/lzo.c  | 14 +++++++-------
 fs/btrfs/zlib.c | 26 +++++++++++++-------------
 2 files changed, 20 insertions(+), 20 deletions(-)
fs/btrfs/lzo.c +7 -7
index b47f669..24f7ede 100644
@@ -143,7 +143,7 @@ static int lzo_compress_pages(struct list_head *ws,
 		if (ret != LZO_E_OK) {
 			printk(KERN_DEBUG "BTRFS: deflate in loop returned %d\n",
 			       ret);
-			ret = -1;
+			ret = -EIO;
 			goto out;
 		}
 
@@ -189,7 +189,7 @@ static int lzo_compress_pages(struct list_head *ws,
 				kunmap(out_page);
 				if (nr_pages == nr_dest_pages) {
 					out_page = NULL;
-					ret = -1;
+					ret = -EIO;
 					goto out;
 				}
 
@@ -208,7 +208,7 @@ static int lzo_compress_pages(struct list_head *ws,
 
 		/* we're making it bigger, give up */
 		if (tot_in > 8192 && tot_in < tot_out) {
-			ret = -1;
+			ret = -EIO;
 			goto out;
 		}
 
@@ -335,7 +335,7 @@ cont:
 					break;
 
 				if (page_in_index + 1 >= total_pages_in) {
-					ret = -1;
+					ret = -EIO;
 					goto done;
 				}
 
@@ -358,7 +358,7 @@ cont:
 			kunmap(pages_in[page_in_index - 1]);
 		if (ret != LZO_E_OK) {
 			printk(KERN_WARNING "BTRFS: decompress failed\n");
-			ret = -1;
+			ret = -EIO;
 			break;
 		}
 
@@ -402,12 +402,12 @@ static int lzo_decompress(struct list_head *ws, unsigned char *data_in,
 	ret = lzo1x_decompress_safe(data_in, in_len, workspace->buf, &out_len);
 	if (ret != LZO_E_OK) {
 		printk(KERN_WARNING "BTRFS: decompress failed!\n");
-		ret = -1;
+		ret = -EIO;
 		goto out;
 	}
 
 	if (out_len < start_byte) {
-		ret = -1;
+		ret = -EIO;
 		goto out;
 	}
 
fs/btrfs/zlib.c +13 -13
index 8e57191..f14c4a0 100644
@@ -98,7 +98,7 @@ static int zlib_compress_pages(struct list_head *ws,
 
 	if (Z_OK != zlib_deflateInit(&workspace->def_strm, 3)) {
 		printk(KERN_WARNING "BTRFS: deflateInit failed\n");
-		ret = -1;
+		ret = -EIO;
 		goto out;
 	}
 
@@ -110,7 +110,7 @@ static int zlib_compress_pages(struct list_head *ws,
 
 	out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM);
 	if (out_page == NULL) {
-		ret = -1;
+		ret = -ENOMEM;
 		goto out;
 	}
 	cpage_out = kmap(out_page);
@@ -128,7 +128,7 @@ static int zlib_compress_pages(struct list_head *ws,
 			printk(KERN_DEBUG "BTRFS: deflate in loop returned %d\n",
 			       ret);
 			zlib_deflateEnd(&workspace->def_strm);
-			ret = -1;
+			ret = -EIO;
 			goto out;
 		}
 
@@ -136,7 +136,7 @@ static int zlib_compress_pages(struct list_head *ws,
 		if (workspace->def_strm.total_in > 8192 &&
 		    workspace->def_strm.total_in <
 		    workspace->def_strm.total_out) {
-			ret = -1;
+			ret = -EIO;
 			goto out;
 		}
 		/* we need another page for writing out.  Test this
@@ -147,12 +147,12 @@ static int zlib_compress_pages(struct list_head *ws,
 			kunmap(out_page);
 			if (nr_pages == nr_dest_pages) {
 				out_page = NULL;
-				ret = -1;
+				ret = -EIO;
 				goto out;
 			}
 			out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM);
 			if (out_page == NULL) {
-				ret = -1;
+				ret = -ENOMEM;
 				goto out;
 			}
 			cpage_out = kmap(out_page);
@@ -188,12 +188,12 @@ static int zlib_compress_pages(struct list_head *ws,
 	zlib_deflateEnd(&workspace->def_strm);
 
 	if (ret != Z_STREAM_END) {
-		ret = -1;
+		ret = -EIO;
 		goto out;
 	}
 
 	if (workspace->def_strm.total_out >= workspace->def_strm.total_in) {
-		ret = -1;
+		ret = -EIO;
 		goto out;
 	}
 
@@ -253,7 +253,7 @@ static int zlib_decompress_biovec(struct list_head *ws, struct page **pages_in,
 
 	if (Z_OK != zlib_inflateInit2(&workspace->inf_strm, wbits)) {
 		printk(KERN_WARNING "BTRFS: inflateInit failed\n");
-		return -1;
+		return -EIO;
 	}
 	while (workspace->inf_strm.total_in < srclen) {
 		ret = zlib_inflate(&workspace->inf_strm, Z_NO_FLUSH);
@@ -295,7 +295,7 @@ static int zlib_decompress_biovec(struct list_head *ws, struct page **pages_in,
 		}
 	}
 	if (ret != Z_STREAM_END)
-		ret = -1;
+		ret = -EIO;
 	else
 		ret = 0;
 done:
@@ -337,7 +337,7 @@ static int zlib_decompress(struct list_head *ws, unsigned char *data_in,
 
 	if (Z_OK != zlib_inflateInit2(&workspace->inf_strm, wbits)) {
 		printk(KERN_WARNING "BTRFS: inflateInit failed\n");
-		return -1;
+		return -EIO;
 	}
 
 	while (bytes_left > 0) {
@@ -354,7 +354,7 @@ static int zlib_decompress(struct list_head *ws, unsigned char *data_in,
 		total_out = workspace->inf_strm.total_out;
 
 		if (total_out == buf_start) {
-			ret = -1;
+			ret = -EIO;
 			break;
 		}
 
@@ -382,7 +382,7 @@ next:
 	}
 
 	if (ret != Z_STREAM_END && bytes_left != 0)
-		ret = -1;
+		ret = -EIO;
 	else
 		ret = 0;
 
Signature · 1 line
1.8.1.4
author
#1 lore raw

PATCH 2/3 btrfs: return ptr error from compression workspace

The btrfs compression wrappers translated errors from workspace allocation to either -ENOMEM or -1. The compression type workspace allocators are already returning a ERR_PTR(-ENOMEM). Just return that and get rid of the magical -1.

This helps a future patch return errors from the compression wrappers.

---
 fs/btrfs/compression.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
fs/btrfs/compression.c +3 -3
index ed1ff1cb..7912695 100644
@@ -888,7 +888,7 @@ int btrfs_compress_pages(int type, struct address_space *mapping,
 
 	workspace = find_workspace(type);
 	if (IS_ERR(workspace))
-		return -1;
+		return PTR_ERR(workspace);
 
 	ret = btrfs_compress_op[type-1]->compress_pages(workspace, mapping,
 						      start, len, pages,
@@ -924,7 +924,7 @@ static int btrfs_decompress_biovec(int type, struct page **pages_in,
 
 	workspace = find_workspace(type);
 	if (IS_ERR(workspace))
-		return -ENOMEM;
+		return PTR_ERR(workspace);
 
 	ret = btrfs_compress_op[type-1]->decompress_biovec(workspace, pages_in,
 							 disk_start,
@@ -946,7 +946,7 @@ int btrfs_decompress(int type, unsigned char *data_in, struct page *dest_page,
 
 	workspace = find_workspace(type);
 	if (IS_ERR(workspace))
-		return -ENOMEM;
+		return PTR_ERR(workspace);
 
 	ret = btrfs_compress_op[type-1]->decompress(workspace, data_in,
 						  dest_page, start_byte,
Signature · 1 line
1.8.1.4
Reviewed-by on v1 2/3
#2 lore raw

Re: [PATCH 2/3] btrfs: return ptr error from compression workspace

Quoted 8 lines · Zach Brown

The btrfs compression wrappers translated errors from workspace allocation to either -ENOMEM or -1. The compression type workspace allocators are already returning a ERR_PTR(-ENOMEM). Just return that and get rid of the magical -1.

This helps a future patch return errors from the compression wrappers.

author
#3 lore raw

PATCH 3/3 btrfs: fix inline compressed read err corruption

In reply to Zach Brown

uncompress_inline() is silently dropping an error from btrfs_decompress() after testing it and zeroing the page that was supposed to hold decompressed data. This can silently turn compressed inline data in to zeros if decompression fails due to corrupt compressed data or memory allocation failure.

I have no idea why uncompress_inline() is zeroing the page for an error from btrfs_decompress() but not for the earlier ENOMEM from kmalloc(). Can someone explain this?

The fix is to pass the error to its caller. Which still has a BUG_ON(). So we fix that too.

I verified this by manually forcing the error from btrfs_decompress() for a silly named copy of od:

	if (!strcmp(current->comm, "failod"))
		ret = -ENOMEM;
  # od -x /mnt/btrfs/dir/80 | head -1
  0000000 3031 3038 310a 2d30 6f70 6e69 0a74 3031
  # echo 3 > /proc/sys/vm/drop_caches
  # cp $(which od) /tmp/failod
  # /tmp/failod -x /mnt/btrfs/dir/80 | head -1
  0000000 0000 0000 0000 0000 0000 0000 0000 0000
---
 fs/btrfs/inode.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)
fs/btrfs/inode.c +5 -2
index 0c0bb45..fc89fa7 100644
@@ -6091,7 +6091,7 @@ static noinline int uncompress_inline(struct btrfs_path *path,
 		kunmap_atomic(kaddr);
 	}
 	kfree(tmp);
-	return 0;
+	return ret;
 }
 
 /*
@@ -6292,7 +6292,10 @@ next:
 				ret = uncompress_inline(path, inode, page,
 							pg_offset,
 							extent_offset, item);
-				BUG_ON(ret); /* -ENOMEM */
+				if (ret) {
+					err = ret;
+					goto out;
+				}
 			} else {
 				map = kmap(page);
 				read_extent_buffer(leaf, map + pg_offset, ptr,
Signature · 1 line
1.8.1.4
Reviewed-by on v1 3/3
#4 lore raw

Re: [PATCH 3/3] btrfs: fix inline compressed read err corruption

Quoted 9 lines · Zach Brown

uncompress_inline() is silently dropping an error from btrfs_decompress() after testing it and zeroing the page that was supposed to hold decompressed data. This can silently turn compressed inline data in to zeros if decompression fails due to corrupt compressed data or memory allocation failure.

I have no idea why uncompress_inline() is zeroing the page for an error from btrfs_decompress() but not for the earlier ENOMEM from kmalloc(). Can someone explain this?

I don't see a reason for that in the current code, nor in the code that introduced it. The changes that could have affected that are in the error handling, but even with that in mind, the silent error does not make sense.

Quoted 1 line

And future ACK if you're going to kill the memset.

author
#5 lore raw

Re: [PATCH 3/3] btrfs: fix inline compressed read err corruption

Quoted 7 lines · David Sterba
Quoted 6 lines · Zach Brown

uncompress_inline() is silently dropping an error from btrfs_decompress() after testing it and zeroing the page that was supposed to hold decompressed data. This can silently turn compressed inline data in to zeros if decompression fails due to corrupt compressed data or memory allocation failure.

Quoted 8 lines
Quoted 3 lines

I have no idea why uncompress_inline() is zeroing the page for an error from btrfs_decompress() but not for the earlier ENOMEM from kmalloc(). Can someone explain this?

I don't see a reason for that in the current code, nor in the code that introduced it. The changes that could have affected that are in the error handling, but even with that in mind, the silent error does not make sense.

Agreed. I removed it and will send out a v2 of the series.

- z

#6 lore raw

Re: [PATCH 3/3] btrfs: fix inline compressed read err corruption

In reply to Zach Brown
Quoted 9 lines · Zach Brown

uncompress_inline() is silently dropping an error from btrfs_decompress() after testing it and zeroing the page that was supposed to hold decompressed data. This can silently turn compressed inline data in to zeros if decompression fails due to corrupt compressed data or memory allocation failure.

I have no idea why uncompress_inline() is zeroing the page for an error from btrfs_decompress() but not for the earlier ENOMEM from kmalloc(). Can someone explain this?

I guess that's because decompress() may have put part of real data on the page and then bail out, and we don't want those data to be exposed to users in this error case.

And kmalloc() 's ENOMEM runs before that decompress(), page has whatever random data.

-liubo

Quoted 54 lines

The fix is to pass the error to its caller. Which still has a BUG_ON(). So we fix that too.

I verified this by manually forcing the error from btrfs_decompress() for a silly named copy of od:

	if (!strcmp(current->comm, "failod"))
		ret = -ENOMEM;
  # od -x /mnt/btrfs/dir/80 | head -1
  0000000 3031 3038 310a 2d30 6f70 6e69 0a74 3031
  # echo 3 > /proc/sys/vm/drop_caches
  # cp $(which od) /tmp/failod
  # /tmp/failod -x /mnt/btrfs/dir/80 | head -1
  0000000 0000 0000 0000 0000 0000 0000 0000 0000
---
 fs/btrfs/inode.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)
fs/btrfs/inode.c +5 -2
index 0c0bb45..fc89fa7 100644
@@ -6091,7 +6091,7 @@ static noinline int uncompress_inline(struct btrfs_path *path,
 		kunmap_atomic(kaddr);
 	}
 	kfree(tmp);
-	return 0;
+	return ret;
 }
 
 /*
@@ -6292,7 +6292,10 @@ next:
 				ret = uncompress_inline(path, inode, page,
 							pg_offset,
 							extent_offset, item);
-				BUG_ON(ret); /* -ENOMEM */
+				if (ret) {
+					err = ret;
+					goto out;
+				}
 			} else {
 				map = kmap(page);
 				read_extent_buffer(leaf, map + pg_offset, ptr,
Signature · 6 lines
1.8.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#7 lore raw

Re: [PATCH 3/3] btrfs: fix inline compressed read err corruption

Quoted 11 lines · Liu Bo
Quoted 9 lines · Zach Brown

uncompress_inline() is silently dropping an error from btrfs_decompress() after testing it and zeroing the page that was supposed to hold decompressed data. This can silently turn compressed inline data in to zeros if decompression fails due to corrupt compressed data or memory allocation failure.

I have no idea why uncompress_inline() is zeroing the page for an error from btrfs_decompress() but not for the earlier ENOMEM from kmalloc(). Can someone explain this?

Quoted 6 lines

I guess that's because decompress() may have put part of real data on the page and then bail out, and we don't want those data to be exposed to users in this error case.

And kmalloc() 's ENOMEM runs before that decompress(), page has whatever random data.

But we don't return any data in case of error. In the unpatched code, there's no error so a zeroed page is returned, but this would not happen after Zach's fix.

#8 lore raw

Re: [PATCH 3/3] btrfs: fix inline compressed read err corruption

Quoted 16 lines · David Sterba
Quoted 15 lines · Liu Bo
Quoted 9 lines · Zach Brown

uncompress_inline() is silently dropping an error from btrfs_decompress() after testing it and zeroing the page that was supposed to hold decompressed data. This can silently turn compressed inline data in to zeros if decompression fails due to corrupt compressed data or memory allocation failure.

I have no idea why uncompress_inline() is zeroing the page for an error from btrfs_decompress() but not for the earlier ENOMEM from kmalloc(). Can someone explain this?

I guess that's because decompress() may have put part of real data on the page and then bail out, and we don't want those data to be exposed to users in this error case.

Quoted 6 lines
Quoted 2 lines

And kmalloc() 's ENOMEM runs before that decompress(), page has whatever random data.

But we don't return any data in case of error. In the unpatched code, there's no error so a zeroed page is returned, but this would not happen after Zach's fix.

I dug a little more, the zeroing goes all the way back to the original compression code. It looks like some paranoia of mine.

I'd say its fine to leave off as long as we don't mark the page uptodate.

-chris

#9 lore raw
In reply to Zach Brown
Quoted 17 lines · Zach Brown

The compression layer seems to have been built to return -1 and have callers make up errors that make sense. This isn't great because there are different classes of errors that originate down in the compression layer. Allocation failure and corrupt compressed data to name two.

fs/btrfs/lzo.c +1 -1
@@ -143,7 +143,7 @@ static int lzo_compress_pages(struct list_head *ws,
 		if (ret != LZO_E_OK) {
 			printk(KERN_DEBUG "BTRFS: deflate in loop returned %d\n",
 			       ret);
-			ret = -1;
+			ret = -EIO;
 			goto out;
 		}
 

fs/btrfs/lzo.c static int lzo_compress_pages(struct list_head *ws, · line 192

Quoted 5 lines
fs/btrfs/lzo.c +1 -1
@@ -189,7 +189,7 @@ static int lzo_compress_pages(struct list_head *ws,
 				kunmap(out_page);
 				if (nr_pages == nr_dest_pages) {
 					out_page = NULL;
-					ret = -1;
+					ret = -EIO;

This is not a true EIO, the error conditions says that the caller prepared nr_dest_pages for the compressed data but the compression wants more.

The number of pages is at most 128k / PAGE_SIZE.

It's a soft error, the data are written uncompressed. The closest errno here seems E2BIG that would apply in the following hunk as well.

fs/btrfs/lzo.c static int lzo_compress_pages(struct list_head *ws, · line 211

Quoted 9 lines
fs/btrfs/lzo.c +1 -1
 					goto out;
 				}
 
@@ -208,7 +208,7 @@ static int lzo_compress_pages(struct list_head *ws,
 
 		/* we're making it bigger, give up */
 		if (tot_in > 8192 && tot_in < tot_out) {
-			ret = -1;
+			ret = -EIO;

Here, E2BIG.

fs/btrfs/lzo.c cont: · line 338

Quoted 9 lines
fs/btrfs/lzo.c +1 -1
 			goto out;
 		}
 
@@ -335,7 +335,7 @@ cont:
 					break;
 
 				if (page_in_index + 1 >= total_pages_in) {
-					ret = -1;
+					ret = -EIO;

That looks like an internal error, we should never ask for more pages than is in the input, so the buffer offset calculations are wrong.

fs/btrfs/lzo.c cont: · line 341

Quoted 3 lines
fs/btrfs/lzo.c +0 -0
 					goto done;
 				}
 

Analogically the same applies to zlib. The rest of the EIOs look ok.

author
#10 lore raw
Quoted 28 lines · David Sterba
Quoted 17 lines · Zach Brown

The compression layer seems to have been built to return -1 and have callers make up errors that make sense. This isn't great because there are different classes of errors that originate down in the compression layer. Allocation failure and corrupt compressed data to name two.

fs/btrfs/lzo.c +1 -1
@@ -143,7 +143,7 @@ static int lzo_compress_pages(struct list_head *ws,
 		if (ret != LZO_E_OK) {
 			printk(KERN_DEBUG "BTRFS: deflate in loop returned %d\n",
 			       ret);
-			ret = -1;
+			ret = -EIO;
 			goto out;
 		}
 

fs/btrfs/lzo.c static int lzo_compress_pages(struct list_head *ws, · line 192

Quoted 5 lines
fs/btrfs/lzo.c +1 -1
@@ -189,7 +189,7 @@ static int lzo_compress_pages(struct list_head *ws,
 				kunmap(out_page);
 				if (nr_pages == nr_dest_pages) {
 					out_page = NULL;
-					ret = -1;
+					ret = -EIO;

This is not a true EIO, the error conditions says that the caller prepared nr_dest_pages for the compressed data but the compression wants more.

Quoted 4 lines

The number of pages is at most 128k / PAGE_SIZE.

It's a soft error, the data are written uncompressed. The closest errno here seems E2BIG that would apply in the following hunk as well.

Sure. *Anything* is better than the raw -EPERM :). I'll change these and resend v2.

Quoted 9 lines

fs/btrfs/lzo.c cont: · line 338

Quoted 6 lines
fs/btrfs/lzo.c +1 -1
@@ -335,7 +335,7 @@ cont:
 					break;
 
 				if (page_in_index + 1 >= total_pages_in) {
-					ret = -1;
+					ret = -EIO;

That looks like an internal error, we should never ask for more pages than is in the input, so the buffer offset calculations are wrong.

Yeah, but EIO is still arguably the right thing. There's nothing userspace can do about broken kernel code. We don't want to give them an error that could be misinterpreted as them having used an interface incorrectly. We could wrap WARN_ON_ONCE() around it, I suppose. I'm inclined to leave it as is.

- z

#11 lore raw
Quoted 10 lines · Zach Brown
Quoted 9 lines

fs/btrfs/lzo.c cont: · line 338

Quoted 6 lines
fs/btrfs/lzo.c +1 -1
@@ -335,7 +335,7 @@ cont:
 					break;
 
 				if (page_in_index + 1 >= total_pages_in) {
-					ret = -1;
+					ret = -EIO;

That looks like an internal error, we should never ask for more pages than is in the input, so the buffer offset calculations are wrong.

Quoted 5 lines

Yeah, but EIO is still arguably the right thing. There's nothing userspace can do about broken kernel code. We don't want to give them an error that could be misinterpreted as them having used an interface incorrectly. We could wrap WARN_ON_ONCE() around it, I suppose. I'm inclined to leave it as is.

Ok. An EIO is better than an ASSERT or BUG_ON, the error paths will handle it. I think adding the WARN_ON_ONCE is a good compromise, I'm not expecting to see this error but the stack trace will help.