Commit 3d74a755 authored by Qu Wenruo's avatar Qu Wenruo Committed by David Sterba
Browse files

btrfs: zlib: introduce zlib_compress_bio() helper



The new helper has the following enhancements against the existing
zlib_compress_folios()

- Much smaller parameter list

  No more shared IN/OUT members, no need to pre-allocate a
  compressed_folios[] array.

  Just a workspace and compressed_bio pointer, everything we need can be
  extracted from that @cb pointer.

- Ready-to-be-submitted compressed bio

  Although the caller still needs to do some common works like
  rounding up and zeroing the tailing part of the last fs block.

Reviewed-by: default avatarBoris Burkov <boris@bur.io>
Signed-off-by: default avatarQu Wenruo <wqu@suse.com>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent bba95965
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -150,6 +150,7 @@ int btrfs_compress_filemap_get_folio(struct address_space *mapping, u64 start,
int zlib_compress_folios(struct list_head *ws, struct btrfs_inode *inode,
			 u64 start, struct folio **folios, unsigned long *out_folios,
		unsigned long *total_in, unsigned long *total_out);
int zlib_compress_bio(struct list_head *ws, struct compressed_bio *cb);
int zlib_decompress_bio(struct list_head *ws, struct compressed_bio *cb);
int zlib_decompress(struct list_head *ws, const u8 *data_in,
		struct folio *dest_folio, unsigned long dest_pgoff, size_t srclen,
+194 −0
Original line number Diff line number Diff line
@@ -334,6 +334,200 @@ int zlib_compress_folios(struct list_head *ws, struct btrfs_inode *inode,
	return ret;
}

int zlib_compress_bio(struct list_head *ws, struct compressed_bio *cb)
{
	struct btrfs_inode *inode = cb->bbio.inode;
	struct btrfs_fs_info *fs_info = inode->root->fs_info;
	struct workspace *workspace = list_entry(ws, struct workspace, list);
	struct address_space *mapping = inode->vfs_inode.i_mapping;
	struct bio *bio = &cb->bbio.bio;
	u64 start = cb->start;
	u32 len = cb->len;
	const u32 min_folio_size = btrfs_min_folio_size(fs_info);
	int ret;
	char *data_in = NULL;
	char *cfolio_out;
	struct folio *in_folio = NULL;
	struct folio *out_folio = NULL;
	const u32 blocksize = fs_info->sectorsize;
	const u64 orig_end = start + len;

	ret = zlib_deflateInit(&workspace->strm, workspace->level);
	if (unlikely(ret != Z_OK)) {
		btrfs_err(fs_info,
	"zlib compression init failed, error %d root %llu inode %llu offset %llu",
			  ret, btrfs_root_id(inode->root), btrfs_ino(inode), start);
		ret = -EIO;
		goto out;
	}

	workspace->strm.total_in = 0;
	workspace->strm.total_out = 0;

	out_folio = btrfs_alloc_compr_folio(fs_info);
	if (out_folio == NULL) {
		ret = -ENOMEM;
		goto out;
	}
	cfolio_out = folio_address(out_folio);

	workspace->strm.next_in = workspace->buf;
	workspace->strm.avail_in = 0;
	workspace->strm.next_out = cfolio_out;
	workspace->strm.avail_out = min_folio_size;

	while (workspace->strm.total_in < len) {
		/*
		 * Get next input pages and copy the contents to the workspace
		 * buffer if required.
		 */
		if (workspace->strm.avail_in == 0) {
			unsigned long bytes_left = len - workspace->strm.total_in;
			unsigned int copy_length = min(bytes_left, workspace->buf_size);

			/*
			 * For s390 hardware accelerated zlib, and our folio is smaller
			 * than the copy_length, we need to fill the buffer so that
			 * we can take full advantage of hardware acceleration.
			 */
			if (need_special_buffer(fs_info)) {
				ret = copy_data_into_buffer(mapping, workspace,
							    start, copy_length);
				if (ret < 0)
					goto out;
				start += copy_length;
				workspace->strm.next_in = workspace->buf;
				workspace->strm.avail_in = copy_length;
			} else {
				unsigned int cur_len;

				if (data_in) {
					kunmap_local(data_in);
					folio_put(in_folio);
					data_in = NULL;
				}
				ret = btrfs_compress_filemap_get_folio(mapping,
						start, &in_folio);
				if (ret < 0)
					goto out;
				cur_len = btrfs_calc_input_length(in_folio, orig_end, start);
				data_in = kmap_local_folio(in_folio,
							   offset_in_folio(in_folio, start));
				start += cur_len;
				workspace->strm.next_in = data_in;
				workspace->strm.avail_in = cur_len;
			}
		}

		ret = zlib_deflate(&workspace->strm, Z_SYNC_FLUSH);
		if (unlikely(ret != Z_OK)) {
			btrfs_warn(fs_info,
		"zlib compression failed, error %d root %llu inode %llu offset %llu",
				   ret, btrfs_root_id(inode->root), btrfs_ino(inode),
				   start);
			zlib_deflateEnd(&workspace->strm);
			ret = -EIO;
			goto out;
		}

		/* We're making it bigger, give up. */
		if (workspace->strm.total_in > blocksize * 2 &&
		    workspace->strm.total_in < workspace->strm.total_out) {
			ret = -E2BIG;
			goto out;
		}
		if (workspace->strm.total_out >= len) {
			ret = -E2BIG;
			goto out;
		}
		/* Queue the full folio and allocate a new one. */
		if (workspace->strm.avail_out == 0) {
			if (!bio_add_folio(bio, out_folio, folio_size(out_folio), 0)) {
				ret = -E2BIG;
				goto out;
			}

			out_folio = btrfs_alloc_compr_folio(fs_info);
			if (out_folio == NULL) {
				ret = -ENOMEM;
				goto out;
			}
			cfolio_out = folio_address(out_folio);
			workspace->strm.avail_out = min_folio_size;
			workspace->strm.next_out = cfolio_out;
		}
		/* We're all done. */
		if (workspace->strm.total_in >= len)
			break;
	}

	workspace->strm.avail_in = 0;

	/*
	 * Call deflate with Z_FINISH flush parameter providing more output
	 * space but no more input data, until it returns with Z_STREAM_END.
	 */
	while (ret != Z_STREAM_END) {
		ret = zlib_deflate(&workspace->strm, Z_FINISH);
		if (ret == Z_STREAM_END)
			break;
		if (unlikely(ret != Z_OK && ret != Z_BUF_ERROR)) {
			zlib_deflateEnd(&workspace->strm);
			ret = -EIO;
			goto out;
		} else if (workspace->strm.avail_out == 0) {
			if (workspace->strm.total_out >= len) {
				ret = -E2BIG;
				goto out;
			}
			if (!bio_add_folio(bio, out_folio, folio_size(out_folio), 0)) {
				ret = -E2BIG;
				goto out;
			}
			/* Get another folio for the stream end. */
			out_folio = btrfs_alloc_compr_folio(fs_info);
			if (out_folio == NULL) {
				ret = -ENOMEM;
				goto out;
			}
			cfolio_out = folio_address(out_folio);
			workspace->strm.avail_out = min_folio_size;
			workspace->strm.next_out = cfolio_out;
		}
	}
	/* Queue the remaining part of the folio. */
	if (workspace->strm.total_out > bio->bi_iter.bi_size) {
		u32 cur_len = offset_in_folio(out_folio, workspace->strm.total_out);

		if (!bio_add_folio(bio, out_folio, cur_len, 0)) {
			ret = -E2BIG;
			goto out;
		}
	} else {
		/* The last folio hasn't' been utilized. */
		btrfs_free_compr_folio(out_folio);
	}
	out_folio = NULL;
	ASSERT(bio->bi_iter.bi_size == workspace->strm.total_out);
	zlib_deflateEnd(&workspace->strm);

	if (workspace->strm.total_out >= workspace->strm.total_in) {
		ret = -E2BIG;
		goto out;
	}

	ret = 0;
out:
	if (out_folio)
		btrfs_free_compr_folio(out_folio);
	if (data_in) {
		kunmap_local(data_in);
		folio_put(in_folio);
	}

	return ret;
}

int zlib_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
{
	struct btrfs_fs_info *fs_info = cb_to_fs_info(cb);