162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2008 Oracle.  All rights reserved.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Based on jffs2 zlib code:
662306a36Sopenharmony_ci * Copyright © 2001-2007 Red Hat, Inc.
762306a36Sopenharmony_ci * Created by David Woodhouse <dwmw2@infradead.org>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/zlib.h>
1362306a36Sopenharmony_ci#include <linux/zutil.h>
1462306a36Sopenharmony_ci#include <linux/mm.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/err.h>
1762306a36Sopenharmony_ci#include <linux/sched.h>
1862306a36Sopenharmony_ci#include <linux/pagemap.h>
1962306a36Sopenharmony_ci#include <linux/bio.h>
2062306a36Sopenharmony_ci#include <linux/refcount.h>
2162306a36Sopenharmony_ci#include "compression.h"
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* workspace buffer size for s390 zlib hardware support */
2462306a36Sopenharmony_ci#define ZLIB_DFLTCC_BUF_SIZE    (4 * PAGE_SIZE)
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct workspace {
2762306a36Sopenharmony_ci	z_stream strm;
2862306a36Sopenharmony_ci	char *buf;
2962306a36Sopenharmony_ci	unsigned int buf_size;
3062306a36Sopenharmony_ci	struct list_head list;
3162306a36Sopenharmony_ci	int level;
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic struct workspace_manager wsm;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistruct list_head *zlib_get_workspace(unsigned int level)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	struct list_head *ws = btrfs_get_workspace(BTRFS_COMPRESS_ZLIB, level);
3962306a36Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	workspace->level = level;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	return ws;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_civoid zlib_free_workspace(struct list_head *ws)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	kvfree(workspace->strm.workspace);
5162306a36Sopenharmony_ci	kfree(workspace->buf);
5262306a36Sopenharmony_ci	kfree(workspace);
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistruct list_head *zlib_alloc_workspace(unsigned int level)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct workspace *workspace;
5862306a36Sopenharmony_ci	int workspacesize;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	workspace = kzalloc(sizeof(*workspace), GFP_KERNEL);
6162306a36Sopenharmony_ci	if (!workspace)
6262306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	workspacesize = max(zlib_deflate_workspacesize(MAX_WBITS, MAX_MEM_LEVEL),
6562306a36Sopenharmony_ci			zlib_inflate_workspacesize());
6662306a36Sopenharmony_ci	workspace->strm.workspace = kvzalloc(workspacesize, GFP_KERNEL | __GFP_NOWARN);
6762306a36Sopenharmony_ci	workspace->level = level;
6862306a36Sopenharmony_ci	workspace->buf = NULL;
6962306a36Sopenharmony_ci	/*
7062306a36Sopenharmony_ci	 * In case of s390 zlib hardware support, allocate lager workspace
7162306a36Sopenharmony_ci	 * buffer. If allocator fails, fall back to a single page buffer.
7262306a36Sopenharmony_ci	 */
7362306a36Sopenharmony_ci	if (zlib_deflate_dfltcc_enabled()) {
7462306a36Sopenharmony_ci		workspace->buf = kmalloc(ZLIB_DFLTCC_BUF_SIZE,
7562306a36Sopenharmony_ci					 __GFP_NOMEMALLOC | __GFP_NORETRY |
7662306a36Sopenharmony_ci					 __GFP_NOWARN | GFP_NOIO);
7762306a36Sopenharmony_ci		workspace->buf_size = ZLIB_DFLTCC_BUF_SIZE;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci	if (!workspace->buf) {
8062306a36Sopenharmony_ci		workspace->buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
8162306a36Sopenharmony_ci		workspace->buf_size = PAGE_SIZE;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci	if (!workspace->strm.workspace || !workspace->buf)
8462306a36Sopenharmony_ci		goto fail;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	INIT_LIST_HEAD(&workspace->list);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return &workspace->list;
8962306a36Sopenharmony_cifail:
9062306a36Sopenharmony_ci	zlib_free_workspace(&workspace->list);
9162306a36Sopenharmony_ci	return ERR_PTR(-ENOMEM);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ciint zlib_compress_pages(struct list_head *ws, struct address_space *mapping,
9562306a36Sopenharmony_ci		u64 start, struct page **pages, unsigned long *out_pages,
9662306a36Sopenharmony_ci		unsigned long *total_in, unsigned long *total_out)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
9962306a36Sopenharmony_ci	int ret;
10062306a36Sopenharmony_ci	char *data_in = NULL;
10162306a36Sopenharmony_ci	char *cpage_out;
10262306a36Sopenharmony_ci	int nr_pages = 0;
10362306a36Sopenharmony_ci	struct page *in_page = NULL;
10462306a36Sopenharmony_ci	struct page *out_page = NULL;
10562306a36Sopenharmony_ci	unsigned long bytes_left;
10662306a36Sopenharmony_ci	unsigned int in_buf_pages;
10762306a36Sopenharmony_ci	unsigned long len = *total_out;
10862306a36Sopenharmony_ci	unsigned long nr_dest_pages = *out_pages;
10962306a36Sopenharmony_ci	const unsigned long max_out = nr_dest_pages * PAGE_SIZE;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	*out_pages = 0;
11262306a36Sopenharmony_ci	*total_out = 0;
11362306a36Sopenharmony_ci	*total_in = 0;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (Z_OK != zlib_deflateInit(&workspace->strm, workspace->level)) {
11662306a36Sopenharmony_ci		pr_warn("BTRFS: deflateInit failed\n");
11762306a36Sopenharmony_ci		ret = -EIO;
11862306a36Sopenharmony_ci		goto out;
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	workspace->strm.total_in = 0;
12262306a36Sopenharmony_ci	workspace->strm.total_out = 0;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	out_page = alloc_page(GFP_NOFS);
12562306a36Sopenharmony_ci	if (out_page == NULL) {
12662306a36Sopenharmony_ci		ret = -ENOMEM;
12762306a36Sopenharmony_ci		goto out;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci	cpage_out = page_address(out_page);
13062306a36Sopenharmony_ci	pages[0] = out_page;
13162306a36Sopenharmony_ci	nr_pages = 1;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	workspace->strm.next_in = workspace->buf;
13462306a36Sopenharmony_ci	workspace->strm.avail_in = 0;
13562306a36Sopenharmony_ci	workspace->strm.next_out = cpage_out;
13662306a36Sopenharmony_ci	workspace->strm.avail_out = PAGE_SIZE;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	while (workspace->strm.total_in < len) {
13962306a36Sopenharmony_ci		/*
14062306a36Sopenharmony_ci		 * Get next input pages and copy the contents to
14162306a36Sopenharmony_ci		 * the workspace buffer if required.
14262306a36Sopenharmony_ci		 */
14362306a36Sopenharmony_ci		if (workspace->strm.avail_in == 0) {
14462306a36Sopenharmony_ci			bytes_left = len - workspace->strm.total_in;
14562306a36Sopenharmony_ci			in_buf_pages = min(DIV_ROUND_UP(bytes_left, PAGE_SIZE),
14662306a36Sopenharmony_ci					   workspace->buf_size / PAGE_SIZE);
14762306a36Sopenharmony_ci			if (in_buf_pages > 1) {
14862306a36Sopenharmony_ci				int i;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci				for (i = 0; i < in_buf_pages; i++) {
15162306a36Sopenharmony_ci					if (data_in) {
15262306a36Sopenharmony_ci						kunmap_local(data_in);
15362306a36Sopenharmony_ci						put_page(in_page);
15462306a36Sopenharmony_ci					}
15562306a36Sopenharmony_ci					in_page = find_get_page(mapping,
15662306a36Sopenharmony_ci								start >> PAGE_SHIFT);
15762306a36Sopenharmony_ci					data_in = kmap_local_page(in_page);
15862306a36Sopenharmony_ci					copy_page(workspace->buf + i * PAGE_SIZE,
15962306a36Sopenharmony_ci						  data_in);
16062306a36Sopenharmony_ci					start += PAGE_SIZE;
16162306a36Sopenharmony_ci				}
16262306a36Sopenharmony_ci				workspace->strm.next_in = workspace->buf;
16362306a36Sopenharmony_ci			} else {
16462306a36Sopenharmony_ci				if (data_in) {
16562306a36Sopenharmony_ci					kunmap_local(data_in);
16662306a36Sopenharmony_ci					put_page(in_page);
16762306a36Sopenharmony_ci				}
16862306a36Sopenharmony_ci				in_page = find_get_page(mapping,
16962306a36Sopenharmony_ci							start >> PAGE_SHIFT);
17062306a36Sopenharmony_ci				data_in = kmap_local_page(in_page);
17162306a36Sopenharmony_ci				start += PAGE_SIZE;
17262306a36Sopenharmony_ci				workspace->strm.next_in = data_in;
17362306a36Sopenharmony_ci			}
17462306a36Sopenharmony_ci			workspace->strm.avail_in = min(bytes_left,
17562306a36Sopenharmony_ci						       (unsigned long) workspace->buf_size);
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		ret = zlib_deflate(&workspace->strm, Z_SYNC_FLUSH);
17962306a36Sopenharmony_ci		if (ret != Z_OK) {
18062306a36Sopenharmony_ci			pr_debug("BTRFS: deflate in loop returned %d\n",
18162306a36Sopenharmony_ci			       ret);
18262306a36Sopenharmony_ci			zlib_deflateEnd(&workspace->strm);
18362306a36Sopenharmony_ci			ret = -EIO;
18462306a36Sopenharmony_ci			goto out;
18562306a36Sopenharmony_ci		}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci		/* we're making it bigger, give up */
18862306a36Sopenharmony_ci		if (workspace->strm.total_in > 8192 &&
18962306a36Sopenharmony_ci		    workspace->strm.total_in <
19062306a36Sopenharmony_ci		    workspace->strm.total_out) {
19162306a36Sopenharmony_ci			ret = -E2BIG;
19262306a36Sopenharmony_ci			goto out;
19362306a36Sopenharmony_ci		}
19462306a36Sopenharmony_ci		/* we need another page for writing out.  Test this
19562306a36Sopenharmony_ci		 * before the total_in so we will pull in a new page for
19662306a36Sopenharmony_ci		 * the stream end if required
19762306a36Sopenharmony_ci		 */
19862306a36Sopenharmony_ci		if (workspace->strm.avail_out == 0) {
19962306a36Sopenharmony_ci			if (nr_pages == nr_dest_pages) {
20062306a36Sopenharmony_ci				ret = -E2BIG;
20162306a36Sopenharmony_ci				goto out;
20262306a36Sopenharmony_ci			}
20362306a36Sopenharmony_ci			out_page = alloc_page(GFP_NOFS);
20462306a36Sopenharmony_ci			if (out_page == NULL) {
20562306a36Sopenharmony_ci				ret = -ENOMEM;
20662306a36Sopenharmony_ci				goto out;
20762306a36Sopenharmony_ci			}
20862306a36Sopenharmony_ci			cpage_out = page_address(out_page);
20962306a36Sopenharmony_ci			pages[nr_pages] = out_page;
21062306a36Sopenharmony_ci			nr_pages++;
21162306a36Sopenharmony_ci			workspace->strm.avail_out = PAGE_SIZE;
21262306a36Sopenharmony_ci			workspace->strm.next_out = cpage_out;
21362306a36Sopenharmony_ci		}
21462306a36Sopenharmony_ci		/* we're all done */
21562306a36Sopenharmony_ci		if (workspace->strm.total_in >= len)
21662306a36Sopenharmony_ci			break;
21762306a36Sopenharmony_ci		if (workspace->strm.total_out > max_out)
21862306a36Sopenharmony_ci			break;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci	workspace->strm.avail_in = 0;
22162306a36Sopenharmony_ci	/*
22262306a36Sopenharmony_ci	 * Call deflate with Z_FINISH flush parameter providing more output
22362306a36Sopenharmony_ci	 * space but no more input data, until it returns with Z_STREAM_END.
22462306a36Sopenharmony_ci	 */
22562306a36Sopenharmony_ci	while (ret != Z_STREAM_END) {
22662306a36Sopenharmony_ci		ret = zlib_deflate(&workspace->strm, Z_FINISH);
22762306a36Sopenharmony_ci		if (ret == Z_STREAM_END)
22862306a36Sopenharmony_ci			break;
22962306a36Sopenharmony_ci		if (ret != Z_OK && ret != Z_BUF_ERROR) {
23062306a36Sopenharmony_ci			zlib_deflateEnd(&workspace->strm);
23162306a36Sopenharmony_ci			ret = -EIO;
23262306a36Sopenharmony_ci			goto out;
23362306a36Sopenharmony_ci		} else if (workspace->strm.avail_out == 0) {
23462306a36Sopenharmony_ci			/* get another page for the stream end */
23562306a36Sopenharmony_ci			if (nr_pages == nr_dest_pages) {
23662306a36Sopenharmony_ci				ret = -E2BIG;
23762306a36Sopenharmony_ci				goto out;
23862306a36Sopenharmony_ci			}
23962306a36Sopenharmony_ci			out_page = alloc_page(GFP_NOFS);
24062306a36Sopenharmony_ci			if (out_page == NULL) {
24162306a36Sopenharmony_ci				ret = -ENOMEM;
24262306a36Sopenharmony_ci				goto out;
24362306a36Sopenharmony_ci			}
24462306a36Sopenharmony_ci			cpage_out = page_address(out_page);
24562306a36Sopenharmony_ci			pages[nr_pages] = out_page;
24662306a36Sopenharmony_ci			nr_pages++;
24762306a36Sopenharmony_ci			workspace->strm.avail_out = PAGE_SIZE;
24862306a36Sopenharmony_ci			workspace->strm.next_out = cpage_out;
24962306a36Sopenharmony_ci		}
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci	zlib_deflateEnd(&workspace->strm);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	if (workspace->strm.total_out >= workspace->strm.total_in) {
25462306a36Sopenharmony_ci		ret = -E2BIG;
25562306a36Sopenharmony_ci		goto out;
25662306a36Sopenharmony_ci	}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	ret = 0;
25962306a36Sopenharmony_ci	*total_out = workspace->strm.total_out;
26062306a36Sopenharmony_ci	*total_in = workspace->strm.total_in;
26162306a36Sopenharmony_ciout:
26262306a36Sopenharmony_ci	*out_pages = nr_pages;
26362306a36Sopenharmony_ci	if (data_in) {
26462306a36Sopenharmony_ci		kunmap_local(data_in);
26562306a36Sopenharmony_ci		put_page(in_page);
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return ret;
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ciint zlib_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
27262306a36Sopenharmony_ci{
27362306a36Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
27462306a36Sopenharmony_ci	int ret = 0, ret2;
27562306a36Sopenharmony_ci	int wbits = MAX_WBITS;
27662306a36Sopenharmony_ci	char *data_in;
27762306a36Sopenharmony_ci	size_t total_out = 0;
27862306a36Sopenharmony_ci	unsigned long page_in_index = 0;
27962306a36Sopenharmony_ci	size_t srclen = cb->compressed_len;
28062306a36Sopenharmony_ci	unsigned long total_pages_in = DIV_ROUND_UP(srclen, PAGE_SIZE);
28162306a36Sopenharmony_ci	unsigned long buf_start;
28262306a36Sopenharmony_ci	struct page **pages_in = cb->compressed_pages;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	data_in = kmap_local_page(pages_in[page_in_index]);
28562306a36Sopenharmony_ci	workspace->strm.next_in = data_in;
28662306a36Sopenharmony_ci	workspace->strm.avail_in = min_t(size_t, srclen, PAGE_SIZE);
28762306a36Sopenharmony_ci	workspace->strm.total_in = 0;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	workspace->strm.total_out = 0;
29062306a36Sopenharmony_ci	workspace->strm.next_out = workspace->buf;
29162306a36Sopenharmony_ci	workspace->strm.avail_out = workspace->buf_size;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	/* If it's deflate, and it's got no preset dictionary, then
29462306a36Sopenharmony_ci	   we can tell zlib to skip the adler32 check. */
29562306a36Sopenharmony_ci	if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
29662306a36Sopenharmony_ci	    ((data_in[0] & 0x0f) == Z_DEFLATED) &&
29762306a36Sopenharmony_ci	    !(((data_in[0]<<8) + data_in[1]) % 31)) {
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci		wbits = -((data_in[0] >> 4) + 8);
30062306a36Sopenharmony_ci		workspace->strm.next_in += 2;
30162306a36Sopenharmony_ci		workspace->strm.avail_in -= 2;
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	if (Z_OK != zlib_inflateInit2(&workspace->strm, wbits)) {
30562306a36Sopenharmony_ci		pr_warn("BTRFS: inflateInit failed\n");
30662306a36Sopenharmony_ci		kunmap_local(data_in);
30762306a36Sopenharmony_ci		return -EIO;
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci	while (workspace->strm.total_in < srclen) {
31062306a36Sopenharmony_ci		ret = zlib_inflate(&workspace->strm, Z_NO_FLUSH);
31162306a36Sopenharmony_ci		if (ret != Z_OK && ret != Z_STREAM_END)
31262306a36Sopenharmony_ci			break;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci		buf_start = total_out;
31562306a36Sopenharmony_ci		total_out = workspace->strm.total_out;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci		/* we didn't make progress in this inflate call, we're done */
31862306a36Sopenharmony_ci		if (buf_start == total_out)
31962306a36Sopenharmony_ci			break;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci		ret2 = btrfs_decompress_buf2page(workspace->buf,
32262306a36Sopenharmony_ci				total_out - buf_start, cb, buf_start);
32362306a36Sopenharmony_ci		if (ret2 == 0) {
32462306a36Sopenharmony_ci			ret = 0;
32562306a36Sopenharmony_ci			goto done;
32662306a36Sopenharmony_ci		}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci		workspace->strm.next_out = workspace->buf;
32962306a36Sopenharmony_ci		workspace->strm.avail_out = workspace->buf_size;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci		if (workspace->strm.avail_in == 0) {
33262306a36Sopenharmony_ci			unsigned long tmp;
33362306a36Sopenharmony_ci			kunmap_local(data_in);
33462306a36Sopenharmony_ci			page_in_index++;
33562306a36Sopenharmony_ci			if (page_in_index >= total_pages_in) {
33662306a36Sopenharmony_ci				data_in = NULL;
33762306a36Sopenharmony_ci				break;
33862306a36Sopenharmony_ci			}
33962306a36Sopenharmony_ci			data_in = kmap_local_page(pages_in[page_in_index]);
34062306a36Sopenharmony_ci			workspace->strm.next_in = data_in;
34162306a36Sopenharmony_ci			tmp = srclen - workspace->strm.total_in;
34262306a36Sopenharmony_ci			workspace->strm.avail_in = min(tmp, PAGE_SIZE);
34362306a36Sopenharmony_ci		}
34462306a36Sopenharmony_ci	}
34562306a36Sopenharmony_ci	if (ret != Z_STREAM_END)
34662306a36Sopenharmony_ci		ret = -EIO;
34762306a36Sopenharmony_ci	else
34862306a36Sopenharmony_ci		ret = 0;
34962306a36Sopenharmony_cidone:
35062306a36Sopenharmony_ci	zlib_inflateEnd(&workspace->strm);
35162306a36Sopenharmony_ci	if (data_in)
35262306a36Sopenharmony_ci		kunmap_local(data_in);
35362306a36Sopenharmony_ci	return ret;
35462306a36Sopenharmony_ci}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ciint zlib_decompress(struct list_head *ws, const u8 *data_in,
35762306a36Sopenharmony_ci		struct page *dest_page, unsigned long start_byte, size_t srclen,
35862306a36Sopenharmony_ci		size_t destlen)
35962306a36Sopenharmony_ci{
36062306a36Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
36162306a36Sopenharmony_ci	int ret = 0;
36262306a36Sopenharmony_ci	int wbits = MAX_WBITS;
36362306a36Sopenharmony_ci	unsigned long bytes_left;
36462306a36Sopenharmony_ci	unsigned long total_out = 0;
36562306a36Sopenharmony_ci	unsigned long pg_offset = 0;
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	destlen = min_t(unsigned long, destlen, PAGE_SIZE);
36862306a36Sopenharmony_ci	bytes_left = destlen;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	workspace->strm.next_in = data_in;
37162306a36Sopenharmony_ci	workspace->strm.avail_in = srclen;
37262306a36Sopenharmony_ci	workspace->strm.total_in = 0;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	workspace->strm.next_out = workspace->buf;
37562306a36Sopenharmony_ci	workspace->strm.avail_out = workspace->buf_size;
37662306a36Sopenharmony_ci	workspace->strm.total_out = 0;
37762306a36Sopenharmony_ci	/* If it's deflate, and it's got no preset dictionary, then
37862306a36Sopenharmony_ci	   we can tell zlib to skip the adler32 check. */
37962306a36Sopenharmony_ci	if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
38062306a36Sopenharmony_ci	    ((data_in[0] & 0x0f) == Z_DEFLATED) &&
38162306a36Sopenharmony_ci	    !(((data_in[0]<<8) + data_in[1]) % 31)) {
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci		wbits = -((data_in[0] >> 4) + 8);
38462306a36Sopenharmony_ci		workspace->strm.next_in += 2;
38562306a36Sopenharmony_ci		workspace->strm.avail_in -= 2;
38662306a36Sopenharmony_ci	}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	if (Z_OK != zlib_inflateInit2(&workspace->strm, wbits)) {
38962306a36Sopenharmony_ci		pr_warn("BTRFS: inflateInit failed\n");
39062306a36Sopenharmony_ci		return -EIO;
39162306a36Sopenharmony_ci	}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	while (bytes_left > 0) {
39462306a36Sopenharmony_ci		unsigned long buf_start;
39562306a36Sopenharmony_ci		unsigned long buf_offset;
39662306a36Sopenharmony_ci		unsigned long bytes;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci		ret = zlib_inflate(&workspace->strm, Z_NO_FLUSH);
39962306a36Sopenharmony_ci		if (ret != Z_OK && ret != Z_STREAM_END)
40062306a36Sopenharmony_ci			break;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci		buf_start = total_out;
40362306a36Sopenharmony_ci		total_out = workspace->strm.total_out;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci		if (total_out == buf_start) {
40662306a36Sopenharmony_ci			ret = -EIO;
40762306a36Sopenharmony_ci			break;
40862306a36Sopenharmony_ci		}
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci		if (total_out <= start_byte)
41162306a36Sopenharmony_ci			goto next;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci		if (total_out > start_byte && buf_start < start_byte)
41462306a36Sopenharmony_ci			buf_offset = start_byte - buf_start;
41562306a36Sopenharmony_ci		else
41662306a36Sopenharmony_ci			buf_offset = 0;
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci		bytes = min(PAGE_SIZE - pg_offset,
41962306a36Sopenharmony_ci			    PAGE_SIZE - (buf_offset % PAGE_SIZE));
42062306a36Sopenharmony_ci		bytes = min(bytes, bytes_left);
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci		memcpy_to_page(dest_page, pg_offset,
42362306a36Sopenharmony_ci			       workspace->buf + buf_offset, bytes);
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci		pg_offset += bytes;
42662306a36Sopenharmony_ci		bytes_left -= bytes;
42762306a36Sopenharmony_cinext:
42862306a36Sopenharmony_ci		workspace->strm.next_out = workspace->buf;
42962306a36Sopenharmony_ci		workspace->strm.avail_out = workspace->buf_size;
43062306a36Sopenharmony_ci	}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	if (ret != Z_STREAM_END && bytes_left != 0)
43362306a36Sopenharmony_ci		ret = -EIO;
43462306a36Sopenharmony_ci	else
43562306a36Sopenharmony_ci		ret = 0;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	zlib_inflateEnd(&workspace->strm);
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	/*
44062306a36Sopenharmony_ci	 * this should only happen if zlib returned fewer bytes than we
44162306a36Sopenharmony_ci	 * expected.  btrfs_get_block is responsible for zeroing from the
44262306a36Sopenharmony_ci	 * end of the inline extent (destlen) to the end of the page
44362306a36Sopenharmony_ci	 */
44462306a36Sopenharmony_ci	if (pg_offset < destlen) {
44562306a36Sopenharmony_ci		memzero_page(dest_page, pg_offset, destlen - pg_offset);
44662306a36Sopenharmony_ci	}
44762306a36Sopenharmony_ci	return ret;
44862306a36Sopenharmony_ci}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ciconst struct btrfs_compress_op btrfs_zlib_compress = {
45162306a36Sopenharmony_ci	.workspace_manager	= &wsm,
45262306a36Sopenharmony_ci	.max_level		= 9,
45362306a36Sopenharmony_ci	.default_level		= BTRFS_ZLIB_DEFAULT_LEVEL,
45462306a36Sopenharmony_ci};
455