18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2008 Oracle. All rights reserved. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/kernel.h> 78c2ecf20Sopenharmony_ci#include <linux/slab.h> 88c2ecf20Sopenharmony_ci#include <linux/mm.h> 98c2ecf20Sopenharmony_ci#include <linux/init.h> 108c2ecf20Sopenharmony_ci#include <linux/err.h> 118c2ecf20Sopenharmony_ci#include <linux/sched.h> 128c2ecf20Sopenharmony_ci#include <linux/pagemap.h> 138c2ecf20Sopenharmony_ci#include <linux/bio.h> 148c2ecf20Sopenharmony_ci#include <linux/lzo.h> 158c2ecf20Sopenharmony_ci#include <linux/refcount.h> 168c2ecf20Sopenharmony_ci#include "compression.h" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define LZO_LEN 4 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci/* 218c2ecf20Sopenharmony_ci * Btrfs LZO compression format 228c2ecf20Sopenharmony_ci * 238c2ecf20Sopenharmony_ci * Regular and inlined LZO compressed data extents consist of: 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * 1. Header 268c2ecf20Sopenharmony_ci * Fixed size. LZO_LEN (4) bytes long, LE32. 278c2ecf20Sopenharmony_ci * Records the total size (including the header) of compressed data. 288c2ecf20Sopenharmony_ci * 298c2ecf20Sopenharmony_ci * 2. Segment(s) 308c2ecf20Sopenharmony_ci * Variable size. Each segment includes one segment header, followed by data 318c2ecf20Sopenharmony_ci * payload. 328c2ecf20Sopenharmony_ci * One regular LZO compressed extent can have one or more segments. 338c2ecf20Sopenharmony_ci * For inlined LZO compressed extent, only one segment is allowed. 348c2ecf20Sopenharmony_ci * One segment represents at most one page of uncompressed data. 358c2ecf20Sopenharmony_ci * 368c2ecf20Sopenharmony_ci * 2.1 Segment header 378c2ecf20Sopenharmony_ci * Fixed size. LZO_LEN (4) bytes long, LE32. 388c2ecf20Sopenharmony_ci * Records the total size of the segment (not including the header). 398c2ecf20Sopenharmony_ci * Segment header never crosses page boundary, thus it's possible to 408c2ecf20Sopenharmony_ci * have at most 3 padding zeros at the end of the page. 418c2ecf20Sopenharmony_ci * 428c2ecf20Sopenharmony_ci * 2.2 Data Payload 438c2ecf20Sopenharmony_ci * Variable size. Size up limit should be lzo1x_worst_compress(PAGE_SIZE) 448c2ecf20Sopenharmony_ci * which is 4419 for a 4KiB page. 458c2ecf20Sopenharmony_ci * 468c2ecf20Sopenharmony_ci * Example: 478c2ecf20Sopenharmony_ci * Page 1: 488c2ecf20Sopenharmony_ci * 0 0x2 0x4 0x6 0x8 0xa 0xc 0xe 0x10 498c2ecf20Sopenharmony_ci * 0x0000 | Header | SegHdr 01 | Data payload 01 ... | 508c2ecf20Sopenharmony_ci * ... 518c2ecf20Sopenharmony_ci * 0x0ff0 | SegHdr N | Data payload N ... |00| 528c2ecf20Sopenharmony_ci * ^^ padding zeros 538c2ecf20Sopenharmony_ci * Page 2: 548c2ecf20Sopenharmony_ci * 0x1000 | SegHdr N+1| Data payload N+1 ... | 558c2ecf20Sopenharmony_ci */ 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistruct workspace { 588c2ecf20Sopenharmony_ci void *mem; 598c2ecf20Sopenharmony_ci void *buf; /* where decompressed data goes */ 608c2ecf20Sopenharmony_ci void *cbuf; /* where compressed data goes */ 618c2ecf20Sopenharmony_ci struct list_head list; 628c2ecf20Sopenharmony_ci}; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic struct workspace_manager wsm; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_civoid lzo_free_workspace(struct list_head *ws) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci struct workspace *workspace = list_entry(ws, struct workspace, list); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci kvfree(workspace->buf); 718c2ecf20Sopenharmony_ci kvfree(workspace->cbuf); 728c2ecf20Sopenharmony_ci kvfree(workspace->mem); 738c2ecf20Sopenharmony_ci kfree(workspace); 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistruct list_head *lzo_alloc_workspace(unsigned int level) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct workspace *workspace; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci workspace = kzalloc(sizeof(*workspace), GFP_KERNEL); 818c2ecf20Sopenharmony_ci if (!workspace) 828c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci workspace->mem = kvmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL); 858c2ecf20Sopenharmony_ci workspace->buf = kvmalloc(lzo1x_worst_compress(PAGE_SIZE), GFP_KERNEL); 868c2ecf20Sopenharmony_ci workspace->cbuf = kvmalloc(lzo1x_worst_compress(PAGE_SIZE), GFP_KERNEL); 878c2ecf20Sopenharmony_ci if (!workspace->mem || !workspace->buf || !workspace->cbuf) 888c2ecf20Sopenharmony_ci goto fail; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&workspace->list); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci return &workspace->list; 938c2ecf20Sopenharmony_cifail: 948c2ecf20Sopenharmony_ci lzo_free_workspace(&workspace->list); 958c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic inline void write_compress_length(char *buf, size_t len) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci __le32 dlen; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci dlen = cpu_to_le32(len); 1038c2ecf20Sopenharmony_ci memcpy(buf, &dlen, LZO_LEN); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic inline size_t read_compress_length(const char *buf) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci __le32 dlen; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci memcpy(&dlen, buf, LZO_LEN); 1118c2ecf20Sopenharmony_ci return le32_to_cpu(dlen); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ciint lzo_compress_pages(struct list_head *ws, struct address_space *mapping, 1158c2ecf20Sopenharmony_ci u64 start, struct page **pages, unsigned long *out_pages, 1168c2ecf20Sopenharmony_ci unsigned long *total_in, unsigned long *total_out) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci struct workspace *workspace = list_entry(ws, struct workspace, list); 1198c2ecf20Sopenharmony_ci int ret = 0; 1208c2ecf20Sopenharmony_ci char *data_in; 1218c2ecf20Sopenharmony_ci char *cpage_out; 1228c2ecf20Sopenharmony_ci int nr_pages = 0; 1238c2ecf20Sopenharmony_ci struct page *in_page = NULL; 1248c2ecf20Sopenharmony_ci struct page *out_page = NULL; 1258c2ecf20Sopenharmony_ci unsigned long bytes_left; 1268c2ecf20Sopenharmony_ci unsigned long len = *total_out; 1278c2ecf20Sopenharmony_ci unsigned long nr_dest_pages = *out_pages; 1288c2ecf20Sopenharmony_ci const unsigned long max_out = nr_dest_pages * PAGE_SIZE; 1298c2ecf20Sopenharmony_ci size_t in_len; 1308c2ecf20Sopenharmony_ci size_t out_len; 1318c2ecf20Sopenharmony_ci char *buf; 1328c2ecf20Sopenharmony_ci unsigned long tot_in = 0; 1338c2ecf20Sopenharmony_ci unsigned long tot_out = 0; 1348c2ecf20Sopenharmony_ci unsigned long pg_bytes_left; 1358c2ecf20Sopenharmony_ci unsigned long out_offset; 1368c2ecf20Sopenharmony_ci unsigned long bytes; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci *out_pages = 0; 1398c2ecf20Sopenharmony_ci *total_out = 0; 1408c2ecf20Sopenharmony_ci *total_in = 0; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci in_page = find_get_page(mapping, start >> PAGE_SHIFT); 1438c2ecf20Sopenharmony_ci data_in = kmap(in_page); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci /* 1468c2ecf20Sopenharmony_ci * store the size of all chunks of compressed data in 1478c2ecf20Sopenharmony_ci * the first 4 bytes 1488c2ecf20Sopenharmony_ci */ 1498c2ecf20Sopenharmony_ci out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM); 1508c2ecf20Sopenharmony_ci if (out_page == NULL) { 1518c2ecf20Sopenharmony_ci ret = -ENOMEM; 1528c2ecf20Sopenharmony_ci goto out; 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci cpage_out = kmap(out_page); 1558c2ecf20Sopenharmony_ci out_offset = LZO_LEN; 1568c2ecf20Sopenharmony_ci tot_out = LZO_LEN; 1578c2ecf20Sopenharmony_ci pages[0] = out_page; 1588c2ecf20Sopenharmony_ci nr_pages = 1; 1598c2ecf20Sopenharmony_ci pg_bytes_left = PAGE_SIZE - LZO_LEN; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci /* compress at most one page of data each time */ 1628c2ecf20Sopenharmony_ci in_len = min(len, PAGE_SIZE); 1638c2ecf20Sopenharmony_ci while (tot_in < len) { 1648c2ecf20Sopenharmony_ci ret = lzo1x_1_compress(data_in, in_len, workspace->cbuf, 1658c2ecf20Sopenharmony_ci &out_len, workspace->mem); 1668c2ecf20Sopenharmony_ci if (ret != LZO_E_OK) { 1678c2ecf20Sopenharmony_ci pr_debug("BTRFS: lzo in loop returned %d\n", 1688c2ecf20Sopenharmony_ci ret); 1698c2ecf20Sopenharmony_ci ret = -EIO; 1708c2ecf20Sopenharmony_ci goto out; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci /* store the size of this chunk of compressed data */ 1748c2ecf20Sopenharmony_ci write_compress_length(cpage_out + out_offset, out_len); 1758c2ecf20Sopenharmony_ci tot_out += LZO_LEN; 1768c2ecf20Sopenharmony_ci out_offset += LZO_LEN; 1778c2ecf20Sopenharmony_ci pg_bytes_left -= LZO_LEN; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci tot_in += in_len; 1808c2ecf20Sopenharmony_ci tot_out += out_len; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci /* copy bytes from the working buffer into the pages */ 1838c2ecf20Sopenharmony_ci buf = workspace->cbuf; 1848c2ecf20Sopenharmony_ci while (out_len) { 1858c2ecf20Sopenharmony_ci bytes = min_t(unsigned long, pg_bytes_left, out_len); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci memcpy(cpage_out + out_offset, buf, bytes); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci out_len -= bytes; 1908c2ecf20Sopenharmony_ci pg_bytes_left -= bytes; 1918c2ecf20Sopenharmony_ci buf += bytes; 1928c2ecf20Sopenharmony_ci out_offset += bytes; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* 1958c2ecf20Sopenharmony_ci * we need another page for writing out. 1968c2ecf20Sopenharmony_ci * 1978c2ecf20Sopenharmony_ci * Note if there's less than 4 bytes left, we just 1988c2ecf20Sopenharmony_ci * skip to a new page. 1998c2ecf20Sopenharmony_ci */ 2008c2ecf20Sopenharmony_ci if ((out_len == 0 && pg_bytes_left < LZO_LEN) || 2018c2ecf20Sopenharmony_ci pg_bytes_left == 0) { 2028c2ecf20Sopenharmony_ci if (pg_bytes_left) { 2038c2ecf20Sopenharmony_ci memset(cpage_out + out_offset, 0, 2048c2ecf20Sopenharmony_ci pg_bytes_left); 2058c2ecf20Sopenharmony_ci tot_out += pg_bytes_left; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* we're done, don't allocate new page */ 2098c2ecf20Sopenharmony_ci if (out_len == 0 && tot_in >= len) 2108c2ecf20Sopenharmony_ci break; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci kunmap(out_page); 2138c2ecf20Sopenharmony_ci if (nr_pages == nr_dest_pages) { 2148c2ecf20Sopenharmony_ci out_page = NULL; 2158c2ecf20Sopenharmony_ci ret = -E2BIG; 2168c2ecf20Sopenharmony_ci goto out; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM); 2208c2ecf20Sopenharmony_ci if (out_page == NULL) { 2218c2ecf20Sopenharmony_ci ret = -ENOMEM; 2228c2ecf20Sopenharmony_ci goto out; 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci cpage_out = kmap(out_page); 2258c2ecf20Sopenharmony_ci pages[nr_pages++] = out_page; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci pg_bytes_left = PAGE_SIZE; 2288c2ecf20Sopenharmony_ci out_offset = 0; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci /* we're making it bigger, give up */ 2338c2ecf20Sopenharmony_ci if (tot_in > 8192 && tot_in < tot_out) { 2348c2ecf20Sopenharmony_ci ret = -E2BIG; 2358c2ecf20Sopenharmony_ci goto out; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci /* we're all done */ 2398c2ecf20Sopenharmony_ci if (tot_in >= len) 2408c2ecf20Sopenharmony_ci break; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci if (tot_out > max_out) 2438c2ecf20Sopenharmony_ci break; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci bytes_left = len - tot_in; 2468c2ecf20Sopenharmony_ci kunmap(in_page); 2478c2ecf20Sopenharmony_ci put_page(in_page); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci start += PAGE_SIZE; 2508c2ecf20Sopenharmony_ci in_page = find_get_page(mapping, start >> PAGE_SHIFT); 2518c2ecf20Sopenharmony_ci data_in = kmap(in_page); 2528c2ecf20Sopenharmony_ci in_len = min(bytes_left, PAGE_SIZE); 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci if (tot_out >= tot_in) { 2568c2ecf20Sopenharmony_ci ret = -E2BIG; 2578c2ecf20Sopenharmony_ci goto out; 2588c2ecf20Sopenharmony_ci } 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* store the size of all chunks of compressed data */ 2618c2ecf20Sopenharmony_ci cpage_out = kmap(pages[0]); 2628c2ecf20Sopenharmony_ci write_compress_length(cpage_out, tot_out); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci kunmap(pages[0]); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci ret = 0; 2678c2ecf20Sopenharmony_ci *total_out = tot_out; 2688c2ecf20Sopenharmony_ci *total_in = tot_in; 2698c2ecf20Sopenharmony_ciout: 2708c2ecf20Sopenharmony_ci *out_pages = nr_pages; 2718c2ecf20Sopenharmony_ci if (out_page) 2728c2ecf20Sopenharmony_ci kunmap(out_page); 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci if (in_page) { 2758c2ecf20Sopenharmony_ci kunmap(in_page); 2768c2ecf20Sopenharmony_ci put_page(in_page); 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci return ret; 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ciint lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb) 2838c2ecf20Sopenharmony_ci{ 2848c2ecf20Sopenharmony_ci struct workspace *workspace = list_entry(ws, struct workspace, list); 2858c2ecf20Sopenharmony_ci int ret = 0, ret2; 2868c2ecf20Sopenharmony_ci char *data_in; 2878c2ecf20Sopenharmony_ci unsigned long page_in_index = 0; 2888c2ecf20Sopenharmony_ci size_t srclen = cb->compressed_len; 2898c2ecf20Sopenharmony_ci unsigned long total_pages_in = DIV_ROUND_UP(srclen, PAGE_SIZE); 2908c2ecf20Sopenharmony_ci unsigned long buf_start; 2918c2ecf20Sopenharmony_ci unsigned long buf_offset = 0; 2928c2ecf20Sopenharmony_ci unsigned long bytes; 2938c2ecf20Sopenharmony_ci unsigned long working_bytes; 2948c2ecf20Sopenharmony_ci size_t in_len; 2958c2ecf20Sopenharmony_ci size_t out_len; 2968c2ecf20Sopenharmony_ci const size_t max_segment_len = lzo1x_worst_compress(PAGE_SIZE); 2978c2ecf20Sopenharmony_ci unsigned long in_offset; 2988c2ecf20Sopenharmony_ci unsigned long in_page_bytes_left; 2998c2ecf20Sopenharmony_ci unsigned long tot_in; 3008c2ecf20Sopenharmony_ci unsigned long tot_out; 3018c2ecf20Sopenharmony_ci unsigned long tot_len; 3028c2ecf20Sopenharmony_ci char *buf; 3038c2ecf20Sopenharmony_ci bool may_late_unmap, need_unmap; 3048c2ecf20Sopenharmony_ci struct page **pages_in = cb->compressed_pages; 3058c2ecf20Sopenharmony_ci u64 disk_start = cb->start; 3068c2ecf20Sopenharmony_ci struct bio *orig_bio = cb->orig_bio; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci data_in = kmap(pages_in[0]); 3098c2ecf20Sopenharmony_ci tot_len = read_compress_length(data_in); 3108c2ecf20Sopenharmony_ci /* 3118c2ecf20Sopenharmony_ci * Compressed data header check. 3128c2ecf20Sopenharmony_ci * 3138c2ecf20Sopenharmony_ci * The real compressed size can't exceed the maximum extent length, and 3148c2ecf20Sopenharmony_ci * all pages should be used (whole unused page with just the segment 3158c2ecf20Sopenharmony_ci * header is not possible). If this happens it means the compressed 3168c2ecf20Sopenharmony_ci * extent is corrupted. 3178c2ecf20Sopenharmony_ci */ 3188c2ecf20Sopenharmony_ci if (tot_len > min_t(size_t, BTRFS_MAX_COMPRESSED, srclen) || 3198c2ecf20Sopenharmony_ci tot_len < srclen - PAGE_SIZE) { 3208c2ecf20Sopenharmony_ci ret = -EUCLEAN; 3218c2ecf20Sopenharmony_ci goto done; 3228c2ecf20Sopenharmony_ci } 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci tot_in = LZO_LEN; 3258c2ecf20Sopenharmony_ci in_offset = LZO_LEN; 3268c2ecf20Sopenharmony_ci in_page_bytes_left = PAGE_SIZE - LZO_LEN; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci tot_out = 0; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci while (tot_in < tot_len) { 3318c2ecf20Sopenharmony_ci in_len = read_compress_length(data_in + in_offset); 3328c2ecf20Sopenharmony_ci in_page_bytes_left -= LZO_LEN; 3338c2ecf20Sopenharmony_ci in_offset += LZO_LEN; 3348c2ecf20Sopenharmony_ci tot_in += LZO_LEN; 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci /* 3378c2ecf20Sopenharmony_ci * Segment header check. 3388c2ecf20Sopenharmony_ci * 3398c2ecf20Sopenharmony_ci * The segment length must not exceed the maximum LZO 3408c2ecf20Sopenharmony_ci * compression size, nor the total compressed size. 3418c2ecf20Sopenharmony_ci */ 3428c2ecf20Sopenharmony_ci if (in_len > max_segment_len || tot_in + in_len > tot_len) { 3438c2ecf20Sopenharmony_ci ret = -EUCLEAN; 3448c2ecf20Sopenharmony_ci goto done; 3458c2ecf20Sopenharmony_ci } 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci tot_in += in_len; 3488c2ecf20Sopenharmony_ci working_bytes = in_len; 3498c2ecf20Sopenharmony_ci may_late_unmap = need_unmap = false; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci /* fast path: avoid using the working buffer */ 3528c2ecf20Sopenharmony_ci if (in_page_bytes_left >= in_len) { 3538c2ecf20Sopenharmony_ci buf = data_in + in_offset; 3548c2ecf20Sopenharmony_ci bytes = in_len; 3558c2ecf20Sopenharmony_ci may_late_unmap = true; 3568c2ecf20Sopenharmony_ci goto cont; 3578c2ecf20Sopenharmony_ci } 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci /* copy bytes from the pages into the working buffer */ 3608c2ecf20Sopenharmony_ci buf = workspace->cbuf; 3618c2ecf20Sopenharmony_ci buf_offset = 0; 3628c2ecf20Sopenharmony_ci while (working_bytes) { 3638c2ecf20Sopenharmony_ci bytes = min(working_bytes, in_page_bytes_left); 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci memcpy(buf + buf_offset, data_in + in_offset, bytes); 3668c2ecf20Sopenharmony_ci buf_offset += bytes; 3678c2ecf20Sopenharmony_cicont: 3688c2ecf20Sopenharmony_ci working_bytes -= bytes; 3698c2ecf20Sopenharmony_ci in_page_bytes_left -= bytes; 3708c2ecf20Sopenharmony_ci in_offset += bytes; 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci /* check if we need to pick another page */ 3738c2ecf20Sopenharmony_ci if ((working_bytes == 0 && in_page_bytes_left < LZO_LEN) 3748c2ecf20Sopenharmony_ci || in_page_bytes_left == 0) { 3758c2ecf20Sopenharmony_ci tot_in += in_page_bytes_left; 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci if (working_bytes == 0 && tot_in >= tot_len) 3788c2ecf20Sopenharmony_ci break; 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci if (page_in_index + 1 >= total_pages_in) { 3818c2ecf20Sopenharmony_ci ret = -EIO; 3828c2ecf20Sopenharmony_ci goto done; 3838c2ecf20Sopenharmony_ci } 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci if (may_late_unmap) 3868c2ecf20Sopenharmony_ci need_unmap = true; 3878c2ecf20Sopenharmony_ci else 3888c2ecf20Sopenharmony_ci kunmap(pages_in[page_in_index]); 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci data_in = kmap(pages_in[++page_in_index]); 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci in_page_bytes_left = PAGE_SIZE; 3938c2ecf20Sopenharmony_ci in_offset = 0; 3948c2ecf20Sopenharmony_ci } 3958c2ecf20Sopenharmony_ci } 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci out_len = max_segment_len; 3988c2ecf20Sopenharmony_ci ret = lzo1x_decompress_safe(buf, in_len, workspace->buf, 3998c2ecf20Sopenharmony_ci &out_len); 4008c2ecf20Sopenharmony_ci if (need_unmap) 4018c2ecf20Sopenharmony_ci kunmap(pages_in[page_in_index - 1]); 4028c2ecf20Sopenharmony_ci if (ret != LZO_E_OK) { 4038c2ecf20Sopenharmony_ci pr_warn("BTRFS: decompress failed\n"); 4048c2ecf20Sopenharmony_ci ret = -EIO; 4058c2ecf20Sopenharmony_ci break; 4068c2ecf20Sopenharmony_ci } 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci buf_start = tot_out; 4098c2ecf20Sopenharmony_ci tot_out += out_len; 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci ret2 = btrfs_decompress_buf2page(workspace->buf, buf_start, 4128c2ecf20Sopenharmony_ci tot_out, disk_start, orig_bio); 4138c2ecf20Sopenharmony_ci if (ret2 == 0) 4148c2ecf20Sopenharmony_ci break; 4158c2ecf20Sopenharmony_ci } 4168c2ecf20Sopenharmony_cidone: 4178c2ecf20Sopenharmony_ci kunmap(pages_in[page_in_index]); 4188c2ecf20Sopenharmony_ci if (!ret) 4198c2ecf20Sopenharmony_ci zero_fill_bio(orig_bio); 4208c2ecf20Sopenharmony_ci return ret; 4218c2ecf20Sopenharmony_ci} 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ciint lzo_decompress(struct list_head *ws, unsigned char *data_in, 4248c2ecf20Sopenharmony_ci struct page *dest_page, unsigned long start_byte, size_t srclen, 4258c2ecf20Sopenharmony_ci size_t destlen) 4268c2ecf20Sopenharmony_ci{ 4278c2ecf20Sopenharmony_ci struct workspace *workspace = list_entry(ws, struct workspace, list); 4288c2ecf20Sopenharmony_ci size_t in_len; 4298c2ecf20Sopenharmony_ci size_t out_len; 4308c2ecf20Sopenharmony_ci size_t max_segment_len = lzo1x_worst_compress(PAGE_SIZE); 4318c2ecf20Sopenharmony_ci int ret = 0; 4328c2ecf20Sopenharmony_ci char *kaddr; 4338c2ecf20Sopenharmony_ci unsigned long bytes; 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci if (srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2) 4368c2ecf20Sopenharmony_ci return -EUCLEAN; 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci in_len = read_compress_length(data_in); 4398c2ecf20Sopenharmony_ci if (in_len != srclen) 4408c2ecf20Sopenharmony_ci return -EUCLEAN; 4418c2ecf20Sopenharmony_ci data_in += LZO_LEN; 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci in_len = read_compress_length(data_in); 4448c2ecf20Sopenharmony_ci if (in_len != srclen - LZO_LEN * 2) { 4458c2ecf20Sopenharmony_ci ret = -EUCLEAN; 4468c2ecf20Sopenharmony_ci goto out; 4478c2ecf20Sopenharmony_ci } 4488c2ecf20Sopenharmony_ci data_in += LZO_LEN; 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci out_len = PAGE_SIZE; 4518c2ecf20Sopenharmony_ci ret = lzo1x_decompress_safe(data_in, in_len, workspace->buf, &out_len); 4528c2ecf20Sopenharmony_ci if (ret != LZO_E_OK) { 4538c2ecf20Sopenharmony_ci pr_warn("BTRFS: decompress failed!\n"); 4548c2ecf20Sopenharmony_ci ret = -EIO; 4558c2ecf20Sopenharmony_ci goto out; 4568c2ecf20Sopenharmony_ci } 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci if (out_len < start_byte) { 4598c2ecf20Sopenharmony_ci ret = -EIO; 4608c2ecf20Sopenharmony_ci goto out; 4618c2ecf20Sopenharmony_ci } 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci /* 4648c2ecf20Sopenharmony_ci * the caller is already checking against PAGE_SIZE, but lets 4658c2ecf20Sopenharmony_ci * move this check closer to the memcpy/memset 4668c2ecf20Sopenharmony_ci */ 4678c2ecf20Sopenharmony_ci destlen = min_t(unsigned long, destlen, PAGE_SIZE); 4688c2ecf20Sopenharmony_ci bytes = min_t(unsigned long, destlen, out_len - start_byte); 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci kaddr = kmap_atomic(dest_page); 4718c2ecf20Sopenharmony_ci memcpy(kaddr, workspace->buf + start_byte, bytes); 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci /* 4748c2ecf20Sopenharmony_ci * btrfs_getblock is doing a zero on the tail of the page too, 4758c2ecf20Sopenharmony_ci * but this will cover anything missing from the decompressed 4768c2ecf20Sopenharmony_ci * data. 4778c2ecf20Sopenharmony_ci */ 4788c2ecf20Sopenharmony_ci if (bytes < destlen) 4798c2ecf20Sopenharmony_ci memset(kaddr+bytes, 0, destlen-bytes); 4808c2ecf20Sopenharmony_ci kunmap_atomic(kaddr); 4818c2ecf20Sopenharmony_ciout: 4828c2ecf20Sopenharmony_ci return ret; 4838c2ecf20Sopenharmony_ci} 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ciconst struct btrfs_compress_op btrfs_lzo_compress = { 4868c2ecf20Sopenharmony_ci .workspace_manager = &wsm, 4878c2ecf20Sopenharmony_ci .max_level = 1, 4888c2ecf20Sopenharmony_ci .default_level = 1, 4898c2ecf20Sopenharmony_ci}; 490