2018-04-03 17:23:33 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2010-10-25 07:12:26 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008 Oracle. All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/slab.h>
|
2017-05-31 15:21:15 +00:00
|
|
|
#include <linux/mm.h>
|
2010-10-25 07:12:26 +00:00
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/pagemap.h>
|
|
|
|
#include <linux/bio.h>
|
|
|
|
#include <linux/lzo.h>
|
2017-05-26 07:44:59 +00:00
|
|
|
#include <linux/refcount.h>
|
2022-10-19 14:50:49 +00:00
|
|
|
#include "messages.h"
|
2010-10-25 07:12:26 +00:00
|
|
|
#include "compression.h"
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
#include "ctree.h"
|
2022-10-26 19:08:40 +00:00
|
|
|
#include "super.h"
|
2023-02-10 07:48:34 +00:00
|
|
|
#include "btrfs_inode.h"
|
2010-10-25 07:12:26 +00:00
|
|
|
|
|
|
|
#define LZO_LEN 4
|
|
|
|
|
2018-05-17 05:10:01 +00:00
|
|
|
/*
|
|
|
|
* Btrfs LZO compression format
|
|
|
|
*
|
|
|
|
* Regular and inlined LZO compressed data extents consist of:
|
|
|
|
*
|
|
|
|
* 1. Header
|
|
|
|
* Fixed size. LZO_LEN (4) bytes long, LE32.
|
|
|
|
* Records the total size (including the header) of compressed data.
|
|
|
|
*
|
|
|
|
* 2. Segment(s)
|
2018-11-28 11:05:13 +00:00
|
|
|
* Variable size. Each segment includes one segment header, followed by data
|
2018-05-17 05:10:01 +00:00
|
|
|
* payload.
|
|
|
|
* One regular LZO compressed extent can have one or more segments.
|
|
|
|
* For inlined LZO compressed extent, only one segment is allowed.
|
2021-09-27 07:22:04 +00:00
|
|
|
* One segment represents at most one sector of uncompressed data.
|
2018-05-17 05:10:01 +00:00
|
|
|
*
|
|
|
|
* 2.1 Segment header
|
|
|
|
* Fixed size. LZO_LEN (4) bytes long, LE32.
|
|
|
|
* Records the total size of the segment (not including the header).
|
2021-09-27 07:22:04 +00:00
|
|
|
* Segment header never crosses sector boundary, thus it's possible to
|
|
|
|
* have at most 3 padding zeros at the end of the sector.
|
2018-05-17 05:10:01 +00:00
|
|
|
*
|
|
|
|
* 2.2 Data Payload
|
2021-09-27 07:22:04 +00:00
|
|
|
* Variable size. Size up limit should be lzo1x_worst_compress(sectorsize)
|
|
|
|
* which is 4419 for a 4KiB sectorsize.
|
2018-05-17 05:10:01 +00:00
|
|
|
*
|
2021-09-27 07:22:04 +00:00
|
|
|
* Example with 4K sectorsize:
|
2018-05-17 05:10:01 +00:00
|
|
|
* Page 1:
|
|
|
|
* 0 0x2 0x4 0x6 0x8 0xa 0xc 0xe 0x10
|
|
|
|
* 0x0000 | Header | SegHdr 01 | Data payload 01 ... |
|
|
|
|
* ...
|
|
|
|
* 0x0ff0 | SegHdr N | Data payload N ... |00|
|
|
|
|
* ^^ padding zeros
|
|
|
|
* Page 2:
|
|
|
|
* 0x1000 | SegHdr N+1| Data payload N+1 ... |
|
|
|
|
*/
|
|
|
|
|
2022-02-02 21:44:54 +00:00
|
|
|
#define WORKSPACE_BUF_LENGTH (lzo1x_worst_compress(PAGE_SIZE))
|
|
|
|
#define WORKSPACE_CBUF_LENGTH (lzo1x_worst_compress(PAGE_SIZE))
|
|
|
|
|
2010-10-25 07:12:26 +00:00
|
|
|
struct workspace {
|
|
|
|
void *mem;
|
2013-06-06 13:38:50 +00:00
|
|
|
void *buf; /* where decompressed data goes */
|
|
|
|
void *cbuf; /* where compressed data goes */
|
2010-10-25 07:12:26 +00:00
|
|
|
struct list_head list;
|
|
|
|
};
|
|
|
|
|
2019-02-04 20:20:03 +00:00
|
|
|
static struct workspace_manager wsm;
|
|
|
|
|
2019-10-04 00:21:48 +00:00
|
|
|
void lzo_free_workspace(struct list_head *ws)
|
2010-10-25 07:12:26 +00:00
|
|
|
{
|
|
|
|
struct workspace *workspace = list_entry(ws, struct workspace, list);
|
|
|
|
|
2017-05-31 15:21:15 +00:00
|
|
|
kvfree(workspace->buf);
|
|
|
|
kvfree(workspace->cbuf);
|
|
|
|
kvfree(workspace->mem);
|
2010-10-25 07:12:26 +00:00
|
|
|
kfree(workspace);
|
|
|
|
}
|
|
|
|
|
2019-10-04 00:21:48 +00:00
|
|
|
struct list_head *lzo_alloc_workspace(unsigned int level)
|
2010-10-25 07:12:26 +00:00
|
|
|
{
|
|
|
|
struct workspace *workspace;
|
|
|
|
|
2017-05-31 15:21:15 +00:00
|
|
|
workspace = kzalloc(sizeof(*workspace), GFP_KERNEL);
|
2010-10-25 07:12:26 +00:00
|
|
|
if (!workspace)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
2023-05-22 14:51:10 +00:00
|
|
|
workspace->mem = kvmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL | __GFP_NOWARN);
|
|
|
|
workspace->buf = kvmalloc(WORKSPACE_BUF_LENGTH, GFP_KERNEL | __GFP_NOWARN);
|
|
|
|
workspace->cbuf = kvmalloc(WORKSPACE_CBUF_LENGTH, GFP_KERNEL | __GFP_NOWARN);
|
2010-10-25 07:12:26 +00:00
|
|
|
if (!workspace->mem || !workspace->buf || !workspace->cbuf)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&workspace->list);
|
|
|
|
|
|
|
|
return &workspace->list;
|
|
|
|
fail:
|
|
|
|
lzo_free_workspace(&workspace->list);
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void write_compress_length(char *buf, size_t len)
|
|
|
|
{
|
|
|
|
__le32 dlen;
|
|
|
|
|
|
|
|
dlen = cpu_to_le32(len);
|
|
|
|
memcpy(buf, &dlen, LZO_LEN);
|
|
|
|
}
|
|
|
|
|
2017-02-14 16:58:04 +00:00
|
|
|
static inline size_t read_compress_length(const char *buf)
|
2010-10-25 07:12:26 +00:00
|
|
|
{
|
|
|
|
__le32 dlen;
|
|
|
|
|
|
|
|
memcpy(&dlen, buf, LZO_LEN);
|
|
|
|
return le32_to_cpu(dlen);
|
|
|
|
}
|
|
|
|
|
2021-09-27 07:22:04 +00:00
|
|
|
/*
|
|
|
|
* Will do:
|
|
|
|
*
|
|
|
|
* - Write a segment header into the destination
|
|
|
|
* - Copy the compressed buffer into the destination
|
|
|
|
* - Make sure we have enough space in the last sector to fit a segment header
|
|
|
|
* If not, we will pad at most (LZO_LEN (4)) - 1 bytes of zeros.
|
|
|
|
*
|
|
|
|
* Will allocate new pages when needed.
|
|
|
|
*/
|
|
|
|
static int copy_compressed_data_to_page(char *compressed_data,
|
|
|
|
size_t compressed_size,
|
|
|
|
struct page **out_pages,
|
btrfs: fix a out-of-bound access in copy_compressed_data_to_page()
[BUG]
The following script can cause btrfs to crash:
$ mount -o compress-force=lzo $DEV /mnt
$ dd if=/dev/urandom of=/mnt/foo bs=4k count=1
$ sync
The call trace looks like this:
general protection fault, probably for non-canonical address 0xe04b37fccce3b000: 0000 [#1] PREEMPT SMP NOPTI
CPU: 5 PID: 164 Comm: kworker/u20:3 Not tainted 5.15.0-rc7-custom+ #4
Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
RIP: 0010:__memcpy+0x12/0x20
Call Trace:
lzo_compress_pages+0x236/0x540 [btrfs]
btrfs_compress_pages+0xaa/0xf0 [btrfs]
compress_file_range+0x431/0x8e0 [btrfs]
async_cow_start+0x12/0x30 [btrfs]
btrfs_work_helper+0xf6/0x3e0 [btrfs]
process_one_work+0x294/0x5d0
worker_thread+0x55/0x3c0
kthread+0x140/0x170
ret_from_fork+0x22/0x30
---[ end trace 63c3c0f131e61982 ]---
[CAUSE]
In lzo_compress_pages(), parameter @out_pages is not only an output
parameter (for the number of compressed pages), but also an input
parameter, as the upper limit of compressed pages we can utilize.
In commit d4088803f511 ("btrfs: subpage: make lzo_compress_pages()
compatible"), the refactoring doesn't take @out_pages as an input, thus
completely ignoring the limit.
And for compress-force case, we could hit incompressible data that
compressed size would go beyond the page limit, and cause the above
crash.
[FIX]
Save @out_pages as @max_nr_page, and pass it to lzo_compress_pages(),
and check if we're beyond the limit before accessing the pages.
Note: this also fixes crash on 32bit architectures that was suspected to
be caused by merge of btrfs patches to 5.16-rc1. Reported in
https://lore.kernel.org/all/20211104115001.GU20319@twin.jikos.cz/ .
Reported-by: Omar Sandoval <osandov@fb.com>
Fixes: d4088803f511 ("btrfs: subpage: make lzo_compress_pages() compatible")
Reviewed-by: Omar Sandoval <osandov@fb.com>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ add note ]
Signed-off-by: David Sterba <dsterba@suse.com>
2021-11-12 04:47:30 +00:00
|
|
|
unsigned long max_nr_page,
|
2021-09-27 07:22:04 +00:00
|
|
|
u32 *cur_out,
|
|
|
|
const u32 sectorsize)
|
|
|
|
{
|
|
|
|
u32 sector_bytes_left;
|
|
|
|
u32 orig_out;
|
|
|
|
struct page *cur_page;
|
2021-11-01 19:48:25 +00:00
|
|
|
char *kaddr;
|
2021-09-27 07:22:04 +00:00
|
|
|
|
btrfs: fix a out-of-bound access in copy_compressed_data_to_page()
[BUG]
The following script can cause btrfs to crash:
$ mount -o compress-force=lzo $DEV /mnt
$ dd if=/dev/urandom of=/mnt/foo bs=4k count=1
$ sync
The call trace looks like this:
general protection fault, probably for non-canonical address 0xe04b37fccce3b000: 0000 [#1] PREEMPT SMP NOPTI
CPU: 5 PID: 164 Comm: kworker/u20:3 Not tainted 5.15.0-rc7-custom+ #4
Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
RIP: 0010:__memcpy+0x12/0x20
Call Trace:
lzo_compress_pages+0x236/0x540 [btrfs]
btrfs_compress_pages+0xaa/0xf0 [btrfs]
compress_file_range+0x431/0x8e0 [btrfs]
async_cow_start+0x12/0x30 [btrfs]
btrfs_work_helper+0xf6/0x3e0 [btrfs]
process_one_work+0x294/0x5d0
worker_thread+0x55/0x3c0
kthread+0x140/0x170
ret_from_fork+0x22/0x30
---[ end trace 63c3c0f131e61982 ]---
[CAUSE]
In lzo_compress_pages(), parameter @out_pages is not only an output
parameter (for the number of compressed pages), but also an input
parameter, as the upper limit of compressed pages we can utilize.
In commit d4088803f511 ("btrfs: subpage: make lzo_compress_pages()
compatible"), the refactoring doesn't take @out_pages as an input, thus
completely ignoring the limit.
And for compress-force case, we could hit incompressible data that
compressed size would go beyond the page limit, and cause the above
crash.
[FIX]
Save @out_pages as @max_nr_page, and pass it to lzo_compress_pages(),
and check if we're beyond the limit before accessing the pages.
Note: this also fixes crash on 32bit architectures that was suspected to
be caused by merge of btrfs patches to 5.16-rc1. Reported in
https://lore.kernel.org/all/20211104115001.GU20319@twin.jikos.cz/ .
Reported-by: Omar Sandoval <osandov@fb.com>
Fixes: d4088803f511 ("btrfs: subpage: make lzo_compress_pages() compatible")
Reviewed-by: Omar Sandoval <osandov@fb.com>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ add note ]
Signed-off-by: David Sterba <dsterba@suse.com>
2021-11-12 04:47:30 +00:00
|
|
|
if ((*cur_out / PAGE_SIZE) >= max_nr_page)
|
|
|
|
return -E2BIG;
|
|
|
|
|
2021-09-27 07:22:04 +00:00
|
|
|
/*
|
|
|
|
* We never allow a segment header crossing sector boundary, previous
|
|
|
|
* run should ensure we have enough space left inside the sector.
|
|
|
|
*/
|
|
|
|
ASSERT((*cur_out / sectorsize) == (*cur_out + LZO_LEN - 1) / sectorsize);
|
|
|
|
|
|
|
|
cur_page = out_pages[*cur_out / PAGE_SIZE];
|
|
|
|
/* Allocate a new page */
|
|
|
|
if (!cur_page) {
|
2023-11-15 16:59:39 +00:00
|
|
|
cur_page = btrfs_alloc_compr_page();
|
2021-09-27 07:22:04 +00:00
|
|
|
if (!cur_page)
|
|
|
|
return -ENOMEM;
|
|
|
|
out_pages[*cur_out / PAGE_SIZE] = cur_page;
|
|
|
|
}
|
|
|
|
|
2022-05-31 14:53:34 +00:00
|
|
|
kaddr = kmap_local_page(cur_page);
|
2021-11-01 19:48:25 +00:00
|
|
|
write_compress_length(kaddr + offset_in_page(*cur_out),
|
2021-09-27 07:22:04 +00:00
|
|
|
compressed_size);
|
|
|
|
*cur_out += LZO_LEN;
|
|
|
|
|
|
|
|
orig_out = *cur_out;
|
|
|
|
|
|
|
|
/* Copy compressed data */
|
|
|
|
while (*cur_out - orig_out < compressed_size) {
|
|
|
|
u32 copy_len = min_t(u32, sectorsize - *cur_out % sectorsize,
|
|
|
|
orig_out + compressed_size - *cur_out);
|
|
|
|
|
2022-05-31 14:53:34 +00:00
|
|
|
kunmap_local(kaddr);
|
2021-11-18 20:41:14 +00:00
|
|
|
|
btrfs: fix a out-of-bound access in copy_compressed_data_to_page()
[BUG]
The following script can cause btrfs to crash:
$ mount -o compress-force=lzo $DEV /mnt
$ dd if=/dev/urandom of=/mnt/foo bs=4k count=1
$ sync
The call trace looks like this:
general protection fault, probably for non-canonical address 0xe04b37fccce3b000: 0000 [#1] PREEMPT SMP NOPTI
CPU: 5 PID: 164 Comm: kworker/u20:3 Not tainted 5.15.0-rc7-custom+ #4
Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
RIP: 0010:__memcpy+0x12/0x20
Call Trace:
lzo_compress_pages+0x236/0x540 [btrfs]
btrfs_compress_pages+0xaa/0xf0 [btrfs]
compress_file_range+0x431/0x8e0 [btrfs]
async_cow_start+0x12/0x30 [btrfs]
btrfs_work_helper+0xf6/0x3e0 [btrfs]
process_one_work+0x294/0x5d0
worker_thread+0x55/0x3c0
kthread+0x140/0x170
ret_from_fork+0x22/0x30
---[ end trace 63c3c0f131e61982 ]---
[CAUSE]
In lzo_compress_pages(), parameter @out_pages is not only an output
parameter (for the number of compressed pages), but also an input
parameter, as the upper limit of compressed pages we can utilize.
In commit d4088803f511 ("btrfs: subpage: make lzo_compress_pages()
compatible"), the refactoring doesn't take @out_pages as an input, thus
completely ignoring the limit.
And for compress-force case, we could hit incompressible data that
compressed size would go beyond the page limit, and cause the above
crash.
[FIX]
Save @out_pages as @max_nr_page, and pass it to lzo_compress_pages(),
and check if we're beyond the limit before accessing the pages.
Note: this also fixes crash on 32bit architectures that was suspected to
be caused by merge of btrfs patches to 5.16-rc1. Reported in
https://lore.kernel.org/all/20211104115001.GU20319@twin.jikos.cz/ .
Reported-by: Omar Sandoval <osandov@fb.com>
Fixes: d4088803f511 ("btrfs: subpage: make lzo_compress_pages() compatible")
Reviewed-by: Omar Sandoval <osandov@fb.com>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ add note ]
Signed-off-by: David Sterba <dsterba@suse.com>
2021-11-12 04:47:30 +00:00
|
|
|
if ((*cur_out / PAGE_SIZE) >= max_nr_page)
|
|
|
|
return -E2BIG;
|
|
|
|
|
2021-09-27 07:22:04 +00:00
|
|
|
cur_page = out_pages[*cur_out / PAGE_SIZE];
|
|
|
|
/* Allocate a new page */
|
|
|
|
if (!cur_page) {
|
2023-11-15 16:59:39 +00:00
|
|
|
cur_page = btrfs_alloc_compr_page();
|
2021-09-27 07:22:04 +00:00
|
|
|
if (!cur_page)
|
|
|
|
return -ENOMEM;
|
|
|
|
out_pages[*cur_out / PAGE_SIZE] = cur_page;
|
|
|
|
}
|
2022-05-31 14:53:34 +00:00
|
|
|
kaddr = kmap_local_page(cur_page);
|
2021-09-27 07:22:04 +00:00
|
|
|
|
2021-11-01 19:48:25 +00:00
|
|
|
memcpy(kaddr + offset_in_page(*cur_out),
|
2021-09-27 07:22:04 +00:00
|
|
|
compressed_data + *cur_out - orig_out, copy_len);
|
|
|
|
|
|
|
|
*cur_out += copy_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if we can fit the next segment header into the remaining space
|
|
|
|
* of the sector.
|
|
|
|
*/
|
|
|
|
sector_bytes_left = round_up(*cur_out, sectorsize) - *cur_out;
|
|
|
|
if (sector_bytes_left >= LZO_LEN || sector_bytes_left == 0)
|
2021-11-01 19:48:25 +00:00
|
|
|
goto out;
|
2021-09-27 07:22:04 +00:00
|
|
|
|
|
|
|
/* The remaining size is not enough, pad it with zeros */
|
2021-11-01 19:48:25 +00:00
|
|
|
memset(kaddr + offset_in_page(*cur_out), 0,
|
2021-09-27 07:22:04 +00:00
|
|
|
sector_bytes_left);
|
|
|
|
*cur_out += sector_bytes_left;
|
2021-11-01 19:48:25 +00:00
|
|
|
|
|
|
|
out:
|
2022-05-31 14:53:34 +00:00
|
|
|
kunmap_local(kaddr);
|
2021-09-27 07:22:04 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-10-01 20:38:34 +00:00
|
|
|
int lzo_compress_pages(struct list_head *ws, struct address_space *mapping,
|
|
|
|
u64 start, struct page **pages, unsigned long *out_pages,
|
|
|
|
unsigned long *total_in, unsigned long *total_out)
|
2010-10-25 07:12:26 +00:00
|
|
|
{
|
|
|
|
struct workspace *workspace = list_entry(ws, struct workspace, list);
|
2021-09-27 07:22:04 +00:00
|
|
|
const u32 sectorsize = btrfs_sb(mapping->host->i_sb)->sectorsize;
|
|
|
|
struct page *page_in = NULL;
|
2021-11-01 19:48:25 +00:00
|
|
|
char *sizes_ptr;
|
btrfs: fix a out-of-bound access in copy_compressed_data_to_page()
[BUG]
The following script can cause btrfs to crash:
$ mount -o compress-force=lzo $DEV /mnt
$ dd if=/dev/urandom of=/mnt/foo bs=4k count=1
$ sync
The call trace looks like this:
general protection fault, probably for non-canonical address 0xe04b37fccce3b000: 0000 [#1] PREEMPT SMP NOPTI
CPU: 5 PID: 164 Comm: kworker/u20:3 Not tainted 5.15.0-rc7-custom+ #4
Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
RIP: 0010:__memcpy+0x12/0x20
Call Trace:
lzo_compress_pages+0x236/0x540 [btrfs]
btrfs_compress_pages+0xaa/0xf0 [btrfs]
compress_file_range+0x431/0x8e0 [btrfs]
async_cow_start+0x12/0x30 [btrfs]
btrfs_work_helper+0xf6/0x3e0 [btrfs]
process_one_work+0x294/0x5d0
worker_thread+0x55/0x3c0
kthread+0x140/0x170
ret_from_fork+0x22/0x30
---[ end trace 63c3c0f131e61982 ]---
[CAUSE]
In lzo_compress_pages(), parameter @out_pages is not only an output
parameter (for the number of compressed pages), but also an input
parameter, as the upper limit of compressed pages we can utilize.
In commit d4088803f511 ("btrfs: subpage: make lzo_compress_pages()
compatible"), the refactoring doesn't take @out_pages as an input, thus
completely ignoring the limit.
And for compress-force case, we could hit incompressible data that
compressed size would go beyond the page limit, and cause the above
crash.
[FIX]
Save @out_pages as @max_nr_page, and pass it to lzo_compress_pages(),
and check if we're beyond the limit before accessing the pages.
Note: this also fixes crash on 32bit architectures that was suspected to
be caused by merge of btrfs patches to 5.16-rc1. Reported in
https://lore.kernel.org/all/20211104115001.GU20319@twin.jikos.cz/ .
Reported-by: Omar Sandoval <osandov@fb.com>
Fixes: d4088803f511 ("btrfs: subpage: make lzo_compress_pages() compatible")
Reviewed-by: Omar Sandoval <osandov@fb.com>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ add note ]
Signed-off-by: David Sterba <dsterba@suse.com>
2021-11-12 04:47:30 +00:00
|
|
|
const unsigned long max_nr_page = *out_pages;
|
2010-10-25 07:12:26 +00:00
|
|
|
int ret = 0;
|
2021-09-27 07:22:04 +00:00
|
|
|
/* Points to the file offset of input data */
|
|
|
|
u64 cur_in = start;
|
|
|
|
/* Points to the current output byte */
|
|
|
|
u32 cur_out = 0;
|
|
|
|
u32 len = *total_out;
|
2010-10-25 07:12:26 +00:00
|
|
|
|
btrfs: fix a out-of-bound access in copy_compressed_data_to_page()
[BUG]
The following script can cause btrfs to crash:
$ mount -o compress-force=lzo $DEV /mnt
$ dd if=/dev/urandom of=/mnt/foo bs=4k count=1
$ sync
The call trace looks like this:
general protection fault, probably for non-canonical address 0xe04b37fccce3b000: 0000 [#1] PREEMPT SMP NOPTI
CPU: 5 PID: 164 Comm: kworker/u20:3 Not tainted 5.15.0-rc7-custom+ #4
Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
RIP: 0010:__memcpy+0x12/0x20
Call Trace:
lzo_compress_pages+0x236/0x540 [btrfs]
btrfs_compress_pages+0xaa/0xf0 [btrfs]
compress_file_range+0x431/0x8e0 [btrfs]
async_cow_start+0x12/0x30 [btrfs]
btrfs_work_helper+0xf6/0x3e0 [btrfs]
process_one_work+0x294/0x5d0
worker_thread+0x55/0x3c0
kthread+0x140/0x170
ret_from_fork+0x22/0x30
---[ end trace 63c3c0f131e61982 ]---
[CAUSE]
In lzo_compress_pages(), parameter @out_pages is not only an output
parameter (for the number of compressed pages), but also an input
parameter, as the upper limit of compressed pages we can utilize.
In commit d4088803f511 ("btrfs: subpage: make lzo_compress_pages()
compatible"), the refactoring doesn't take @out_pages as an input, thus
completely ignoring the limit.
And for compress-force case, we could hit incompressible data that
compressed size would go beyond the page limit, and cause the above
crash.
[FIX]
Save @out_pages as @max_nr_page, and pass it to lzo_compress_pages(),
and check if we're beyond the limit before accessing the pages.
Note: this also fixes crash on 32bit architectures that was suspected to
be caused by merge of btrfs patches to 5.16-rc1. Reported in
https://lore.kernel.org/all/20211104115001.GU20319@twin.jikos.cz/ .
Reported-by: Omar Sandoval <osandov@fb.com>
Fixes: d4088803f511 ("btrfs: subpage: make lzo_compress_pages() compatible")
Reviewed-by: Omar Sandoval <osandov@fb.com>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ add note ]
Signed-off-by: David Sterba <dsterba@suse.com>
2021-11-12 04:47:30 +00:00
|
|
|
ASSERT(max_nr_page > 0);
|
2010-10-25 07:12:26 +00:00
|
|
|
*out_pages = 0;
|
|
|
|
*total_out = 0;
|
|
|
|
*total_in = 0;
|
|
|
|
|
|
|
|
/*
|
2021-09-27 07:22:04 +00:00
|
|
|
* Skip the header for now, we will later come back and write the total
|
|
|
|
* compressed size
|
2010-10-25 07:12:26 +00:00
|
|
|
*/
|
2021-09-27 07:22:04 +00:00
|
|
|
cur_out += LZO_LEN;
|
|
|
|
while (cur_in < start + len) {
|
2021-11-01 19:48:25 +00:00
|
|
|
char *data_in;
|
2021-09-27 07:22:04 +00:00
|
|
|
const u32 sectorsize_mask = sectorsize - 1;
|
|
|
|
u32 sector_off = (cur_in - start) & sectorsize_mask;
|
|
|
|
u32 in_len;
|
|
|
|
size_t out_len;
|
|
|
|
|
|
|
|
/* Get the input page first */
|
|
|
|
if (!page_in) {
|
|
|
|
page_in = find_get_page(mapping, cur_in >> PAGE_SHIFT);
|
|
|
|
ASSERT(page_in);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Compress at most one sector of data each time */
|
|
|
|
in_len = min_t(u32, start + len - cur_in, sectorsize - sector_off);
|
|
|
|
ASSERT(in_len);
|
2022-05-31 14:53:34 +00:00
|
|
|
data_in = kmap_local_page(page_in);
|
2021-11-01 19:48:25 +00:00
|
|
|
ret = lzo1x_1_compress(data_in +
|
2021-09-27 07:22:04 +00:00
|
|
|
offset_in_page(cur_in), in_len,
|
|
|
|
workspace->cbuf, &out_len,
|
|
|
|
workspace->mem);
|
2022-05-31 14:53:34 +00:00
|
|
|
kunmap_local(data_in);
|
2021-09-27 07:22:04 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
pr_debug("BTRFS: lzo in loop returned %d\n", ret);
|
2014-05-09 21:15:08 +00:00
|
|
|
ret = -EIO;
|
2010-10-25 07:12:26 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2021-09-27 07:22:04 +00:00
|
|
|
ret = copy_compressed_data_to_page(workspace->cbuf, out_len,
|
btrfs: fix a out-of-bound access in copy_compressed_data_to_page()
[BUG]
The following script can cause btrfs to crash:
$ mount -o compress-force=lzo $DEV /mnt
$ dd if=/dev/urandom of=/mnt/foo bs=4k count=1
$ sync
The call trace looks like this:
general protection fault, probably for non-canonical address 0xe04b37fccce3b000: 0000 [#1] PREEMPT SMP NOPTI
CPU: 5 PID: 164 Comm: kworker/u20:3 Not tainted 5.15.0-rc7-custom+ #4
Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
RIP: 0010:__memcpy+0x12/0x20
Call Trace:
lzo_compress_pages+0x236/0x540 [btrfs]
btrfs_compress_pages+0xaa/0xf0 [btrfs]
compress_file_range+0x431/0x8e0 [btrfs]
async_cow_start+0x12/0x30 [btrfs]
btrfs_work_helper+0xf6/0x3e0 [btrfs]
process_one_work+0x294/0x5d0
worker_thread+0x55/0x3c0
kthread+0x140/0x170
ret_from_fork+0x22/0x30
---[ end trace 63c3c0f131e61982 ]---
[CAUSE]
In lzo_compress_pages(), parameter @out_pages is not only an output
parameter (for the number of compressed pages), but also an input
parameter, as the upper limit of compressed pages we can utilize.
In commit d4088803f511 ("btrfs: subpage: make lzo_compress_pages()
compatible"), the refactoring doesn't take @out_pages as an input, thus
completely ignoring the limit.
And for compress-force case, we could hit incompressible data that
compressed size would go beyond the page limit, and cause the above
crash.
[FIX]
Save @out_pages as @max_nr_page, and pass it to lzo_compress_pages(),
and check if we're beyond the limit before accessing the pages.
Note: this also fixes crash on 32bit architectures that was suspected to
be caused by merge of btrfs patches to 5.16-rc1. Reported in
https://lore.kernel.org/all/20211104115001.GU20319@twin.jikos.cz/ .
Reported-by: Omar Sandoval <osandov@fb.com>
Fixes: d4088803f511 ("btrfs: subpage: make lzo_compress_pages() compatible")
Reviewed-by: Omar Sandoval <osandov@fb.com>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ add note ]
Signed-off-by: David Sterba <dsterba@suse.com>
2021-11-12 04:47:30 +00:00
|
|
|
pages, max_nr_page,
|
|
|
|
&cur_out, sectorsize);
|
2021-09-27 07:22:04 +00:00
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
2010-10-25 07:12:26 +00:00
|
|
|
|
2021-09-27 07:22:04 +00:00
|
|
|
cur_in += in_len;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if we're making it bigger after two sectors. And if
|
|
|
|
* it is so, give up.
|
|
|
|
*/
|
|
|
|
if (cur_in - start > sectorsize * 2 && cur_in - start < cur_out) {
|
2014-05-09 21:15:08 +00:00
|
|
|
ret = -E2BIG;
|
2010-10-25 07:12:26 +00:00
|
|
|
goto out;
|
2013-07-01 18:33:39 +00:00
|
|
|
}
|
2010-10-25 07:12:26 +00:00
|
|
|
|
2021-09-27 07:22:04 +00:00
|
|
|
/* Check if we have reached page boundary */
|
2023-01-03 05:11:37 +00:00
|
|
|
if (PAGE_ALIGNED(cur_in)) {
|
2021-09-27 07:22:04 +00:00
|
|
|
put_page(page_in);
|
|
|
|
page_in = NULL;
|
|
|
|
}
|
2017-05-29 23:18:04 +00:00
|
|
|
}
|
2010-10-25 07:12:26 +00:00
|
|
|
|
2021-09-27 07:22:04 +00:00
|
|
|
/* Store the size of all chunks of compressed data */
|
2021-10-27 08:44:21 +00:00
|
|
|
sizes_ptr = kmap_local_page(pages[0]);
|
2021-11-01 19:48:25 +00:00
|
|
|
write_compress_length(sizes_ptr, cur_out);
|
2021-10-27 08:44:21 +00:00
|
|
|
kunmap_local(sizes_ptr);
|
2010-10-25 07:12:26 +00:00
|
|
|
|
|
|
|
ret = 0;
|
2021-09-27 07:22:04 +00:00
|
|
|
*total_out = cur_out;
|
|
|
|
*total_in = cur_in - start;
|
2010-10-25 07:12:26 +00:00
|
|
|
out:
|
2021-11-20 08:34:11 +00:00
|
|
|
if (page_in)
|
|
|
|
put_page(page_in);
|
2021-09-27 07:22:04 +00:00
|
|
|
*out_pages = DIV_ROUND_UP(cur_out, PAGE_SIZE);
|
2010-10-25 07:12:26 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
/*
|
|
|
|
* Copy the compressed segment payload into @dest.
|
|
|
|
*
|
|
|
|
* For the payload there will be no padding, just need to do page switching.
|
|
|
|
*/
|
|
|
|
static void copy_compressed_segment(struct compressed_bio *cb,
|
|
|
|
char *dest, u32 len, u32 *cur_in)
|
|
|
|
{
|
|
|
|
u32 orig_in = *cur_in;
|
|
|
|
|
|
|
|
while (*cur_in < orig_in + len) {
|
|
|
|
struct page *cur_page;
|
|
|
|
u32 copy_len = min_t(u32, PAGE_SIZE - offset_in_page(*cur_in),
|
|
|
|
orig_in + len - *cur_in);
|
|
|
|
|
|
|
|
ASSERT(copy_len);
|
|
|
|
cur_page = cb->compressed_pages[*cur_in / PAGE_SIZE];
|
|
|
|
|
2022-05-31 14:53:34 +00:00
|
|
|
memcpy_from_page(dest + *cur_in - orig_in, cur_page,
|
|
|
|
offset_in_page(*cur_in), copy_len);
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
|
|
|
|
*cur_in += copy_len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-01 20:38:34 +00:00
|
|
|
int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
|
2010-10-25 07:12:26 +00:00
|
|
|
{
|
|
|
|
struct workspace *workspace = list_entry(ws, struct workspace, list);
|
2023-02-10 07:48:34 +00:00
|
|
|
const struct btrfs_fs_info *fs_info = cb->bbio.inode->root->fs_info;
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
const u32 sectorsize = fs_info->sectorsize;
|
2021-10-27 08:44:21 +00:00
|
|
|
char *kaddr;
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
int ret;
|
|
|
|
/* Compressed data length, can be unaligned */
|
|
|
|
u32 len_in;
|
|
|
|
/* Offset inside the compressed data */
|
|
|
|
u32 cur_in = 0;
|
|
|
|
/* Bytes decompressed so far */
|
|
|
|
u32 cur_out = 0;
|
|
|
|
|
2022-05-31 14:53:34 +00:00
|
|
|
kaddr = kmap_local_page(cb->compressed_pages[0]);
|
2021-10-27 08:44:21 +00:00
|
|
|
len_in = read_compress_length(kaddr);
|
2022-05-31 14:53:34 +00:00
|
|
|
kunmap_local(kaddr);
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
cur_in += LZO_LEN;
|
2010-10-25 07:12:26 +00:00
|
|
|
|
2018-05-15 06:57:51 +00:00
|
|
|
/*
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
* LZO header length check
|
2018-05-15 06:57:51 +00:00
|
|
|
*
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
* The total length should not exceed the maximum extent length,
|
|
|
|
* and all sectors should be used.
|
|
|
|
* If this happens, it means the compressed extent is corrupted.
|
2018-05-15 06:57:51 +00:00
|
|
|
*/
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
if (len_in > min_t(size_t, BTRFS_MAX_COMPRESSED, cb->compressed_len) ||
|
|
|
|
round_up(len_in, sectorsize) < cb->compressed_len) {
|
|
|
|
btrfs_err(fs_info,
|
|
|
|
"invalid lzo header, lzo len %u compressed len %u",
|
|
|
|
len_in, cb->compressed_len);
|
|
|
|
return -EUCLEAN;
|
2018-05-15 06:57:51 +00:00
|
|
|
}
|
2010-10-25 07:12:26 +00:00
|
|
|
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
/* Go through each lzo segment */
|
|
|
|
while (cur_in < len_in) {
|
|
|
|
struct page *cur_page;
|
|
|
|
/* Length of the compressed segment */
|
|
|
|
u32 seg_len;
|
|
|
|
u32 sector_bytes_left;
|
|
|
|
size_t out_len = lzo1x_worst_compress(sectorsize);
|
2010-10-25 07:12:26 +00:00
|
|
|
|
2018-05-15 06:57:51 +00:00
|
|
|
/*
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
* We should always have enough space for one segment header
|
|
|
|
* inside current sector.
|
2018-05-15 06:57:51 +00:00
|
|
|
*/
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
ASSERT(cur_in / sectorsize ==
|
|
|
|
(cur_in + LZO_LEN - 1) / sectorsize);
|
|
|
|
cur_page = cb->compressed_pages[cur_in / PAGE_SIZE];
|
|
|
|
ASSERT(cur_page);
|
2022-05-31 14:53:34 +00:00
|
|
|
kaddr = kmap_local_page(cur_page);
|
2021-10-27 08:44:21 +00:00
|
|
|
seg_len = read_compress_length(kaddr + offset_in_page(cur_in));
|
2022-05-31 14:53:34 +00:00
|
|
|
kunmap_local(kaddr);
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
cur_in += LZO_LEN;
|
|
|
|
|
2022-02-02 21:44:54 +00:00
|
|
|
if (seg_len > WORKSPACE_CBUF_LENGTH) {
|
2022-02-02 21:44:55 +00:00
|
|
|
/*
|
|
|
|
* seg_len shouldn't be larger than we have allocated
|
|
|
|
* for workspace->cbuf
|
|
|
|
*/
|
|
|
|
btrfs_err(fs_info, "unexpectedly large lzo segment len %u",
|
|
|
|
seg_len);
|
2023-03-07 16:39:38 +00:00
|
|
|
return -EIO;
|
2022-02-02 21:44:55 +00:00
|
|
|
}
|
|
|
|
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
/* Copy the compressed segment payload into workspace */
|
|
|
|
copy_compressed_segment(cb, workspace->cbuf, seg_len, &cur_in);
|
|
|
|
|
|
|
|
/* Decompress the data */
|
|
|
|
ret = lzo1x_decompress_safe(workspace->cbuf, seg_len,
|
|
|
|
workspace->buf, &out_len);
|
2010-10-25 07:12:26 +00:00
|
|
|
if (ret != LZO_E_OK) {
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
btrfs_err(fs_info, "failed to decompress");
|
2023-03-07 16:39:38 +00:00
|
|
|
return -EIO;
|
2010-10-25 07:12:26 +00:00
|
|
|
}
|
|
|
|
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
/* Copy the data into inode pages */
|
|
|
|
ret = btrfs_decompress_buf2page(workspace->buf, out_len, cb, cur_out);
|
|
|
|
cur_out += out_len;
|
2010-10-25 07:12:26 +00:00
|
|
|
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
/* All data read, exit */
|
|
|
|
if (ret == 0)
|
2023-03-07 16:39:38 +00:00
|
|
|
return 0;
|
btrfs: rework lzo_decompress_bio() to make it subpage compatible
For the initial subpage support, although we won't support compressed
write, we still need to support compressed read.
But for lzo_decompress_bio() it has several problems:
- The abuse of PAGE_SIZE for boundary detection
For subpage case, we should follow sectorsize to detect the padding
zeros.
Using PAGE_SIZE will cause subpage compress read to skip certain
bytes, and causing read error.
- Too many helper variables
There are half a dozen helper variables, which is only making things
harder to read
This patch will rework lzo_decompress_bio() to make it work for subpage:
- Use sectorsize to do boundary check, while still use PAGE_SIZE for
page switching
This allows us to have the same on-disk format for 4K sectorsize fs,
while take advantage of larger page size.
- Use two main cursors
Only @cur_in and @cur_out is utilized as the main cursor.
The helper variables will only be declared inside the loop, and only 2
helper variables needed.
- Introduce a helper function to copy compressed segment payload
Introduce a new helper, copy_compressed_segment(), to copy a
compressed segment to workspace buffer.
This function will handle the page switching.
Now the net result is, with all the excessive comments and new helper
function, the refactored code is still smaller, and easier to read.
For other decompression code, they have no special padding rule, thus no
need to bother for initial subpage support, but will be refactored to
the same style later.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-26 06:34:55 +00:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
/* Check if the sector has enough space for a segment header */
|
|
|
|
sector_bytes_left = sectorsize - (cur_in % sectorsize);
|
|
|
|
if (sector_bytes_left >= LZO_LEN)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Skip the padding zeros */
|
|
|
|
cur_in += sector_bytes_left;
|
2010-10-25 07:12:26 +00:00
|
|
|
}
|
2023-03-07 16:39:38 +00:00
|
|
|
|
|
|
|
return 0;
|
2010-10-25 07:12:26 +00:00
|
|
|
}
|
|
|
|
|
2022-11-07 16:30:21 +00:00
|
|
|
int lzo_decompress(struct list_head *ws, const u8 *data_in,
|
btrfs: lzo: fix and simplify the inline extent decompression
[BUG]
If we have a filesystem with 4k sectorsize, and an inlined compressed
extent created like this:
item 4 key (257 INODE_ITEM 0) itemoff 15863 itemsize 160
generation 8 transid 8 size 4096 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 5 key (257 INODE_REF 256) itemoff 15839 itemsize 24
index 2 namelen 14 name: source_inlined
item 6 key (257 EXTENT_DATA 0) itemoff 15770 itemsize 69
generation 8 type 0 (inline)
inline extent data size 48 ram_bytes 4096 compression 2 (lzo)
Then trying to reflink that extent in an aarch64 system with 64K page
size, the reflink would just fail:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
XFS_IOC_CLONE_RANGE: Input/output error
[CAUSE]
In zlib_decompress(), we didn't treat @start_byte as just a page offset,
but also use it as an indicator on whether we should error out, without
any proper explanation (this is from the very beginning of btrfs).
In reality, for subpage cases, although @start_byte can be non-zero,
we should never switch input/output buffer nor error out, since the whole
input/output buffer should never exceed one sector.
Note: The above assumption is only not true if we're going to support
multi-page sectorsize.
Thus the current code using @start_byte as a condition to switch
input/output buffer or finish the decompression is completely incorrect.
[FIX]
The fix involves several modifications:
- Rename @start_byte to @dest_pgoff to properly express its meaning
- Use @sectorsize other than PAGE_SIZE to properly initialize the
output buffer size
- Use correct destination offset inside the destination page
- Use memcpy_to_page() to copy the contents to the destination page
- Use memzero_page() to zero out the tailing part
- Consider early end as an error
After the fix, even on 64K page sized aarch64, above reflink now
works as expected:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
linked 4096/4096 bytes at offset 61440
And results the correct file layout:
item 9 key (258 INODE_ITEM 0) itemoff 15542 itemsize 160
generation 10 transid 10 size 65536 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 10 key (258 INODE_REF 256) itemoff 15528 itemsize 14
index 3 namelen 4 name: dest
item 11 key (258 XATTR_ITEM 3817753667) itemoff 15445 itemsize 83
location key (0 UNKNOWN.0 0) type XATTR
transid 10 data_len 37 name_len 16
name: security.selinux
data unconfined_u:object_r:unlabeled_t:s0
item 12 key (258 EXTENT_DATA 61440) itemoff 15392 itemsize 53
generation 10 type 1 (regular)
extent data disk byte 13631488 nr 4096
extent data offset 0 nr 4096 ram 4096
extent compression 0 (none)
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2024-01-08 09:08:45 +00:00
|
|
|
struct page *dest_page, unsigned long dest_pgoff, size_t srclen,
|
2019-10-01 20:38:34 +00:00
|
|
|
size_t destlen)
|
2010-10-25 07:12:26 +00:00
|
|
|
{
|
|
|
|
struct workspace *workspace = list_entry(ws, struct workspace, list);
|
btrfs: lzo: fix and simplify the inline extent decompression
[BUG]
If we have a filesystem with 4k sectorsize, and an inlined compressed
extent created like this:
item 4 key (257 INODE_ITEM 0) itemoff 15863 itemsize 160
generation 8 transid 8 size 4096 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 5 key (257 INODE_REF 256) itemoff 15839 itemsize 24
index 2 namelen 14 name: source_inlined
item 6 key (257 EXTENT_DATA 0) itemoff 15770 itemsize 69
generation 8 type 0 (inline)
inline extent data size 48 ram_bytes 4096 compression 2 (lzo)
Then trying to reflink that extent in an aarch64 system with 64K page
size, the reflink would just fail:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
XFS_IOC_CLONE_RANGE: Input/output error
[CAUSE]
In zlib_decompress(), we didn't treat @start_byte as just a page offset,
but also use it as an indicator on whether we should error out, without
any proper explanation (this is from the very beginning of btrfs).
In reality, for subpage cases, although @start_byte can be non-zero,
we should never switch input/output buffer nor error out, since the whole
input/output buffer should never exceed one sector.
Note: The above assumption is only not true if we're going to support
multi-page sectorsize.
Thus the current code using @start_byte as a condition to switch
input/output buffer or finish the decompression is completely incorrect.
[FIX]
The fix involves several modifications:
- Rename @start_byte to @dest_pgoff to properly express its meaning
- Use @sectorsize other than PAGE_SIZE to properly initialize the
output buffer size
- Use correct destination offset inside the destination page
- Use memcpy_to_page() to copy the contents to the destination page
- Use memzero_page() to zero out the tailing part
- Consider early end as an error
After the fix, even on 64K page sized aarch64, above reflink now
works as expected:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
linked 4096/4096 bytes at offset 61440
And results the correct file layout:
item 9 key (258 INODE_ITEM 0) itemoff 15542 itemsize 160
generation 10 transid 10 size 65536 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 10 key (258 INODE_REF 256) itemoff 15528 itemsize 14
index 3 namelen 4 name: dest
item 11 key (258 XATTR_ITEM 3817753667) itemoff 15445 itemsize 83
location key (0 UNKNOWN.0 0) type XATTR
transid 10 data_len 37 name_len 16
name: security.selinux
data unconfined_u:object_r:unlabeled_t:s0
item 12 key (258 EXTENT_DATA 61440) itemoff 15392 itemsize 53
generation 10 type 1 (regular)
extent data disk byte 13631488 nr 4096
extent data offset 0 nr 4096 ram 4096
extent compression 0 (none)
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2024-01-08 09:08:45 +00:00
|
|
|
struct btrfs_fs_info *fs_info = btrfs_sb(dest_page->mapping->host->i_sb);
|
|
|
|
const u32 sectorsize = fs_info->sectorsize;
|
2010-10-25 07:12:26 +00:00
|
|
|
size_t in_len;
|
|
|
|
size_t out_len;
|
2022-02-02 21:44:54 +00:00
|
|
|
size_t max_segment_len = WORKSPACE_BUF_LENGTH;
|
2010-10-25 07:12:26 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
2018-05-17 06:10:29 +00:00
|
|
|
if (srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2)
|
|
|
|
return -EUCLEAN;
|
2010-10-25 07:12:26 +00:00
|
|
|
|
2018-05-17 06:10:29 +00:00
|
|
|
in_len = read_compress_length(data_in);
|
|
|
|
if (in_len != srclen)
|
|
|
|
return -EUCLEAN;
|
2010-10-25 07:12:26 +00:00
|
|
|
data_in += LZO_LEN;
|
|
|
|
|
|
|
|
in_len = read_compress_length(data_in);
|
2018-05-17 06:10:29 +00:00
|
|
|
if (in_len != srclen - LZO_LEN * 2) {
|
|
|
|
ret = -EUCLEAN;
|
|
|
|
goto out;
|
|
|
|
}
|
2010-10-25 07:12:26 +00:00
|
|
|
data_in += LZO_LEN;
|
|
|
|
|
btrfs: lzo: fix and simplify the inline extent decompression
[BUG]
If we have a filesystem with 4k sectorsize, and an inlined compressed
extent created like this:
item 4 key (257 INODE_ITEM 0) itemoff 15863 itemsize 160
generation 8 transid 8 size 4096 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 5 key (257 INODE_REF 256) itemoff 15839 itemsize 24
index 2 namelen 14 name: source_inlined
item 6 key (257 EXTENT_DATA 0) itemoff 15770 itemsize 69
generation 8 type 0 (inline)
inline extent data size 48 ram_bytes 4096 compression 2 (lzo)
Then trying to reflink that extent in an aarch64 system with 64K page
size, the reflink would just fail:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
XFS_IOC_CLONE_RANGE: Input/output error
[CAUSE]
In zlib_decompress(), we didn't treat @start_byte as just a page offset,
but also use it as an indicator on whether we should error out, without
any proper explanation (this is from the very beginning of btrfs).
In reality, for subpage cases, although @start_byte can be non-zero,
we should never switch input/output buffer nor error out, since the whole
input/output buffer should never exceed one sector.
Note: The above assumption is only not true if we're going to support
multi-page sectorsize.
Thus the current code using @start_byte as a condition to switch
input/output buffer or finish the decompression is completely incorrect.
[FIX]
The fix involves several modifications:
- Rename @start_byte to @dest_pgoff to properly express its meaning
- Use @sectorsize other than PAGE_SIZE to properly initialize the
output buffer size
- Use correct destination offset inside the destination page
- Use memcpy_to_page() to copy the contents to the destination page
- Use memzero_page() to zero out the tailing part
- Consider early end as an error
After the fix, even on 64K page sized aarch64, above reflink now
works as expected:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
linked 4096/4096 bytes at offset 61440
And results the correct file layout:
item 9 key (258 INODE_ITEM 0) itemoff 15542 itemsize 160
generation 10 transid 10 size 65536 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 10 key (258 INODE_REF 256) itemoff 15528 itemsize 14
index 3 namelen 4 name: dest
item 11 key (258 XATTR_ITEM 3817753667) itemoff 15445 itemsize 83
location key (0 UNKNOWN.0 0) type XATTR
transid 10 data_len 37 name_len 16
name: security.selinux
data unconfined_u:object_r:unlabeled_t:s0
item 12 key (258 EXTENT_DATA 61440) itemoff 15392 itemsize 53
generation 10 type 1 (regular)
extent data disk byte 13631488 nr 4096
extent data offset 0 nr 4096 ram 4096
extent compression 0 (none)
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2024-01-08 09:08:45 +00:00
|
|
|
out_len = sectorsize;
|
2010-10-25 07:12:26 +00:00
|
|
|
ret = lzo1x_decompress_safe(data_in, in_len, workspace->buf, &out_len);
|
|
|
|
if (ret != LZO_E_OK) {
|
2016-09-20 14:05:01 +00:00
|
|
|
pr_warn("BTRFS: decompress failed!\n");
|
2014-05-09 21:15:08 +00:00
|
|
|
ret = -EIO;
|
2010-10-25 07:12:26 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
btrfs: lzo: fix and simplify the inline extent decompression
[BUG]
If we have a filesystem with 4k sectorsize, and an inlined compressed
extent created like this:
item 4 key (257 INODE_ITEM 0) itemoff 15863 itemsize 160
generation 8 transid 8 size 4096 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 5 key (257 INODE_REF 256) itemoff 15839 itemsize 24
index 2 namelen 14 name: source_inlined
item 6 key (257 EXTENT_DATA 0) itemoff 15770 itemsize 69
generation 8 type 0 (inline)
inline extent data size 48 ram_bytes 4096 compression 2 (lzo)
Then trying to reflink that extent in an aarch64 system with 64K page
size, the reflink would just fail:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
XFS_IOC_CLONE_RANGE: Input/output error
[CAUSE]
In zlib_decompress(), we didn't treat @start_byte as just a page offset,
but also use it as an indicator on whether we should error out, without
any proper explanation (this is from the very beginning of btrfs).
In reality, for subpage cases, although @start_byte can be non-zero,
we should never switch input/output buffer nor error out, since the whole
input/output buffer should never exceed one sector.
Note: The above assumption is only not true if we're going to support
multi-page sectorsize.
Thus the current code using @start_byte as a condition to switch
input/output buffer or finish the decompression is completely incorrect.
[FIX]
The fix involves several modifications:
- Rename @start_byte to @dest_pgoff to properly express its meaning
- Use @sectorsize other than PAGE_SIZE to properly initialize the
output buffer size
- Use correct destination offset inside the destination page
- Use memcpy_to_page() to copy the contents to the destination page
- Use memzero_page() to zero out the tailing part
- Consider early end as an error
After the fix, even on 64K page sized aarch64, above reflink now
works as expected:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
linked 4096/4096 bytes at offset 61440
And results the correct file layout:
item 9 key (258 INODE_ITEM 0) itemoff 15542 itemsize 160
generation 10 transid 10 size 65536 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 10 key (258 INODE_REF 256) itemoff 15528 itemsize 14
index 3 namelen 4 name: dest
item 11 key (258 XATTR_ITEM 3817753667) itemoff 15445 itemsize 83
location key (0 UNKNOWN.0 0) type XATTR
transid 10 data_len 37 name_len 16
name: security.selinux
data unconfined_u:object_r:unlabeled_t:s0
item 12 key (258 EXTENT_DATA 61440) itemoff 15392 itemsize 53
generation 10 type 1 (regular)
extent data disk byte 13631488 nr 4096
extent data offset 0 nr 4096 ram 4096
extent compression 0 (none)
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2024-01-08 09:08:45 +00:00
|
|
|
ASSERT(out_len <= sectorsize);
|
|
|
|
memcpy_to_page(dest_page, dest_pgoff, workspace->buf, out_len);
|
|
|
|
/* Early end, considered as an error. */
|
|
|
|
if (unlikely(out_len < destlen)) {
|
2014-05-09 21:15:08 +00:00
|
|
|
ret = -EIO;
|
btrfs: lzo: fix and simplify the inline extent decompression
[BUG]
If we have a filesystem with 4k sectorsize, and an inlined compressed
extent created like this:
item 4 key (257 INODE_ITEM 0) itemoff 15863 itemsize 160
generation 8 transid 8 size 4096 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 5 key (257 INODE_REF 256) itemoff 15839 itemsize 24
index 2 namelen 14 name: source_inlined
item 6 key (257 EXTENT_DATA 0) itemoff 15770 itemsize 69
generation 8 type 0 (inline)
inline extent data size 48 ram_bytes 4096 compression 2 (lzo)
Then trying to reflink that extent in an aarch64 system with 64K page
size, the reflink would just fail:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
XFS_IOC_CLONE_RANGE: Input/output error
[CAUSE]
In zlib_decompress(), we didn't treat @start_byte as just a page offset,
but also use it as an indicator on whether we should error out, without
any proper explanation (this is from the very beginning of btrfs).
In reality, for subpage cases, although @start_byte can be non-zero,
we should never switch input/output buffer nor error out, since the whole
input/output buffer should never exceed one sector.
Note: The above assumption is only not true if we're going to support
multi-page sectorsize.
Thus the current code using @start_byte as a condition to switch
input/output buffer or finish the decompression is completely incorrect.
[FIX]
The fix involves several modifications:
- Rename @start_byte to @dest_pgoff to properly express its meaning
- Use @sectorsize other than PAGE_SIZE to properly initialize the
output buffer size
- Use correct destination offset inside the destination page
- Use memcpy_to_page() to copy the contents to the destination page
- Use memzero_page() to zero out the tailing part
- Consider early end as an error
After the fix, even on 64K page sized aarch64, above reflink now
works as expected:
# xfs_io -f -c "reflink $mnt/source_inlined 0 60k 4k" $mnt/dest
linked 4096/4096 bytes at offset 61440
And results the correct file layout:
item 9 key (258 INODE_ITEM 0) itemoff 15542 itemsize 160
generation 10 transid 10 size 65536 nbytes 4096
block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
sequence 1 flags 0x0(none)
item 10 key (258 INODE_REF 256) itemoff 15528 itemsize 14
index 3 namelen 4 name: dest
item 11 key (258 XATTR_ITEM 3817753667) itemoff 15445 itemsize 83
location key (0 UNKNOWN.0 0) type XATTR
transid 10 data_len 37 name_len 16
name: security.selinux
data unconfined_u:object_r:unlabeled_t:s0
item 12 key (258 EXTENT_DATA 61440) itemoff 15392 itemsize 53
generation 10 type 1 (regular)
extent data disk byte 13631488 nr 4096
extent data offset 0 nr 4096 ram 4096
extent compression 0 (none)
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2024-01-08 09:08:45 +00:00
|
|
|
memzero_page(dest_page, dest_pgoff + out_len, destlen - out_len);
|
2010-10-25 07:12:26 +00:00
|
|
|
}
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-01-02 17:23:10 +00:00
|
|
|
const struct btrfs_compress_op btrfs_lzo_compress = {
|
2019-10-01 22:53:31 +00:00
|
|
|
.workspace_manager = &wsm,
|
2019-08-09 14:25:34 +00:00
|
|
|
.max_level = 1,
|
|
|
|
.default_level = 1,
|
2010-10-25 07:12:26 +00:00
|
|
|
};
|