| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (C) 2019 HUAWEI, Inc.
- * https://www.huawei.com/
- * Copyright (C) 2024 Alibaba Cloud
- */
- #include "compress.h"
- #include <linux/lz4.h>
- #define LZ4_MAX_DISTANCE_PAGES (DIV_ROUND_UP(LZ4_DISTANCE_MAX, PAGE_SIZE) + 1)
- static int z_erofs_load_lz4_config(struct super_block *sb,
- struct erofs_super_block *dsb, void *data, int size)
- {
- struct erofs_sb_info *sbi = EROFS_SB(sb);
- struct z_erofs_lz4_cfgs *lz4 = data;
- u16 distance;
- if (lz4) {
- if (size < sizeof(struct z_erofs_lz4_cfgs)) {
- erofs_err(sb, "invalid lz4 cfgs, size=%u", size);
- return -EINVAL;
- }
- distance = le16_to_cpu(lz4->max_distance);
- sbi->lz4.max_pclusterblks = le16_to_cpu(lz4->max_pclusterblks);
- if (!sbi->lz4.max_pclusterblks) {
- sbi->lz4.max_pclusterblks = 1; /* reserved case */
- } else if (sbi->lz4.max_pclusterblks >
- erofs_blknr(sb, Z_EROFS_PCLUSTER_MAX_SIZE)) {
- erofs_err(sb, "too large lz4 pclusterblks %u",
- sbi->lz4.max_pclusterblks);
- return -EINVAL;
- }
- } else {
- distance = le16_to_cpu(dsb->u1.lz4_max_distance);
- if (!distance && !erofs_sb_has_lz4_0padding(sbi))
- return 0;
- sbi->lz4.max_pclusterblks = 1;
- sbi->available_compr_algs = 1 << Z_EROFS_COMPRESSION_LZ4;
- }
- sbi->lz4.max_distance_pages = distance ?
- DIV_ROUND_UP(distance, PAGE_SIZE) + 1 :
- LZ4_MAX_DISTANCE_PAGES;
- return z_erofs_gbuf_growsize(sbi->lz4.max_pclusterblks);
- }
- /*
- * Fill all gaps with bounce pages if it's a sparse page list. Also check if
- * all physical pages are consecutive, which can be seen for moderate CR.
- */
- static int z_erofs_lz4_prepare_dstpages(struct z_erofs_decompress_req *rq,
- struct page **pagepool)
- {
- struct page *availables[LZ4_MAX_DISTANCE_PAGES] = { NULL };
- unsigned long bounced[DIV_ROUND_UP(LZ4_MAX_DISTANCE_PAGES,
- BITS_PER_LONG)] = { 0 };
- unsigned int lz4_max_distance_pages =
- EROFS_SB(rq->sb)->lz4.max_distance_pages;
- void *kaddr = NULL;
- unsigned int i, j, top;
- top = 0;
- for (i = j = 0; i < rq->outpages; ++i, ++j) {
- struct page *const page = rq->out[i];
- struct page *victim;
- if (j >= lz4_max_distance_pages)
- j = 0;
- /* 'valid' bounced can only be tested after a complete round */
- if (!rq->fillgaps && test_bit(j, bounced)) {
- DBG_BUGON(i < lz4_max_distance_pages);
- DBG_BUGON(top >= lz4_max_distance_pages);
- availables[top++] = rq->out[i - lz4_max_distance_pages];
- }
- if (page) {
- __clear_bit(j, bounced);
- if (!PageHighMem(page)) {
- if (!i) {
- kaddr = page_address(page);
- continue;
- }
- if (kaddr &&
- kaddr + PAGE_SIZE == page_address(page)) {
- kaddr += PAGE_SIZE;
- continue;
- }
- }
- kaddr = NULL;
- continue;
- }
- kaddr = NULL;
- __set_bit(j, bounced);
- if (top) {
- victim = availables[--top];
- } else {
- victim = __erofs_allocpage(pagepool, rq->gfp, true);
- if (!victim)
- return -ENOMEM;
- set_page_private(victim, Z_EROFS_SHORTLIVED_PAGE);
- }
- rq->out[i] = victim;
- }
- return kaddr ? 1 : 0;
- }
- static void *z_erofs_lz4_handle_overlap(const struct z_erofs_decompress_req *rq,
- void *inpage, void *out, unsigned int *inputmargin,
- int *maptype, bool may_inplace)
- {
- unsigned int oend, omargin, cnt, i;
- struct page **in;
- void *src;
- /*
- * If in-place I/O isn't used, for example, the bounce compressed cache
- * can hold data for incomplete read requests. Just map the compressed
- * buffer as well and decompress directly.
- */
- if (!rq->inplace_io) {
- if (rq->inpages <= 1) {
- *maptype = 0;
- return inpage;
- }
- kunmap_local(inpage);
- src = erofs_vm_map_ram(rq->in, rq->inpages);
- if (!src)
- return ERR_PTR(-ENOMEM);
- *maptype = 1;
- return src;
- }
- /*
- * Then, deal with in-place I/Os. The reasons why in-place I/O is useful
- * are: (1) It minimizes memory footprint during the I/O submission,
- * which is useful for slow storage (including network devices and
- * low-end HDDs/eMMCs) but with a lot inflight I/Os; (2) If in-place
- * decompression can also be applied, it will reuse the unique buffer so
- * that no extra CPU D-cache is polluted with temporary compressed data
- * for extreme performance.
- */
- oend = rq->pageofs_out + rq->outputsize;
- omargin = PAGE_ALIGN(oend) - oend;
- if (!rq->partial_decoding && may_inplace &&
- omargin >= LZ4_DECOMPRESS_INPLACE_MARGIN(rq->inputsize)) {
- for (i = 0; i < rq->inpages; ++i)
- if (rq->out[rq->outpages - rq->inpages + i] !=
- rq->in[i])
- break;
- if (i >= rq->inpages) {
- kunmap_local(inpage);
- *maptype = 3;
- return out + ((rq->outpages - rq->inpages) << PAGE_SHIFT);
- }
- }
- /*
- * If in-place decompression can't be applied, copy compressed data that
- * may potentially overlap during decompression to a per-CPU buffer.
- */
- src = z_erofs_get_gbuf(rq->inpages);
- if (!src) {
- DBG_BUGON(1);
- kunmap_local(inpage);
- return ERR_PTR(-EFAULT);
- }
- for (i = 0, in = rq->in; i < rq->inputsize; i += cnt, ++in) {
- cnt = min_t(u32, rq->inputsize - i, PAGE_SIZE - *inputmargin);
- if (!inpage)
- inpage = kmap_local_page(*in);
- memcpy(src + i, inpage + *inputmargin, cnt);
- kunmap_local(inpage);
- inpage = NULL;
- *inputmargin = 0;
- }
- *maptype = 2;
- return src;
- }
- /*
- * Get the exact on-disk size of the compressed data:
- * - For LZ4, it should apply if the zero_padding feature is on (5.3+);
- * - For others, zero_padding is enabled all the time.
- */
- const char *z_erofs_fixup_insize(struct z_erofs_decompress_req *rq,
- const char *padbuf, unsigned int padbufsize)
- {
- const char *padend;
- padend = memchr_inv(padbuf, 0, padbufsize);
- if (!padend)
- return "compressed data start not found";
- rq->inputsize -= padend - padbuf;
- rq->pageofs_in += padend - padbuf;
- return NULL;
- }
- static const char *__z_erofs_lz4_decompress(struct z_erofs_decompress_req *rq,
- u8 *dst)
- {
- bool may_inplace = false;
- unsigned int inputmargin;
- u8 *out, *headpage, *src;
- const char *reason;
- int ret, maptype;
- headpage = kmap_local_page(*rq->in);
- reason = z_erofs_fixup_insize(rq, headpage + rq->pageofs_in,
- min_t(unsigned int, rq->inputsize,
- rq->sb->s_blocksize - rq->pageofs_in));
- if (reason) {
- kunmap_local(headpage);
- return reason;
- }
- may_inplace = !((rq->pageofs_in + rq->inputsize) &
- (rq->sb->s_blocksize - 1));
- inputmargin = rq->pageofs_in;
- src = z_erofs_lz4_handle_overlap(rq, headpage, dst, &inputmargin,
- &maptype, may_inplace);
- if (IS_ERR(src))
- return ERR_CAST(src);
- out = dst + rq->pageofs_out;
- if (rq->partial_decoding)
- ret = LZ4_decompress_safe_partial(src + inputmargin, out,
- rq->inputsize, rq->outputsize, rq->outputsize);
- else
- ret = LZ4_decompress_safe(src + inputmargin, out,
- rq->inputsize, rq->outputsize);
- if (ret == rq->outputsize)
- reason = NULL;
- else if (ret < 0)
- reason = "corrupted compressed data";
- else
- reason = "unexpected end of stream";
- if (!maptype) {
- kunmap_local(headpage);
- } else if (maptype == 1) {
- vm_unmap_ram(src, rq->inpages);
- } else if (maptype == 2) {
- z_erofs_put_gbuf(src);
- } else if (maptype != 3) {
- DBG_BUGON(1);
- return ERR_PTR(-EFAULT);
- }
- return reason;
- }
- static const char *z_erofs_lz4_decompress(struct z_erofs_decompress_req *rq,
- struct page **pagepool)
- {
- unsigned int dst_maptype;
- const char *reason;
- void *dst;
- int ret;
- /* one optimized fast path only for non bigpcluster cases yet */
- if (rq->inpages == 1 && rq->outpages == 1 && !rq->inplace_io) {
- DBG_BUGON(!*rq->out);
- dst = kmap_local_page(*rq->out);
- dst_maptype = 0;
- } else {
- /* general decoding path which can be used for all cases */
- ret = z_erofs_lz4_prepare_dstpages(rq, pagepool);
- if (ret < 0)
- return ERR_PTR(ret);
- if (ret > 0) {
- dst = page_address(*rq->out);
- dst_maptype = 1;
- } else {
- dst = erofs_vm_map_ram(rq->out, rq->outpages);
- if (!dst)
- return ERR_PTR(-ENOMEM);
- dst_maptype = 2;
- }
- }
- reason = __z_erofs_lz4_decompress(rq, dst);
- if (!dst_maptype)
- kunmap_local(dst);
- else if (dst_maptype == 2)
- vm_unmap_ram(dst, rq->outpages);
- return reason;
- }
- static const char *z_erofs_transform_plain(struct z_erofs_decompress_req *rq,
- struct page **pagepool)
- {
- const unsigned int nrpages_in = rq->inpages, nrpages_out = rq->outpages;
- const unsigned int bs = rq->sb->s_blocksize;
- unsigned int cur = 0, ni = 0, no, pi, po, insz, cnt;
- u8 *kin;
- if (rq->outputsize > rq->inputsize)
- return ERR_PTR(-EOPNOTSUPP);
- if (rq->alg == Z_EROFS_COMPRESSION_INTERLACED) {
- cur = bs - (rq->pageofs_out & (bs - 1));
- pi = (rq->pageofs_in + rq->inputsize - cur) & ~PAGE_MASK;
- cur = min(cur, rq->outputsize);
- if (cur && rq->out[0]) {
- kin = kmap_local_page(rq->in[nrpages_in - 1]);
- if (rq->out[0] == rq->in[nrpages_in - 1])
- memmove(kin + rq->pageofs_out, kin + pi, cur);
- else
- memcpy_to_page(rq->out[0], rq->pageofs_out,
- kin + pi, cur);
- kunmap_local(kin);
- }
- rq->outputsize -= cur;
- }
- for (; rq->outputsize; rq->pageofs_in = 0, cur += insz, ni++) {
- insz = min(PAGE_SIZE - rq->pageofs_in, rq->outputsize);
- rq->outputsize -= insz;
- if (!rq->in[ni])
- continue;
- kin = kmap_local_page(rq->in[ni]);
- pi = 0;
- do {
- no = (rq->pageofs_out + cur + pi) >> PAGE_SHIFT;
- po = (rq->pageofs_out + cur + pi) & ~PAGE_MASK;
- DBG_BUGON(no >= nrpages_out);
- cnt = min(insz - pi, PAGE_SIZE - po);
- if (rq->out[no] == rq->in[ni])
- memmove(kin + po,
- kin + rq->pageofs_in + pi, cnt);
- else if (rq->out[no])
- memcpy_to_page(rq->out[no], po,
- kin + rq->pageofs_in + pi, cnt);
- pi += cnt;
- } while (pi < insz);
- kunmap_local(kin);
- }
- DBG_BUGON(ni > nrpages_in);
- return NULL;
- }
- const char *z_erofs_stream_switch_bufs(struct z_erofs_stream_dctx *dctx,
- void **dst, void **src, struct page **pgpl)
- {
- struct z_erofs_decompress_req *rq = dctx->rq;
- struct page **pgo, *tmppage;
- unsigned int j;
- if (!dctx->avail_out) {
- if (++dctx->no >= rq->outpages || !rq->outputsize)
- return "insufficient space for decompressed data";
- if (dctx->kout)
- kunmap_local(dctx->kout);
- dctx->avail_out = min(rq->outputsize, PAGE_SIZE - rq->pageofs_out);
- rq->outputsize -= dctx->avail_out;
- pgo = &rq->out[dctx->no];
- if (!*pgo && rq->fillgaps) { /* deduped */
- *pgo = erofs_allocpage(pgpl, rq->gfp);
- if (!*pgo) {
- dctx->kout = NULL;
- return ERR_PTR(-ENOMEM);
- }
- set_page_private(*pgo, Z_EROFS_SHORTLIVED_PAGE);
- }
- if (*pgo) {
- dctx->kout = kmap_local_page(*pgo);
- *dst = dctx->kout + rq->pageofs_out;
- } else {
- *dst = dctx->kout = NULL;
- }
- rq->pageofs_out = 0;
- }
- if (dctx->inbuf_pos == dctx->inbuf_sz && rq->inputsize) {
- if (++dctx->ni >= rq->inpages)
- return "invalid compressed data";
- if (dctx->kout) /* unlike kmap(), take care of the orders */
- kunmap_local(dctx->kout);
- kunmap_local(dctx->kin);
- dctx->inbuf_sz = min_t(u32, rq->inputsize, PAGE_SIZE);
- rq->inputsize -= dctx->inbuf_sz;
- dctx->kin = kmap_local_page(rq->in[dctx->ni]);
- *src = dctx->kin;
- dctx->bounced = false;
- if (dctx->kout) {
- j = (u8 *)*dst - dctx->kout;
- dctx->kout = kmap_local_page(rq->out[dctx->no]);
- *dst = dctx->kout + j;
- }
- dctx->inbuf_pos = 0;
- }
- /*
- * Handle overlapping: Use the given bounce buffer if the input data is
- * under processing; Or utilize short-lived pages from the on-stack page
- * pool, where pages are shared among the same request. Note that only
- * a few inplace I/O pages need to be doubled.
- */
- if (!dctx->bounced && rq->out[dctx->no] == rq->in[dctx->ni]) {
- memcpy(dctx->bounce, *src, dctx->inbuf_sz);
- *src = dctx->bounce;
- dctx->bounced = true;
- }
- for (j = dctx->ni + 1; j < rq->inpages; ++j) {
- if (rq->out[dctx->no] != rq->in[j])
- continue;
- tmppage = erofs_allocpage(pgpl, rq->gfp);
- if (!tmppage)
- return ERR_PTR(-ENOMEM);
- set_page_private(tmppage, Z_EROFS_SHORTLIVED_PAGE);
- copy_highpage(tmppage, rq->in[j]);
- rq->in[j] = tmppage;
- }
- return NULL;
- }
- const struct z_erofs_decompressor *z_erofs_decomp[] = {
- [Z_EROFS_COMPRESSION_SHIFTED] = &(const struct z_erofs_decompressor) {
- .decompress = z_erofs_transform_plain,
- .name = "shifted"
- },
- [Z_EROFS_COMPRESSION_INTERLACED] = &(const struct z_erofs_decompressor) {
- .decompress = z_erofs_transform_plain,
- .name = "interlaced"
- },
- [Z_EROFS_COMPRESSION_LZ4] = &(const struct z_erofs_decompressor) {
- .config = z_erofs_load_lz4_config,
- .decompress = z_erofs_lz4_decompress,
- .init = z_erofs_gbuf_init,
- .exit = z_erofs_gbuf_exit,
- .name = "lz4"
- },
- #ifdef CONFIG_EROFS_FS_ZIP_LZMA
- [Z_EROFS_COMPRESSION_LZMA] = &z_erofs_lzma_decomp,
- #endif
- #ifdef CONFIG_EROFS_FS_ZIP_DEFLATE
- [Z_EROFS_COMPRESSION_DEFLATE] = &z_erofs_deflate_decomp,
- #endif
- #ifdef CONFIG_EROFS_FS_ZIP_ZSTD
- [Z_EROFS_COMPRESSION_ZSTD] = &z_erofs_zstd_decomp,
- #endif
- };
- int z_erofs_parse_cfgs(struct super_block *sb, struct erofs_super_block *dsb)
- {
- struct erofs_sb_info *sbi = EROFS_SB(sb);
- struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
- unsigned long algs, alg;
- erofs_off_t offset;
- int size, ret = 0;
- if (!erofs_sb_has_compr_cfgs(sbi))
- return z_erofs_load_lz4_config(sb, dsb, NULL, 0);
- algs = le16_to_cpu(dsb->u1.available_compr_algs);
- sbi->available_compr_algs = algs;
- if (algs & ~Z_EROFS_ALL_COMPR_ALGS) {
- erofs_err(sb, "unidentified algorithms %lx, please upgrade kernel",
- algs & ~Z_EROFS_ALL_COMPR_ALGS);
- return -EOPNOTSUPP;
- }
- (void)erofs_init_metabuf(&buf, sb, false);
- offset = EROFS_SUPER_OFFSET + sbi->sb_size;
- for_each_set_bit(alg, &algs, Z_EROFS_COMPRESSION_MAX) {
- const struct z_erofs_decompressor *dec = z_erofs_decomp[alg];
- void *data;
- data = erofs_read_metadata(sb, &buf, &offset, &size);
- if (IS_ERR(data)) {
- ret = PTR_ERR(data);
- break;
- }
- if (dec && dec->config) {
- ret = dec->config(sb, dsb, data, size);
- } else {
- erofs_err(sb, "algorithm %ld isn't enabled on this kernel",
- alg);
- ret = -EOPNOTSUPP;
- }
- kfree(data);
- if (ret)
- break;
- }
- erofs_put_metabuf(&buf);
- return ret;
- }
- int __init z_erofs_init_decompressor(void)
- {
- int i, err;
- for (i = 0; i < Z_EROFS_COMPRESSION_MAX; ++i) {
- err = z_erofs_decomp[i] ? z_erofs_decomp[i]->init() : 0;
- if (err) {
- while (i--)
- if (z_erofs_decomp[i])
- z_erofs_decomp[i]->exit();
- return err;
- }
- }
- return 0;
- }
- void z_erofs_exit_decompressor(void)
- {
- int i;
- for (i = 0; i < Z_EROFS_COMPRESSION_MAX; ++i)
- if (z_erofs_decomp[i])
- z_erofs_decomp[i]->exit();
- }
|