btrfs-progs: restore, fix page alignment issue for lzo compression
authorGui Hecheng <guihc.fnst@cn.fujitsu.com>
Mon, 22 Sep 2014 08:29:28 +0000 (16:29 +0800)
committerDavid Sterba <dsterba@suse.cz>
Mon, 23 Mar 2015 22:43:49 +0000 (23:43 +0100)
When runing restore under lzo compression, "bad compress length"
problems are encountered.
It is because there is a page alignment problem with the @decompress_lzo,
as follows:
|------| |----|-| |------|...|------|
  page         ^    page       page
       |
  3 bytes left

When lzo pages are compressed in memory, we will ensure that the 4 bytes
length header will not cross a page boundary.  There is a situation that
3 (or less) bytes are left at the end of a page, and then the 4 bytes
len is stored at the start of the next page.  But the @decompress_lzo
doesn't go to the start of the next page and continue to read the next 4
bytes which crosses two pages, so a random value is fetched as a "bad
compress length".

So we check page alignment every time before we are going to fetch the
next @len and after the former piece of data is decompressed.  If the
current page that we reach has less than 4 bytes left, then we should
fetch the next @len at the start of next page.

Signed-off-by: Gui Hecheng <guihc.fnst@cn.fujitsu.com>
[simplifed and moved into decompress_lzo]
Signed-off-by: David Sterba <dsterba@suse.cz>
cmds-restore.c

index 4a3f795..d2caa6a 100644 (file)
@@ -111,6 +111,8 @@ static int decompress_lzo(unsigned char *inbuf, char *outbuf, u64 compress_len,
        tot_in = LZO_LEN;
 
        while (tot_in < tot_len) {
+               size_t mod_page;
+               size_t rem_page;
                in_len = read_compress_length(inbuf);
 
                if ((tot_in + LZO_LEN + in_len) > tot_len) {
@@ -134,6 +136,17 @@ static int decompress_lzo(unsigned char *inbuf, char *outbuf, u64 compress_len,
                outbuf += new_len;
                inbuf += in_len;
                tot_in += in_len;
+
+               /*
+                * If the 4 byte header does not fit to the rest of the page we
+                * have to move to the next one, unless we read some garbage
+                */
+               mod_page = tot_in % PAGE_CACHE_SIZE;
+               rem_page = PAGE_CACHE_SIZE - mod_page;
+               if (rem_page < LZO_LEN) {
+                       inbuf += rem_page;
+                       tot_in += rem_page;
+               }
        }
 
        *decompress_len = out_len;