LCOV - code coverage report
Current view: top level - fs/btrfs - zlib.c (source / functions) Hit Total Coverage
Test: fstests of 6.5.0-rc4-xfsa @ Mon Jul 31 20:08:27 PDT 2023 Lines: 16 255 6.3 %
Date: 2023-07-31 20:08:27 Functions: 1 6 16.7 %

          Line data    Source code
       1             : // SPDX-License-Identifier: GPL-2.0
       2             : /*
       3             :  * Copyright (C) 2008 Oracle.  All rights reserved.
       4             :  *
       5             :  * Based on jffs2 zlib code:
       6             :  * Copyright © 2001-2007 Red Hat, Inc.
       7             :  * Created by David Woodhouse <dwmw2@infradead.org>
       8             :  */
       9             : 
      10             : #include <linux/kernel.h>
      11             : #include <linux/slab.h>
      12             : #include <linux/zlib.h>
      13             : #include <linux/zutil.h>
      14             : #include <linux/mm.h>
      15             : #include <linux/init.h>
      16             : #include <linux/err.h>
      17             : #include <linux/sched.h>
      18             : #include <linux/pagemap.h>
      19             : #include <linux/bio.h>
      20             : #include <linux/refcount.h>
      21             : #include "compression.h"
      22             : 
      23             : /* workspace buffer size for s390 zlib hardware support */
      24             : #define ZLIB_DFLTCC_BUF_SIZE    (4 * PAGE_SIZE)
      25             : 
      26             : struct workspace {
      27             :         z_stream strm;
      28             :         char *buf;
      29             :         unsigned int buf_size;
      30             :         struct list_head list;
      31             :         int level;
      32             : };
      33             : 
      34             : static struct workspace_manager wsm;
      35             : 
      36           0 : struct list_head *zlib_get_workspace(unsigned int level)
      37             : {
      38           0 :         struct list_head *ws = btrfs_get_workspace(BTRFS_COMPRESS_ZLIB, level);
      39           0 :         struct workspace *workspace = list_entry(ws, struct workspace, list);
      40             : 
      41           0 :         workspace->level = level;
      42             : 
      43           0 :         return ws;
      44             : }
      45             : 
      46           0 : void zlib_free_workspace(struct list_head *ws)
      47             : {
      48           0 :         struct workspace *workspace = list_entry(ws, struct workspace, list);
      49             : 
      50           0 :         kvfree(workspace->strm.workspace);
      51           0 :         kfree(workspace->buf);
      52           0 :         kfree(workspace);
      53           0 : }
      54             : 
      55           2 : struct list_head *zlib_alloc_workspace(unsigned int level)
      56             : {
      57           2 :         struct workspace *workspace;
      58           2 :         int workspacesize;
      59             : 
      60           2 :         workspace = kzalloc(sizeof(*workspace), GFP_KERNEL);
      61           2 :         if (!workspace)
      62             :                 return ERR_PTR(-ENOMEM);
      63             : 
      64           2 :         workspacesize = max(zlib_deflate_workspacesize(MAX_WBITS, MAX_MEM_LEVEL),
      65             :                         zlib_inflate_workspacesize());
      66           2 :         workspace->strm.workspace = kvzalloc(workspacesize, GFP_KERNEL | __GFP_NOWARN);
      67           2 :         workspace->level = level;
      68           2 :         workspace->buf = NULL;
      69             :         /*
      70             :          * In case of s390 zlib hardware support, allocate lager workspace
      71             :          * buffer. If allocator fails, fall back to a single page buffer.
      72             :          */
      73           2 :         if (zlib_deflate_dfltcc_enabled()) {
      74           0 :                 workspace->buf = kmalloc(ZLIB_DFLTCC_BUF_SIZE,
      75             :                                          __GFP_NOMEMALLOC | __GFP_NORETRY |
      76             :                                          __GFP_NOWARN | GFP_NOIO);
      77           0 :                 workspace->buf_size = ZLIB_DFLTCC_BUF_SIZE;
      78             :         }
      79           2 :         if (!workspace->buf) {
      80           2 :                 workspace->buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
      81           2 :                 workspace->buf_size = PAGE_SIZE;
      82             :         }
      83           2 :         if (!workspace->strm.workspace || !workspace->buf)
      84           0 :                 goto fail;
      85             : 
      86           2 :         INIT_LIST_HEAD(&workspace->list);
      87             : 
      88           2 :         return &workspace->list;
      89             : fail:
      90           0 :         zlib_free_workspace(&workspace->list);
      91           0 :         return ERR_PTR(-ENOMEM);
      92             : }
      93             : 
      94           0 : int zlib_compress_pages(struct list_head *ws, struct address_space *mapping,
      95             :                 u64 start, struct page **pages, unsigned long *out_pages,
      96             :                 unsigned long *total_in, unsigned long *total_out)
      97             : {
      98           0 :         struct workspace *workspace = list_entry(ws, struct workspace, list);
      99           0 :         int ret;
     100           0 :         char *data_in = NULL;
     101           0 :         char *cpage_out;
     102           0 :         int nr_pages = 0;
     103           0 :         struct page *in_page = NULL;
     104           0 :         struct page *out_page = NULL;
     105           0 :         unsigned long bytes_left;
     106           0 :         unsigned int in_buf_pages;
     107           0 :         unsigned long len = *total_out;
     108           0 :         unsigned long nr_dest_pages = *out_pages;
     109           0 :         const unsigned long max_out = nr_dest_pages * PAGE_SIZE;
     110             : 
     111           0 :         *out_pages = 0;
     112           0 :         *total_out = 0;
     113           0 :         *total_in = 0;
     114             : 
     115           0 :         if (Z_OK != zlib_deflateInit(&workspace->strm, workspace->level)) {
     116           0 :                 pr_warn("BTRFS: deflateInit failed\n");
     117           0 :                 ret = -EIO;
     118           0 :                 goto out;
     119             :         }
     120             : 
     121           0 :         workspace->strm.total_in = 0;
     122           0 :         workspace->strm.total_out = 0;
     123             : 
     124           0 :         out_page = alloc_page(GFP_NOFS);
     125           0 :         if (out_page == NULL) {
     126           0 :                 ret = -ENOMEM;
     127           0 :                 goto out;
     128             :         }
     129           0 :         cpage_out = page_address(out_page);
     130           0 :         pages[0] = out_page;
     131           0 :         nr_pages = 1;
     132             : 
     133           0 :         workspace->strm.next_in = workspace->buf;
     134           0 :         workspace->strm.avail_in = 0;
     135           0 :         workspace->strm.next_out = cpage_out;
     136           0 :         workspace->strm.avail_out = PAGE_SIZE;
     137             : 
     138           0 :         while (workspace->strm.total_in < len) {
     139             :                 /*
     140             :                  * Get next input pages and copy the contents to
     141             :                  * the workspace buffer if required.
     142             :                  */
     143           0 :                 if (workspace->strm.avail_in == 0) {
     144           0 :                         bytes_left = len - workspace->strm.total_in;
     145           0 :                         in_buf_pages = min(DIV_ROUND_UP(bytes_left, PAGE_SIZE),
     146             :                                            workspace->buf_size / PAGE_SIZE);
     147           0 :                         if (in_buf_pages > 1) {
     148             :                                 int i;
     149             : 
     150           0 :                                 for (i = 0; i < in_buf_pages; i++) {
     151           0 :                                         if (data_in) {
     152           0 :                                                 kunmap_local(data_in);
     153           0 :                                                 put_page(in_page);
     154             :                                         }
     155           0 :                                         in_page = find_get_page(mapping,
     156           0 :                                                                 start >> PAGE_SHIFT);
     157           0 :                                         data_in = kmap_local_page(in_page);
     158           0 :                                         copy_page(workspace->buf + i * PAGE_SIZE,
     159             :                                                   data_in);
     160           0 :                                         start += PAGE_SIZE;
     161             :                                 }
     162           0 :                                 workspace->strm.next_in = workspace->buf;
     163             :                         } else {
     164           0 :                                 if (data_in) {
     165           0 :                                         kunmap_local(data_in);
     166           0 :                                         put_page(in_page);
     167             :                                 }
     168           0 :                                 in_page = find_get_page(mapping,
     169           0 :                                                         start >> PAGE_SHIFT);
     170           0 :                                 data_in = kmap_local_page(in_page);
     171           0 :                                 start += PAGE_SIZE;
     172           0 :                                 workspace->strm.next_in = data_in;
     173             :                         }
     174           0 :                         workspace->strm.avail_in = min(bytes_left,
     175             :                                                        (unsigned long) workspace->buf_size);
     176             :                 }
     177             : 
     178           0 :                 ret = zlib_deflate(&workspace->strm, Z_SYNC_FLUSH);
     179           0 :                 if (ret != Z_OK) {
     180           0 :                         pr_debug("BTRFS: deflate in loop returned %d\n",
     181             :                                ret);
     182           0 :                         zlib_deflateEnd(&workspace->strm);
     183           0 :                         ret = -EIO;
     184           0 :                         goto out;
     185             :                 }
     186             : 
     187             :                 /* we're making it bigger, give up */
     188           0 :                 if (workspace->strm.total_in > 8192 &&
     189             :                     workspace->strm.total_in <
     190           0 :                     workspace->strm.total_out) {
     191           0 :                         ret = -E2BIG;
     192           0 :                         goto out;
     193             :                 }
     194             :                 /* we need another page for writing out.  Test this
     195             :                  * before the total_in so we will pull in a new page for
     196             :                  * the stream end if required
     197             :                  */
     198           0 :                 if (workspace->strm.avail_out == 0) {
     199           0 :                         if (nr_pages == nr_dest_pages) {
     200           0 :                                 ret = -E2BIG;
     201           0 :                                 goto out;
     202             :                         }
     203           0 :                         out_page = alloc_page(GFP_NOFS);
     204           0 :                         if (out_page == NULL) {
     205           0 :                                 ret = -ENOMEM;
     206           0 :                                 goto out;
     207             :                         }
     208           0 :                         cpage_out = page_address(out_page);
     209           0 :                         pages[nr_pages] = out_page;
     210           0 :                         nr_pages++;
     211           0 :                         workspace->strm.avail_out = PAGE_SIZE;
     212           0 :                         workspace->strm.next_out = cpage_out;
     213             :                 }
     214             :                 /* we're all done */
     215           0 :                 if (workspace->strm.total_in >= len)
     216             :                         break;
     217           0 :                 if (workspace->strm.total_out > max_out)
     218             :                         break;
     219             :         }
     220           0 :         workspace->strm.avail_in = 0;
     221             :         /*
     222             :          * Call deflate with Z_FINISH flush parameter providing more output
     223             :          * space but no more input data, until it returns with Z_STREAM_END.
     224             :          */
     225           0 :         while (ret != Z_STREAM_END) {
     226           0 :                 ret = zlib_deflate(&workspace->strm, Z_FINISH);
     227           0 :                 if (ret == Z_STREAM_END)
     228             :                         break;
     229           0 :                 if (ret != Z_OK && ret != Z_BUF_ERROR) {
     230           0 :                         zlib_deflateEnd(&workspace->strm);
     231           0 :                         ret = -EIO;
     232           0 :                         goto out;
     233           0 :                 } else if (workspace->strm.avail_out == 0) {
     234             :                         /* get another page for the stream end */
     235           0 :                         if (nr_pages == nr_dest_pages) {
     236           0 :                                 ret = -E2BIG;
     237           0 :                                 goto out;
     238             :                         }
     239           0 :                         out_page = alloc_page(GFP_NOFS);
     240           0 :                         if (out_page == NULL) {
     241           0 :                                 ret = -ENOMEM;
     242           0 :                                 goto out;
     243             :                         }
     244           0 :                         cpage_out = page_address(out_page);
     245           0 :                         pages[nr_pages] = out_page;
     246           0 :                         nr_pages++;
     247           0 :                         workspace->strm.avail_out = PAGE_SIZE;
     248           0 :                         workspace->strm.next_out = cpage_out;
     249             :                 }
     250             :         }
     251           0 :         zlib_deflateEnd(&workspace->strm);
     252             : 
     253           0 :         if (workspace->strm.total_out >= workspace->strm.total_in) {
     254           0 :                 ret = -E2BIG;
     255           0 :                 goto out;
     256             :         }
     257             : 
     258           0 :         ret = 0;
     259           0 :         *total_out = workspace->strm.total_out;
     260           0 :         *total_in = workspace->strm.total_in;
     261           0 : out:
     262           0 :         *out_pages = nr_pages;
     263           0 :         if (data_in) {
     264           0 :                 kunmap_local(data_in);
     265           0 :                 put_page(in_page);
     266             :         }
     267             : 
     268           0 :         return ret;
     269             : }
     270             : 
     271           0 : int zlib_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
     272             : {
     273           0 :         struct workspace *workspace = list_entry(ws, struct workspace, list);
     274           0 :         int ret = 0, ret2;
     275           0 :         int wbits = MAX_WBITS;
     276           0 :         char *data_in;
     277           0 :         size_t total_out = 0;
     278           0 :         unsigned long page_in_index = 0;
     279           0 :         size_t srclen = cb->compressed_len;
     280           0 :         unsigned long total_pages_in = DIV_ROUND_UP(srclen, PAGE_SIZE);
     281           0 :         unsigned long buf_start;
     282           0 :         struct page **pages_in = cb->compressed_pages;
     283             : 
     284           0 :         data_in = kmap_local_page(pages_in[page_in_index]);
     285           0 :         workspace->strm.next_in = data_in;
     286           0 :         workspace->strm.avail_in = min_t(size_t, srclen, PAGE_SIZE);
     287           0 :         workspace->strm.total_in = 0;
     288             : 
     289           0 :         workspace->strm.total_out = 0;
     290           0 :         workspace->strm.next_out = workspace->buf;
     291           0 :         workspace->strm.avail_out = workspace->buf_size;
     292             : 
     293             :         /* If it's deflate, and it's got no preset dictionary, then
     294             :            we can tell zlib to skip the adler32 check. */
     295           0 :         if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
     296           0 :             ((data_in[0] & 0x0f) == Z_DEFLATED) &&
     297           0 :             !(((data_in[0]<<8) + data_in[1]) % 31)) {
     298             : 
     299           0 :                 wbits = -((data_in[0] >> 4) + 8);
     300           0 :                 workspace->strm.next_in += 2;
     301           0 :                 workspace->strm.avail_in -= 2;
     302             :         }
     303             : 
     304           0 :         if (Z_OK != zlib_inflateInit2(&workspace->strm, wbits)) {
     305           0 :                 pr_warn("BTRFS: inflateInit failed\n");
     306           0 :                 kunmap_local(data_in);
     307           0 :                 return -EIO;
     308             :         }
     309           0 :         while (workspace->strm.total_in < srclen) {
     310           0 :                 ret = zlib_inflate(&workspace->strm, Z_NO_FLUSH);
     311           0 :                 if (ret != Z_OK && ret != Z_STREAM_END)
     312             :                         break;
     313             : 
     314           0 :                 buf_start = total_out;
     315           0 :                 total_out = workspace->strm.total_out;
     316             : 
     317             :                 /* we didn't make progress in this inflate call, we're done */
     318           0 :                 if (buf_start == total_out)
     319             :                         break;
     320             : 
     321           0 :                 ret2 = btrfs_decompress_buf2page(workspace->buf,
     322             :                                 total_out - buf_start, cb, buf_start);
     323           0 :                 if (ret2 == 0) {
     324           0 :                         ret = 0;
     325           0 :                         goto done;
     326             :                 }
     327             : 
     328           0 :                 workspace->strm.next_out = workspace->buf;
     329           0 :                 workspace->strm.avail_out = workspace->buf_size;
     330             : 
     331           0 :                 if (workspace->strm.avail_in == 0) {
     332           0 :                         unsigned long tmp;
     333           0 :                         kunmap_local(data_in);
     334           0 :                         page_in_index++;
     335           0 :                         if (page_in_index >= total_pages_in) {
     336             :                                 data_in = NULL;
     337             :                                 break;
     338             :                         }
     339           0 :                         data_in = kmap_local_page(pages_in[page_in_index]);
     340           0 :                         workspace->strm.next_in = data_in;
     341           0 :                         tmp = srclen - workspace->strm.total_in;
     342           0 :                         workspace->strm.avail_in = min(tmp, PAGE_SIZE);
     343             :                 }
     344             :         }
     345           0 :         if (ret != Z_STREAM_END)
     346             :                 ret = -EIO;
     347             :         else
     348           0 :                 ret = 0;
     349           0 : done:
     350           0 :         zlib_inflateEnd(&workspace->strm);
     351           0 :         if (data_in)
     352           0 :                 kunmap_local(data_in);
     353           0 :         return ret;
     354             : }
     355             : 
     356           0 : int zlib_decompress(struct list_head *ws, const u8 *data_in,
     357             :                 struct page *dest_page, unsigned long start_byte, size_t srclen,
     358             :                 size_t destlen)
     359             : {
     360           0 :         struct workspace *workspace = list_entry(ws, struct workspace, list);
     361           0 :         int ret = 0;
     362           0 :         int wbits = MAX_WBITS;
     363           0 :         unsigned long bytes_left;
     364           0 :         unsigned long total_out = 0;
     365           0 :         unsigned long pg_offset = 0;
     366             : 
     367           0 :         destlen = min_t(unsigned long, destlen, PAGE_SIZE);
     368           0 :         bytes_left = destlen;
     369             : 
     370           0 :         workspace->strm.next_in = data_in;
     371           0 :         workspace->strm.avail_in = srclen;
     372           0 :         workspace->strm.total_in = 0;
     373             : 
     374           0 :         workspace->strm.next_out = workspace->buf;
     375           0 :         workspace->strm.avail_out = workspace->buf_size;
     376           0 :         workspace->strm.total_out = 0;
     377             :         /* If it's deflate, and it's got no preset dictionary, then
     378             :            we can tell zlib to skip the adler32 check. */
     379           0 :         if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
     380           0 :             ((data_in[0] & 0x0f) == Z_DEFLATED) &&
     381           0 :             !(((data_in[0]<<8) + data_in[1]) % 31)) {
     382             : 
     383           0 :                 wbits = -((data_in[0] >> 4) + 8);
     384           0 :                 workspace->strm.next_in += 2;
     385           0 :                 workspace->strm.avail_in -= 2;
     386             :         }
     387             : 
     388           0 :         if (Z_OK != zlib_inflateInit2(&workspace->strm, wbits)) {
     389           0 :                 pr_warn("BTRFS: inflateInit failed\n");
     390           0 :                 return -EIO;
     391             :         }
     392             : 
     393           0 :         while (bytes_left > 0) {
     394           0 :                 unsigned long buf_start;
     395           0 :                 unsigned long buf_offset;
     396           0 :                 unsigned long bytes;
     397             : 
     398           0 :                 ret = zlib_inflate(&workspace->strm, Z_NO_FLUSH);
     399           0 :                 if (ret != Z_OK && ret != Z_STREAM_END)
     400             :                         break;
     401             : 
     402           0 :                 buf_start = total_out;
     403           0 :                 total_out = workspace->strm.total_out;
     404             : 
     405           0 :                 if (total_out == buf_start) {
     406             :                         ret = -EIO;
     407             :                         break;
     408             :                 }
     409             : 
     410           0 :                 if (total_out <= start_byte)
     411           0 :                         goto next;
     412             : 
     413           0 :                 if (total_out > start_byte && buf_start < start_byte)
     414           0 :                         buf_offset = start_byte - buf_start;
     415             :                 else
     416             :                         buf_offset = 0;
     417             : 
     418           0 :                 bytes = min(PAGE_SIZE - pg_offset,
     419             :                             PAGE_SIZE - (buf_offset % PAGE_SIZE));
     420           0 :                 bytes = min(bytes, bytes_left);
     421             : 
     422           0 :                 memcpy_to_page(dest_page, pg_offset,
     423           0 :                                workspace->buf + buf_offset, bytes);
     424             : 
     425           0 :                 pg_offset += bytes;
     426           0 :                 bytes_left -= bytes;
     427           0 : next:
     428           0 :                 workspace->strm.next_out = workspace->buf;
     429           0 :                 workspace->strm.avail_out = workspace->buf_size;
     430             :         }
     431             : 
     432           0 :         if (ret != Z_STREAM_END && bytes_left != 0)
     433             :                 ret = -EIO;
     434             :         else
     435           0 :                 ret = 0;
     436             : 
     437           0 :         zlib_inflateEnd(&workspace->strm);
     438             : 
     439             :         /*
     440             :          * this should only happen if zlib returned fewer bytes than we
     441             :          * expected.  btrfs_get_block is responsible for zeroing from the
     442             :          * end of the inline extent (destlen) to the end of the page
     443             :          */
     444           0 :         if (pg_offset < destlen) {
     445           0 :                 memzero_page(dest_page, pg_offset, destlen - pg_offset);
     446             :         }
     447             :         return ret;
     448             : }
     449             : 
     450             : const struct btrfs_compress_op btrfs_zlib_compress = {
     451             :         .workspace_manager      = &wsm,
     452             :         .max_level              = 9,
     453             :         .default_level          = BTRFS_ZLIB_DEFAULT_LEVEL,
     454             : };

Generated by: LCOV version 1.14