diff options
Diffstat (limited to 'fs/btrfs/lzo.c')
| -rw-r--r-- | fs/btrfs/lzo.c | 236 | 
1 files changed, 100 insertions, 136 deletions
diff --git a/fs/btrfs/lzo.c b/fs/btrfs/lzo.c index cd042c7567a4..c25dfd1a8a54 100644 --- a/fs/btrfs/lzo.c +++ b/fs/btrfs/lzo.c @@ -14,6 +14,7 @@  #include <linux/lzo.h>  #include <linux/refcount.h>  #include "compression.h" +#include "ctree.h"  #define LZO_LEN	4 @@ -140,18 +141,18 @@ int lzo_compress_pages(struct list_head *ws, struct address_space *mapping,  	*total_in = 0;  	in_page = find_get_page(mapping, start >> PAGE_SHIFT); -	data_in = kmap(in_page); +	data_in = page_address(in_page);  	/*  	 * store the size of all chunks of compressed data in  	 * the first 4 bytes  	 */ -	out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM); +	out_page = alloc_page(GFP_NOFS);  	if (out_page == NULL) {  		ret = -ENOMEM;  		goto out;  	} -	cpage_out = kmap(out_page); +	cpage_out = page_address(out_page);  	out_offset = LZO_LEN;  	tot_out = LZO_LEN;  	pages[0] = out_page; @@ -209,19 +210,18 @@ int lzo_compress_pages(struct list_head *ws, struct address_space *mapping,  				if (out_len == 0 && tot_in >= len)  					break; -				kunmap(out_page);  				if (nr_pages == nr_dest_pages) {  					out_page = NULL;  					ret = -E2BIG;  					goto out;  				} -				out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM); +				out_page = alloc_page(GFP_NOFS);  				if (out_page == NULL) {  					ret = -ENOMEM;  					goto out;  				} -				cpage_out = kmap(out_page); +				cpage_out = page_address(out_page);  				pages[nr_pages++] = out_page;  				pg_bytes_left = PAGE_SIZE; @@ -243,12 +243,11 @@ int lzo_compress_pages(struct list_head *ws, struct address_space *mapping,  			break;  		bytes_left = len - tot_in; -		kunmap(in_page);  		put_page(in_page);  		start += PAGE_SIZE;  		in_page = find_get_page(mapping, start >> PAGE_SHIFT); -		data_in = kmap(in_page); +		data_in = page_address(in_page);  		in_len = min(bytes_left, PAGE_SIZE);  	} @@ -258,164 +257,130 @@ int lzo_compress_pages(struct list_head *ws, struct address_space *mapping,  	}  	/* store the size of all chunks of compressed data */ -	sizes_ptr = kmap_local_page(pages[0]); +	sizes_ptr = page_address(pages[0]);  	write_compress_length(sizes_ptr, tot_out); -	kunmap_local(sizes_ptr);  	ret = 0;  	*total_out = tot_out;  	*total_in = tot_in;  out:  	*out_pages = nr_pages; -	if (out_page) -		kunmap(out_page); -	if (in_page) { -		kunmap(in_page); +	if (in_page)  		put_page(in_page); -	}  	return ret;  } +/* + * 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]; + +		memcpy(dest + *cur_in - orig_in, +			page_address(cur_page) + offset_in_page(*cur_in), +			copy_len); + +		*cur_in += copy_len; +	} +} +  int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)  {  	struct workspace *workspace = list_entry(ws, struct workspace, list); -	int ret = 0, ret2; -	char *data_in; -	unsigned long page_in_index = 0; -	size_t srclen = cb->compressed_len; -	unsigned long total_pages_in = DIV_ROUND_UP(srclen, PAGE_SIZE); -	unsigned long buf_start; -	unsigned long buf_offset = 0; -	unsigned long bytes; -	unsigned long working_bytes; -	size_t in_len; -	size_t out_len; -	const size_t max_segment_len = lzo1x_worst_compress(PAGE_SIZE); -	unsigned long in_offset; -	unsigned long in_page_bytes_left; -	unsigned long tot_in; -	unsigned long tot_out; -	unsigned long tot_len; -	char *buf; -	bool may_late_unmap, need_unmap; -	struct page **pages_in = cb->compressed_pages; -	u64 disk_start = cb->start; -	struct bio *orig_bio = cb->orig_bio; +	const struct btrfs_fs_info *fs_info = btrfs_sb(cb->inode->i_sb); +	const u32 sectorsize = fs_info->sectorsize; +	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; + +	len_in = read_compress_length(page_address(cb->compressed_pages[0])); +	cur_in += LZO_LEN; -	data_in = kmap(pages_in[0]); -	tot_len = read_compress_length(data_in);  	/* -	 * Compressed data header check. +	 * LZO header length check  	 * -	 * The real compressed size can't exceed the maximum extent length, and -	 * all pages should be used (whole unused page with just the segment -	 * header is not possible).  If this happens it means the compressed -	 * extent is corrupted. +	 * 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.  	 */ -	if (tot_len > min_t(size_t, BTRFS_MAX_COMPRESSED, srclen) || -	    tot_len < srclen - PAGE_SIZE) { -		ret = -EUCLEAN; -		goto done; +	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;  	} -	tot_in = LZO_LEN; -	in_offset = LZO_LEN; -	in_page_bytes_left = PAGE_SIZE - LZO_LEN; - -	tot_out = 0; - -	while (tot_in < tot_len) { -		in_len = read_compress_length(data_in + in_offset); -		in_page_bytes_left -= LZO_LEN; -		in_offset += LZO_LEN; -		tot_in += LZO_LEN; +	/* 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);  		/* -		 * Segment header check. -		 * -		 * The segment length must not exceed the maximum LZO -		 * compression size, nor the total compressed size. +		 * We should always have enough space for one segment header +		 * inside current sector.  		 */ -		if (in_len > max_segment_len || tot_in + in_len > tot_len) { -			ret = -EUCLEAN; -			goto done; -		} - -		tot_in += in_len; -		working_bytes = in_len; -		may_late_unmap = need_unmap = false; - -		/* fast path: avoid using the working buffer */ -		if (in_page_bytes_left >= in_len) { -			buf = data_in + in_offset; -			bytes = in_len; -			may_late_unmap = true; -			goto cont; -		} - -		/* copy bytes from the pages into the working buffer */ -		buf = workspace->cbuf; -		buf_offset = 0; -		while (working_bytes) { -			bytes = min(working_bytes, in_page_bytes_left); - -			memcpy(buf + buf_offset, data_in + in_offset, bytes); -			buf_offset += bytes; -cont: -			working_bytes -= bytes; -			in_page_bytes_left -= bytes; -			in_offset += bytes; - -			/* check if we need to pick another page */ -			if ((working_bytes == 0 && in_page_bytes_left < LZO_LEN) -			    || in_page_bytes_left == 0) { -				tot_in += in_page_bytes_left; - -				if (working_bytes == 0 && tot_in >= tot_len) -					break; - -				if (page_in_index + 1 >= total_pages_in) { -					ret = -EIO; -					goto done; -				} - -				if (may_late_unmap) -					need_unmap = true; -				else -					kunmap(pages_in[page_in_index]); - -				data_in = kmap(pages_in[++page_in_index]); - -				in_page_bytes_left = PAGE_SIZE; -				in_offset = 0; -			} -		} - -		out_len = max_segment_len; -		ret = lzo1x_decompress_safe(buf, in_len, workspace->buf, -					    &out_len); -		if (need_unmap) -			kunmap(pages_in[page_in_index - 1]); +		ASSERT(cur_in / sectorsize == +		       (cur_in + LZO_LEN - 1) / sectorsize); +		cur_page = cb->compressed_pages[cur_in / PAGE_SIZE]; +		ASSERT(cur_page); +		seg_len = read_compress_length(page_address(cur_page) + +					       offset_in_page(cur_in)); +		cur_in += LZO_LEN; + +		/* 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);  		if (ret != LZO_E_OK) { -			pr_warn("BTRFS: decompress failed\n"); +			btrfs_err(fs_info, "failed to decompress");  			ret = -EIO; -			break; +			goto out;  		} -		buf_start = tot_out; -		tot_out += out_len; +		/* Copy the data into inode pages */ +		ret = btrfs_decompress_buf2page(workspace->buf, out_len, cb, cur_out); +		cur_out += out_len; -		ret2 = btrfs_decompress_buf2page(workspace->buf, buf_start, -						 tot_out, disk_start, orig_bio); -		if (ret2 == 0) -			break; +		/* All data read, exit */ +		if (ret == 0) +			goto out; +		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;  	} -done: -	kunmap(pages_in[page_in_index]); +out:  	if (!ret) -		zero_fill_bio(orig_bio); +		zero_fill_bio(cb->orig_bio);  	return ret;  } @@ -466,7 +431,7 @@ int lzo_decompress(struct list_head *ws, unsigned char *data_in,  	destlen = min_t(unsigned long, destlen, PAGE_SIZE);  	bytes = min_t(unsigned long, destlen, out_len - start_byte); -	kaddr = kmap_local_page(dest_page); +	kaddr = page_address(dest_page);  	memcpy(kaddr, workspace->buf + start_byte, bytes);  	/* @@ -476,7 +441,6 @@ int lzo_decompress(struct list_head *ws, unsigned char *data_in,  	 */  	if (bytes < destlen)  		memset(kaddr+bytes, 0, destlen-bytes); -	kunmap_local(kaddr);  out:  	return ret;  }  |