| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2016-present, Facebook, Inc. | |
| * All rights reserved. | |
| * | |
| */ | |
| #include <linux/bio.h> | |
| #include <linux/bitmap.h> | |
| #include <linux/err.h> | |
| #include <linux/init.h> | |
| #include <linux/kernel.h> | |
| #include <linux/mm.h> | |
| #include <linux/sched/mm.h> | |
| #include <linux/pagemap.h> | |
| #include <linux/refcount.h> | |
| #include <linux/sched.h> | |
| #include <linux/slab.h> | |
| #include <linux/zstd.h> | |
| #include "misc.h" | |
| #include "fs.h" | |
| #include "btrfs_inode.h" | |
| #include "compression.h" | |
| #include "super.h" | |
| #define ZSTD_BTRFS_MAX_WINDOWLOG 17 | |
| #define ZSTD_BTRFS_MAX_INPUT (1U << ZSTD_BTRFS_MAX_WINDOWLOG) | |
| #define ZSTD_BTRFS_DEFAULT_LEVEL 3 | |
| #define ZSTD_BTRFS_MIN_LEVEL -15 | |
| #define ZSTD_BTRFS_MAX_LEVEL 15 | |
| /* 307s to avoid pathologically clashing with transaction commit */ | |
| #define ZSTD_BTRFS_RECLAIM_JIFFIES (307 * HZ) | |
| static zstd_parameters zstd_get_btrfs_parameters(int level, | |
| size_t src_len) | |
| { | |
| zstd_parameters params = zstd_get_params(level, src_len); | |
| if (params.cParams.windowLog > ZSTD_BTRFS_MAX_WINDOWLOG) | |
| params.cParams.windowLog = ZSTD_BTRFS_MAX_WINDOWLOG; | |
| WARN_ON(src_len > ZSTD_BTRFS_MAX_INPUT); | |
| return params; | |
| } | |
| struct workspace { | |
| void *mem; | |
| size_t size; | |
| char *buf; | |
| int level; | |
| int req_level; | |
| unsigned long last_used; /* jiffies */ | |
| struct list_head list; | |
| struct list_head lru_list; | |
| zstd_in_buffer in_buf; | |
| zstd_out_buffer out_buf; | |
| zstd_parameters params; | |
| }; | |
| /* | |
| * Zstd Workspace Management | |
| * | |
| * Zstd workspaces have different memory requirements depending on the level. | |
| * The zstd workspaces are managed by having individual lists for each level | |
| * and a global lru. Forward progress is maintained by protecting a max level | |
| * workspace. | |
| * | |
| * Getting a workspace is done by using the bitmap to identify the levels that | |
| * have available workspaces and scans up. This lets us recycle higher level | |
| * workspaces because of the monotonic memory guarantee. A workspace's | |
| * last_used is only updated if it is being used by the corresponding memory | |
| * level. Putting a workspace involves adding it back to the appropriate places | |
| * and adding it back to the lru if necessary. | |
| * | |
| * A timer is used to reclaim workspaces if they have not been used for | |
| * ZSTD_BTRFS_RECLAIM_JIFFIES. This helps keep only active workspaces around. | |
| * The upper bound is provided by the workqueue limit which is 2 (percpu limit). | |
| */ | |
| struct zstd_workspace_manager { | |
| spinlock_t lock; | |
| struct list_head lru_list; | |
| struct list_head idle_ws[ZSTD_BTRFS_MAX_LEVEL]; | |
| unsigned long active_map; | |
| wait_queue_head_t wait; | |
| struct timer_list timer; | |
| }; | |
| static size_t zstd_ws_mem_sizes[ZSTD_BTRFS_MAX_LEVEL]; | |
| static inline struct workspace *list_to_workspace(struct list_head *list) | |
| { | |
| return container_of(list, struct workspace, list); | |
| } | |
| static inline int clip_level(int level) | |
| { | |
| return max(0, level - 1); | |
| } | |
| /* | |
| * Timer callback to free unused workspaces. | |
| * | |
| * @t: timer | |
| * | |
| * This scans the lru_list and attempts to reclaim any workspace that hasn't | |
| * been used for ZSTD_BTRFS_RECLAIM_JIFFIES. | |
| * | |
| * The context is softirq and does not need the _bh locking primitives. | |
| */ | |
| static void zstd_reclaim_timer_fn(struct timer_list *timer) | |
| { | |
| struct zstd_workspace_manager *zwsm = | |
| container_of(timer, struct zstd_workspace_manager, timer); | |
| unsigned long reclaim_threshold = jiffies - ZSTD_BTRFS_RECLAIM_JIFFIES; | |
| struct list_head *pos, *next; | |
| spin_lock(&zwsm->lock); | |
| if (list_empty(&zwsm->lru_list)) { | |
| spin_unlock(&zwsm->lock); | |
| return; | |
| } | |
| list_for_each_prev_safe(pos, next, &zwsm->lru_list) { | |
| struct workspace *victim = container_of(pos, struct workspace, | |
| lru_list); | |
| int level; | |
| if (time_after(victim->last_used, reclaim_threshold)) | |
| break; | |
| /* workspace is in use */ | |
| if (victim->req_level) | |
| continue; | |
| level = victim->level; | |
| list_del(&victim->lru_list); | |
| list_del(&victim->list); | |
| zstd_free_workspace(&victim->list); | |
| if (list_empty(&zwsm->idle_ws[level])) | |
| clear_bit(level, &zwsm->active_map); | |
| } | |
| if (!list_empty(&zwsm->lru_list)) | |
| mod_timer(&zwsm->timer, jiffies + ZSTD_BTRFS_RECLAIM_JIFFIES); | |
| spin_unlock(&zwsm->lock); | |
| } | |
| /* | |
| * Calculate monotonic memory bounds. | |
| * | |
| * It is possible based on the level configurations that a higher level | |
| * workspace uses less memory than a lower level workspace. In order to reuse | |
| * workspaces, this must be made a monotonic relationship. This precomputes | |
| * the required memory for each level and enforces the monotonicity between | |
| * level and memory required. | |
| */ | |
| static void zstd_calc_ws_mem_sizes(void) | |
| { | |
| size_t max_size = 0; | |
| int level; | |
| for (level = ZSTD_BTRFS_MIN_LEVEL; level <= ZSTD_BTRFS_MAX_LEVEL; level++) { | |
| if (level == 0) | |
| continue; | |
| zstd_parameters params = | |
| zstd_get_btrfs_parameters(level, ZSTD_BTRFS_MAX_INPUT); | |
| size_t level_size = | |
| max_t(size_t, | |
| zstd_cstream_workspace_bound(¶ms.cParams), | |
| zstd_dstream_workspace_bound(ZSTD_BTRFS_MAX_INPUT)); | |
| max_size = max_t(size_t, max_size, level_size); | |
| /* Use level 1 workspace size for all the fast mode negative levels. */ | |
| zstd_ws_mem_sizes[clip_level(level)] = max_size; | |
| } | |
| } | |
| int zstd_alloc_workspace_manager(struct btrfs_fs_info *fs_info) | |
| { | |
| struct zstd_workspace_manager *zwsm; | |
| struct list_head *ws; | |
| ASSERT(fs_info->compr_wsm[BTRFS_COMPRESS_ZSTD] == NULL); | |
| zwsm = kzalloc_obj(*zwsm); | |
| if (!zwsm) | |
| return -ENOMEM; | |
| zstd_calc_ws_mem_sizes(); | |
| spin_lock_init(&zwsm->lock); | |
| init_waitqueue_head(&zwsm->wait); | |
| timer_setup(&zwsm->timer, zstd_reclaim_timer_fn, 0); | |
| INIT_LIST_HEAD(&zwsm->lru_list); | |
| for (int i = 0; i < ZSTD_BTRFS_MAX_LEVEL; i++) | |
| INIT_LIST_HEAD(&zwsm->idle_ws[i]); | |
| fs_info->compr_wsm[BTRFS_COMPRESS_ZSTD] = zwsm; | |
| ws = zstd_alloc_workspace(fs_info, ZSTD_BTRFS_MAX_LEVEL); | |
| if (IS_ERR(ws)) { | |
| btrfs_warn(NULL, "cannot preallocate zstd compression workspace"); | |
| } else { | |
| set_bit(ZSTD_BTRFS_MAX_LEVEL - 1, &zwsm->active_map); | |
| list_add(ws, &zwsm->idle_ws[ZSTD_BTRFS_MAX_LEVEL - 1]); | |
| } | |
| return 0; | |
| } | |
| void zstd_free_workspace_manager(struct btrfs_fs_info *fs_info) | |
| { | |
| struct zstd_workspace_manager *zwsm = fs_info->compr_wsm[BTRFS_COMPRESS_ZSTD]; | |
| struct workspace *workspace; | |
| if (!zwsm) | |
| return; | |
| fs_info->compr_wsm[BTRFS_COMPRESS_ZSTD] = NULL; | |
| spin_lock_bh(&zwsm->lock); | |
| for (int i = 0; i < ZSTD_BTRFS_MAX_LEVEL; i++) { | |
| while (!list_empty(&zwsm->idle_ws[i])) { | |
| workspace = container_of(zwsm->idle_ws[i].next, | |
| struct workspace, list); | |
| list_del(&workspace->list); | |
| list_del(&workspace->lru_list); | |
| zstd_free_workspace(&workspace->list); | |
| } | |
| } | |
| spin_unlock_bh(&zwsm->lock); | |
| timer_delete_sync(&zwsm->timer); | |
| kfree(zwsm); | |
| } | |
| /* | |
| * Find workspace for given level. | |
| * | |
| * @level: compression level | |
| * | |
| * This iterates over the set bits in the active_map beginning at the requested | |
| * compression level. This lets us utilize already allocated workspaces before | |
| * allocating a new one. If the workspace is of a larger size, it is used, but | |
| * the place in the lru_list and last_used times are not updated. This is to | |
| * offer the opportunity to reclaim the workspace in favor of allocating an | |
| * appropriately sized one in the future. | |
| */ | |
| static struct list_head *zstd_find_workspace(struct btrfs_fs_info *fs_info, int level) | |
| { | |
| struct zstd_workspace_manager *zwsm = fs_info->compr_wsm[BTRFS_COMPRESS_ZSTD]; | |
| struct list_head *ws; | |
| struct workspace *workspace; | |
| int i = clip_level(level); | |
| ASSERT(zwsm); | |
| spin_lock_bh(&zwsm->lock); | |
| for_each_set_bit_from(i, &zwsm->active_map, ZSTD_BTRFS_MAX_LEVEL) { | |
| if (!list_empty(&zwsm->idle_ws[i])) { | |
| ws = zwsm->idle_ws[i].next; | |
| workspace = list_to_workspace(ws); | |
| list_del_init(ws); | |
| /* keep its place if it's a lower level using this */ | |
| workspace->req_level = level; | |
| if (clip_level(level) == workspace->level) | |
| list_del(&workspace->lru_list); | |
| if (list_empty(&zwsm->idle_ws[i])) | |
| clear_bit(i, &zwsm->active_map); | |
| spin_unlock_bh(&zwsm->lock); | |
| return ws; | |
| } | |
| } | |
| spin_unlock_bh(&zwsm->lock); | |
| return NULL; | |
| } | |
| /* | |
| * Zstd get_workspace for level. | |
| * | |
| * @level: compression level | |
| * | |
| * If @level is 0, then any compression level can be used. Therefore, we begin | |
| * scanning from 1. We first scan through possible workspaces and then after | |
| * attempt to allocate a new workspace. If we fail to allocate one due to | |
| * memory pressure, go to sleep waiting for the max level workspace to free up. | |
| */ | |
| struct list_head *zstd_get_workspace(struct btrfs_fs_info *fs_info, int level) | |
| { | |
| struct zstd_workspace_manager *zwsm = fs_info->compr_wsm[BTRFS_COMPRESS_ZSTD]; | |
| struct list_head *ws; | |
| unsigned int nofs_flag; | |
| ASSERT(zwsm); | |
| /* level == 0 means we can use any workspace */ | |
| if (!level) | |
| level = 1; | |
| again: | |
| ws = zstd_find_workspace(fs_info, level); | |
| if (ws) | |
| return ws; | |
| nofs_flag = memalloc_nofs_save(); | |
| ws = zstd_alloc_workspace(fs_info, level); | |
| memalloc_nofs_restore(nofs_flag); | |
| if (IS_ERR(ws)) { | |
| DEFINE_WAIT(wait); | |
| prepare_to_wait(&zwsm->wait, &wait, TASK_UNINTERRUPTIBLE); | |
| /* | |
| * Re-check after being queued: zstd_put_workspace() only wakes | |
| * a queue that already has a sleeper, so a workspace returned | |
| * since the failed allocation woke nobody. | |
| */ | |
| ws = zstd_find_workspace(fs_info, level); | |
| if (!ws) | |
| schedule(); | |
| finish_wait(&zwsm->wait, &wait); | |
| if (ws) | |
| return ws; | |
| goto again; | |
| } | |
| return ws; | |
| } | |
| /* | |
| * Zstd put_workspace. | |
| * | |
| * @ws: list_head for the workspace | |
| * | |
| * When putting back a workspace, we only need to update the LRU if we are of | |
| * the requested compression level. Here is where we continue to protect the | |
| * max level workspace or update last_used accordingly. If the reclaim timer | |
| * isn't set, it is also set here. Only the max level workspace tries and wakes | |
| * up waiting workspaces. | |
| */ | |
| void zstd_put_workspace(struct btrfs_fs_info *fs_info, struct list_head *ws) | |
| { | |
| struct zstd_workspace_manager *zwsm = fs_info->compr_wsm[BTRFS_COMPRESS_ZSTD]; | |
| struct workspace *workspace = list_to_workspace(ws); | |
| ASSERT(zwsm); | |
| spin_lock_bh(&zwsm->lock); | |
| /* A node is only taken off the lru if we are the corresponding level */ | |
| if (clip_level(workspace->req_level) == workspace->level) { | |
| /* Hide a max level workspace from reclaim */ | |
| if (list_empty(&zwsm->idle_ws[ZSTD_BTRFS_MAX_LEVEL - 1])) { | |
| INIT_LIST_HEAD(&workspace->lru_list); | |
| } else { | |
| workspace->last_used = jiffies; | |
| list_add(&workspace->lru_list, &zwsm->lru_list); | |
| if (!timer_pending(&zwsm->timer)) | |
| mod_timer(&zwsm->timer, | |
| jiffies + ZSTD_BTRFS_RECLAIM_JIFFIES); | |
| } | |
| } | |
| set_bit(workspace->level, &zwsm->active_map); | |
| list_add(&workspace->list, &zwsm->idle_ws[workspace->level]); | |
| workspace->req_level = 0; | |
| spin_unlock_bh(&zwsm->lock); | |
| if (workspace->level == clip_level(ZSTD_BTRFS_MAX_LEVEL)) | |
| cond_wake_up(&zwsm->wait); | |
| } | |
| void zstd_free_workspace(struct list_head *ws) | |
| { | |
| struct workspace *workspace = list_entry(ws, struct workspace, list); | |
| kvfree(workspace->mem); | |
| kfree(workspace->buf); | |
| kfree(workspace); | |
| } | |
| struct list_head *zstd_alloc_workspace(struct btrfs_fs_info *fs_info, int level) | |
| { | |
| struct workspace *workspace; | |
| workspace = kzalloc_obj(*workspace); | |
| if (!workspace) | |
| return ERR_PTR(-ENOMEM); | |
| /* Use level 1 workspace size for all the fast mode negative levels. */ | |
| workspace->size = zstd_ws_mem_sizes[clip_level(level)]; | |
| workspace->level = clip_level(level); | |
| workspace->req_level = level; | |
| workspace->last_used = jiffies; | |
| workspace->mem = kvmalloc(workspace->size, GFP_KERNEL | __GFP_NOWARN); | |
| workspace->buf = kmalloc(fs_info->sectorsize, GFP_KERNEL); | |
| if (!workspace->mem || !workspace->buf) | |
| goto fail; | |
| INIT_LIST_HEAD(&workspace->list); | |
| INIT_LIST_HEAD(&workspace->lru_list); | |
| return &workspace->list; | |
| fail: | |
| zstd_free_workspace(&workspace->list); | |
| return ERR_PTR(-ENOMEM); | |
| } | |
| int zstd_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; | |
| zstd_cstream *stream; | |
| int ret = 0; | |
| /* The current folio to read. */ | |
| struct folio *in_folio = NULL; | |
| /* The current folio to write to. */ | |
| struct folio *out_folio = NULL; | |
| unsigned long tot_in = 0; | |
| unsigned long tot_out = 0; | |
| const u64 start = cb->start; | |
| const u32 len = cb->len; | |
| const u64 end = start + len; | |
| const u32 min_folio_size = btrfs_min_folio_size(fs_info); | |
| workspace->params = zstd_get_btrfs_parameters(workspace->req_level, len); | |
| /* Initialize the stream. */ | |
| stream = zstd_init_cstream(&workspace->params, len, workspace->mem, workspace->size); | |
| if (unlikely(!stream)) { | |
| btrfs_err(fs_info, | |
| "zstd compression init level %d failed, root %llu inode %llu offset %llu", | |
| workspace->req_level, btrfs_root_id(inode->root), | |
| btrfs_ino(inode), start); | |
| ret = -EIO; | |
| goto out; | |
| } | |
| /* Map in the first page of input data. */ | |
| ret = btrfs_compress_filemap_get_folio(mapping, start, &in_folio); | |
| if (ret < 0) | |
| goto out; | |
| workspace->in_buf.src = kmap_local_folio(in_folio, offset_in_folio(in_folio, start)); | |
| workspace->in_buf.pos = 0; | |
| workspace->in_buf.size = btrfs_calc_input_length(in_folio, end, start); | |
| /* Allocate and map in the output buffer. */ | |
| out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS); | |
| if (out_folio == NULL) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| workspace->out_buf.dst = folio_address(out_folio); | |
| workspace->out_buf.pos = 0; | |
| workspace->out_buf.size = min_folio_size; | |
| while (1) { | |
| size_t ret2; | |
| ret2 = zstd_compress_stream(stream, &workspace->out_buf, &workspace->in_buf); | |
| if (unlikely(zstd_is_error(ret2))) { | |
| btrfs_warn(fs_info, | |
| "zstd compression level %d failed, error %d root %llu inode %llu offset %llu", | |
| workspace->req_level, zstd_get_error_code(ret2), | |
| btrfs_root_id(inode->root), btrfs_ino(inode), | |
| start + tot_in); | |
| ret = -EIO; | |
| goto out; | |
| } | |
| /* Check to see if we are making it bigger. */ | |
| if (tot_in + workspace->in_buf.pos > fs_info->sectorsize * 2 && | |
| tot_in + workspace->in_buf.pos < tot_out + workspace->out_buf.pos) { | |
| ret = -E2BIG; | |
| goto out; | |
| } | |
| /* Check if we need more output space. */ | |
| if (workspace->out_buf.pos >= workspace->out_buf.size) { | |
| tot_out += min_folio_size; | |
| if (tot_out >= len) { | |
| ret = -E2BIG; | |
| goto out; | |
| } | |
| /* Queue the current foliot into the bio. */ | |
| if (!bio_add_folio(bio, out_folio, folio_size(out_folio), 0)) { | |
| ret = -E2BIG; | |
| goto out; | |
| } | |
| out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS); | |
| if (out_folio == NULL) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| workspace->out_buf.dst = folio_address(out_folio); | |
| workspace->out_buf.pos = 0; | |
| workspace->out_buf.size = min_folio_size; | |
| } | |
| /* We've reached the end of the input. */ | |
| if (tot_in + workspace->in_buf.pos >= len) { | |
| tot_in += workspace->in_buf.pos; | |
| break; | |
| } | |
| /* Check if we need more input. */ | |
| if (workspace->in_buf.pos >= workspace->in_buf.size) { | |
| u64 cur; | |
| tot_in += workspace->in_buf.size; | |
| cur = start + tot_in; | |
| kunmap_local(workspace->in_buf.src); | |
| workspace->in_buf.src = NULL; | |
| folio_put(in_folio); | |
| ret = btrfs_compress_filemap_get_folio(mapping, cur, &in_folio); | |
| if (ret < 0) | |
| goto out; | |
| workspace->in_buf.src = kmap_local_folio(in_folio, | |
| offset_in_folio(in_folio, cur)); | |
| workspace->in_buf.pos = 0; | |
| workspace->in_buf.size = btrfs_calc_input_length(in_folio, end, cur); | |
| } | |
| } | |
| while (1) { | |
| size_t ret2; | |
| ret2 = zstd_end_stream(stream, &workspace->out_buf); | |
| if (unlikely(zstd_is_error(ret2))) { | |
| btrfs_err(fs_info, | |
| "zstd compression end level %d failed, error %d root %llu inode %llu offset %llu", | |
| workspace->req_level, zstd_get_error_code(ret2), | |
| btrfs_root_id(inode->root), btrfs_ino(inode), | |
| start + tot_in); | |
| ret = -EIO; | |
| goto out; | |
| } | |
| /* Queue the remaining part of the output folio into bio. */ | |
| if (ret2 == 0) { | |
| tot_out += workspace->out_buf.pos; | |
| if (tot_out >= len) { | |
| ret = -E2BIG; | |
| goto out; | |
| } | |
| if (!bio_add_folio(bio, out_folio, workspace->out_buf.pos, 0)) { | |
| ret = -E2BIG; | |
| goto out; | |
| } | |
| out_folio = NULL; | |
| break; | |
| } | |
| tot_out += min_folio_size; | |
| if (tot_out >= len) { | |
| ret = -E2BIG; | |
| goto out; | |
| } | |
| if (!bio_add_folio(bio, out_folio, folio_size(out_folio), 0)) { | |
| ret = -E2BIG; | |
| goto out; | |
| } | |
| out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS); | |
| if (out_folio == NULL) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| workspace->out_buf.dst = folio_address(out_folio); | |
| workspace->out_buf.pos = 0; | |
| workspace->out_buf.size = min_folio_size; | |
| } | |
| if (tot_out >= tot_in) { | |
| ret = -E2BIG; | |
| goto out; | |
| } | |
| ret = 0; | |
| ASSERT(tot_out == bio->bi_iter.bi_size); | |
| out: | |
| if (out_folio) | |
| btrfs_free_compr_folio(out_folio); | |
| if (workspace->in_buf.src) { | |
| kunmap_local(workspace->in_buf.src); | |
| folio_put(in_folio); | |
| } | |
| return ret; | |
| } | |
| int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) | |
| { | |
| struct btrfs_fs_info *fs_info = cb_to_fs_info(cb); | |
| struct workspace *workspace = list_entry(ws, struct workspace, list); | |
| struct folio_iter fi; | |
| size_t srclen = bio_get_size(&cb->bbio.bio); | |
| zstd_dstream *stream; | |
| int ret = 0; | |
| const unsigned int min_folio_size = btrfs_min_folio_size(fs_info); | |
| unsigned long folio_in_index = 0; | |
| unsigned long total_folios_in = DIV_ROUND_UP(srclen, min_folio_size); | |
| unsigned long buf_start; | |
| unsigned long total_out = 0; | |
| bio_first_folio(&fi, &cb->bbio.bio, 0); | |
| if (unlikely(!fi.folio)) | |
| return -EINVAL; | |
| ASSERT(folio_size(fi.folio) == min_folio_size); | |
| stream = zstd_init_dstream( | |
| ZSTD_BTRFS_MAX_INPUT, workspace->mem, workspace->size); | |
| if (unlikely(!stream)) { | |
| struct btrfs_inode *inode = cb->bbio.inode; | |
| btrfs_err(inode->root->fs_info, | |
| "zstd decompression init failed, root %llu inode %llu offset %llu", | |
| btrfs_root_id(inode->root), btrfs_ino(inode), cb->start); | |
| ret = -EIO; | |
| goto done; | |
| } | |
| workspace->in_buf.src = kmap_local_folio(fi.folio, 0); | |
| workspace->in_buf.pos = 0; | |
| workspace->in_buf.size = min_t(size_t, srclen, min_folio_size); | |
| workspace->out_buf.dst = workspace->buf; | |
| workspace->out_buf.pos = 0; | |
| workspace->out_buf.size = fs_info->sectorsize; | |
| while (1) { | |
| size_t ret2; | |
| ret2 = zstd_decompress_stream(stream, &workspace->out_buf, | |
| &workspace->in_buf); | |
| if (unlikely(zstd_is_error(ret2))) { | |
| struct btrfs_inode *inode = cb->bbio.inode; | |
| btrfs_err(inode->root->fs_info, | |
| "zstd decompression failed, error %d root %llu inode %llu offset %llu", | |
| zstd_get_error_code(ret2), btrfs_root_id(inode->root), | |
| btrfs_ino(inode), cb->start); | |
| ret = -EIO; | |
| goto done; | |
| } | |
| buf_start = total_out; | |
| total_out += workspace->out_buf.pos; | |
| workspace->out_buf.pos = 0; | |
| ret = btrfs_decompress_buf2page(workspace->out_buf.dst, | |
| total_out - buf_start, cb, buf_start); | |
| if (ret == 0) | |
| break; | |
| if (workspace->in_buf.pos >= srclen) | |
| break; | |
| /* Check if we've hit the end of a frame */ | |
| if (ret2 == 0) | |
| break; | |
| if (workspace->in_buf.pos == workspace->in_buf.size) { | |
| kunmap_local(workspace->in_buf.src); | |
| folio_in_index++; | |
| if (unlikely(folio_in_index >= total_folios_in)) { | |
| workspace->in_buf.src = NULL; | |
| ret = -EIO; | |
| goto done; | |
| } | |
| srclen -= min_folio_size; | |
| bio_next_folio(&fi, &cb->bbio.bio); | |
| ASSERT(fi.folio); | |
| workspace->in_buf.src = kmap_local_folio(fi.folio, 0); | |
| workspace->in_buf.pos = 0; | |
| workspace->in_buf.size = min_t(size_t, srclen, min_folio_size); | |
| } | |
| } | |
| ret = 0; | |
| done: | |
| if (workspace->in_buf.src) | |
| kunmap_local(workspace->in_buf.src); | |
| return ret; | |
| } | |
| int zstd_decompress(struct list_head *ws, const u8 *data_in, | |
| struct folio *dest_folio, unsigned long dest_pgoff, size_t srclen, | |
| size_t destlen) | |
| { | |
| struct workspace *workspace = list_entry(ws, struct workspace, list); | |
| struct btrfs_fs_info *fs_info = btrfs_sb(folio_inode(dest_folio)->i_sb); | |
| zstd_dstream *stream; | |
| int ret = 0; | |
| unsigned long to_copy = 0; | |
| stream = zstd_init_dstream( | |
| ZSTD_BTRFS_MAX_INPUT, workspace->mem, workspace->size); | |
| if (unlikely(!stream)) { | |
| struct btrfs_inode *inode = folio_to_inode(dest_folio); | |
| btrfs_err(inode->root->fs_info, | |
| "zstd decompression init failed, root %llu inode %llu offset %llu", | |
| btrfs_root_id(inode->root), btrfs_ino(inode), | |
| folio_pos(dest_folio)); | |
| ret = -EIO; | |
| goto finish; | |
| } | |
| workspace->in_buf.src = data_in; | |
| workspace->in_buf.pos = 0; | |
| workspace->in_buf.size = srclen; | |
| workspace->out_buf.dst = workspace->buf; | |
| workspace->out_buf.pos = 0; | |
| workspace->out_buf.size = fs_info->sectorsize; | |
| /* | |
| * Since both input and output buffers should not exceed one sector, | |
| * one call should end the decompression. | |
| */ | |
| ret = zstd_decompress_stream(stream, &workspace->out_buf, &workspace->in_buf); | |
| if (unlikely(zstd_is_error(ret))) { | |
| struct btrfs_inode *inode = folio_to_inode(dest_folio); | |
| btrfs_err(inode->root->fs_info, | |
| "zstd decompression failed, error %d root %llu inode %llu offset %llu", | |
| zstd_get_error_code(ret), btrfs_root_id(inode->root), | |
| btrfs_ino(inode), folio_pos(dest_folio)); | |
| goto finish; | |
| } | |
| to_copy = workspace->out_buf.pos; | |
| memcpy_to_folio(dest_folio, dest_pgoff, workspace->out_buf.dst, to_copy); | |
| finish: | |
| /* Error or early end. */ | |
| if (unlikely(to_copy < destlen)) { | |
| ret = -EIO; | |
| folio_zero_range(dest_folio, dest_pgoff + to_copy, destlen - to_copy); | |
| } | |
| return ret; | |
| } | |
| const struct btrfs_compress_levels btrfs_zstd_compress = { | |
| .min_level = ZSTD_BTRFS_MIN_LEVEL, | |
| .max_level = ZSTD_BTRFS_MAX_LEVEL, | |
| .default_level = ZSTD_BTRFS_DEFAULT_LEVEL, | |
| }; |