linux/fs/ntfs/wof.c
Zhan Xusheng 41a52ba4a5 ntfs: read WOF chunks outside the decompression lock
WOF decompression uses four module-global workspaces, one per compression
format, each with a static mutex.  ntfs_read_wof_compressed_block() takes
that mutex once and holds it across the whole chunk loop, so both block
reads run inside it:

  mutex_lock(ws->lock);
  for each chunk {
          parse_wof_chunk_table(..., ws->input, ...);   /* reads disk */
          ntfs_read_wof_chunk(..., ws->input, ...);     /* reads disk */
          decompress into ws->output;
  }
  mutex_unlock(ws->lock);

Readers of system-compressed files then serialise system-wide on the disk
waits, not just on the decompressor scratch the lock exists for.  One
reader sleeping in submit_bio_wait() blocks all the rest.

The waits dominate.  Reading an 8 MiB xpress4k file (2048 chunks at a 48%
compressed ratio, so 2048 acquisitions and 4096 block reads) and timing
ws->lock against the part of it spent in ntfs_bdev_read():

  backing store                held     of that in I/O   held after
  virtio, host page cache      348 ms   321 ms  (92%)    24.6 ms
  virtio, throttled 100 MB/s   978 ms   948 ms  (96%)    36.6 ms

The page-cache row is a lower bound, having no seek cost at all, and the
share still grows with slower storage because only the wait scales while
decompression stays near 26 ms.

The reads are inside the lock only because they land in ws->input, a
buffer shared through the workspace.  Nothing else requires it:
parse_wof_chunk_table() and ntfs_read_wof_chunk() already take the buffer
as a parameter and both set *chunk_mem to a pointer inside it, so a
caller-owned buffer works unchanged.

Allocate that buffer per call, do both reads without the lock, and take
the lock only around decompression, which is the step needing ws->output
and ws->scratch.  squashfs is arranged this way already: its
squashfs_decompress() is handed a bio that has been read, and locks only
for the CPU work.

Block reads are unchanged in number, they just no longer run under the
lock, and hold time stops tracking device speed.

This also unnests two per-inode locks from the global one, runlist->lock
taken by both reads and base_ni->mrec_lock taken for a resident stream.
A resident chunk needs no I/O at all, yet used to queue behind a reader
blocked in submit_bio_wait() and then take mrec_lock inside the global
mutex.

The buffer is 4608 bytes for xpress4k and at most 33280 for lzx32k.  This
path already does GFP_NOFS allocations per call in ntfs_attr_iget(), and
in ntfs_attr_get_search_ctx() for a resident stream, so one more does not
change how it behaves under memory pressure.  The workspace keeps output
and scratch, 4 KiB to 32 KiB and 6224 bytes (xpress) or 10240 (lzx), and
its "already allocated" test moves from ws->input to ws->output.

The lock is now taken per chunk rather than per call, which differs only
for a folio spanning several chunks: a few more uncontended mutex
operations in exchange for not holding it across the reads between them.

Verified under QEMU against an uncompressed copy of the same data, on an
8 MiB file and a 100000 byte one, the latter covering the tail chunk that
is not a full comp_unit.

Signed-off-by: Zhan Xusheng <zhanxusheng@xiaomi.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-30 11:04:29 +09:00

758 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Windows System Compression (WOF) decompression glue.
*
* Copyright (c) 2026 LG Electronics Co., Ltd.
*/
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/overflow.h>
#include <linux/pagemap.h>
#include <linux/sched/mm.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
#include <linux/vmalloc.h>
#include "ntfs.h"
#include "inode.h"
#include "debug.h"
#include "ntfs_codec.h"
#include "attrib.h"
static const __le16 WOF_NAME[] = {
cpu_to_le16('W'), cpu_to_le16('o'), cpu_to_le16('f'),
cpu_to_le16('C'), cpu_to_le16('o'), cpu_to_le16('m'),
cpu_to_le16('p'), cpu_to_le16('r'), cpu_to_le16('e'),
cpu_to_le16('s'), cpu_to_le16('s'), cpu_to_le16('e'),
cpu_to_le16('d'), cpu_to_le16('D'), cpu_to_le16('a'),
cpu_to_le16('t'), cpu_to_le16('a'),
};
#define WOF_NAME_LEN 17
#define NTFS_WOF_MAX_COMP_UNIT (1U << 15)
#define NTFS_WOF_MAX_PAGES \
DIV_ROUND_UP(NTFS_WOF_MAX_COMP_UNIT + PAGE_SIZE - 1, PAGE_SIZE)
struct ntfs_wof_workspace {
struct mutex *lock;
const struct ntfs_codec_ops *codec;
u32 comp_unit;
void *output;
void *scratch;
};
static DEFINE_MUTEX(ntfs_wof_xpress4k_lock);
static DEFINE_MUTEX(ntfs_wof_xpress8k_lock);
static DEFINE_MUTEX(ntfs_wof_xpress16k_lock);
static DEFINE_MUTEX(ntfs_wof_lzx32k_lock);
static struct ntfs_wof_workspace ntfs_wof_xpress4k_workspace = {
.lock = &ntfs_wof_xpress4k_lock,
.codec = &ntfs_xpress4k_codec_ops,
.comp_unit = 1U << 12,
};
static struct ntfs_wof_workspace ntfs_wof_xpress8k_workspace = {
.lock = &ntfs_wof_xpress8k_lock,
.codec = &ntfs_xpress8k_codec_ops,
.comp_unit = 1U << 13,
};
static struct ntfs_wof_workspace ntfs_wof_xpress16k_workspace = {
.lock = &ntfs_wof_xpress16k_lock,
.codec = &ntfs_xpress16k_codec_ops,
.comp_unit = 1U << 14,
};
static struct ntfs_wof_workspace ntfs_wof_lzx32k_workspace = {
.lock = &ntfs_wof_lzx32k_lock,
.codec = &ntfs_lzx32k_codec_ops,
.comp_unit = 1U << 15,
};
static struct ntfs_wof_workspace *const ntfs_wof_workspaces[] = {
&ntfs_wof_xpress4k_workspace,
&ntfs_wof_xpress8k_workspace,
&ntfs_wof_xpress16k_workspace,
&ntfs_wof_lzx32k_workspace,
};
static struct ntfs_wof_workspace *ntfs_wof_workspace(u8 block_size_bits)
{
switch (block_size_bits) {
case 12:
return &ntfs_wof_xpress4k_workspace;
case 13:
return &ntfs_wof_xpress8k_workspace;
case 14:
return &ntfs_wof_xpress16k_workspace;
case 15:
return &ntfs_wof_lzx32k_workspace;
default:
return NULL;
}
}
/*
* Size of the buffer a chunk is read into. A chunk is read straight off the
* device, so the buffer has to hold @comp_unit bytes plus the leading partial
* sector.
*/
static size_t ntfs_wof_input_size(const struct ntfs_wof_workspace *ws)
{
return round_up((size_t)ws->comp_unit + 511, 512);
}
static int ntfs_wof_workspace_prepare(struct ntfs_wof_workspace *ws)
{
void *output, *scratch;
size_t scratch_size;
if (ws->output)
return 0;
scratch_size = ws->codec->scratch_size(ws->comp_unit);
if (!scratch_size)
return -EINVAL;
output = kvmalloc(ws->comp_unit, GFP_NOFS);
scratch = kvzalloc(scratch_size, GFP_NOFS);
if (!output || !scratch) {
kvfree(output);
kvfree(scratch);
return -ENOMEM;
}
ws->output = output;
ws->scratch = scratch;
return 0;
}
void ntfs_wof_free_workspaces(void)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(ntfs_wof_workspaces); i++) {
struct ntfs_wof_workspace *ws = ntfs_wof_workspaces[i];
mutex_lock(ws->lock);
kvfree(ws->output);
kvfree(ws->scratch);
ws->output = NULL;
ws->scratch = NULL;
mutex_unlock(ws->lock);
}
}
static int ntfs_bdev_read_from_rl(struct ntfs_volume *vol,
struct runlist *runlist,
sector_t start_sector, u64 sector_count,
void *buf)
{
struct runlist_element *rl;
u32 sec_per_clu_bits;
s64 vcn;
u64 sec_off;
size_t buf_off = 0;
unsigned int nofs_flags;
int err;
if (vol->cluster_size_bits < 9)
return -EINVAL;
sec_per_clu_bits = vol->cluster_size_bits - 9;
vcn = start_sector >> sec_per_clu_bits;
sec_off = start_sector & ((1ULL << sec_per_clu_bits) - 1);
nofs_flags = memalloc_nofs_save();
down_read(&runlist->lock);
if (!runlist->rl) {
err = -EINVAL;
goto out_unlock;
}
rl = __ntfs_attr_find_vcn_nolock(runlist, vcn);
if (IS_ERR(rl)) {
err = PTR_ERR(rl);
goto out_unlock;
}
while (sector_count > 0) {
s64 lcn;
s64 rl_end;
u64 byte_off, byte_len, sectors, available;
if (rl->length <= 0 || vcn < rl->vcn) {
err = -EINVAL;
goto out_unlock;
}
lcn = ntfs_rl_vcn_to_lcn(rl, vcn);
if (lcn < 0 && lcn != LCN_HOLE) {
err = -EINVAL;
goto out_unlock;
}
if (check_add_overflow(rl->vcn, rl->length, &rl_end) ||
rl_end <= vcn ||
(u64)(rl_end - vcn) > (U64_MAX >> sec_per_clu_bits)) {
err = -EOVERFLOW;
goto out_unlock;
}
available = (u64)(rl_end - vcn) << sec_per_clu_bits;
if (available <= sec_off) {
err = -EINVAL;
goto out_unlock;
}
available -= sec_off;
sectors = min_t(u64, sector_count, available);
if (check_mul_overflow(sectors, (u64)SECTOR_SIZE, &byte_len) ||
byte_len > SIZE_MAX - buf_off) {
err = -EOVERFLOW;
goto out_unlock;
}
if (lcn == LCN_HOLE) {
memset((u8 *)buf + buf_off, 0, byte_len);
} else {
byte_off = ntfs_cluster_to_bytes(vol, lcn);
if (check_add_overflow(byte_off, sec_off << 9,
&byte_off) ||
byte_off > S64_MAX) {
err = -EOVERFLOW;
goto out_unlock;
}
err = ntfs_bdev_read(vol->sb->s_bdev,
(char *)buf + buf_off,
(loff_t)byte_off, byte_len);
if (err)
goto out_unlock;
}
buf_off += byte_len;
sector_count -= sectors;
rl++;
vcn = rl->vcn;
sec_off = 0;
}
err = 0;
out_unlock:
up_read(&runlist->lock);
memalloc_nofs_restore(nofs_flags);
return err;
}
static int parse_wof_chunk_table(struct ntfs_inode *base_ni,
struct ntfs_inode *ni, u64 chunk_idx,
u64 chunk_count, u32 decomp_size,
u64 *chunk_offset, u32 *chunk_size,
void *table_buf, size_t table_buf_size)
{
u8 bytes_per_off;
u8 *buf;
u64 off[2];
u64 byte_off, chunk_data_size, table_size;
u32 bytes_to_read;
int ret = 0;
if (i_size_read(VFS_I(base_ni)) < (1ULL << 32))
bytes_per_off = sizeof(__le32);
else
bytes_per_off = sizeof(__le64);
if (!chunk_count || chunk_idx >= chunk_count)
return -EINVAL;
table_size = (chunk_count - 1) * bytes_per_off;
if (ni->data_size < 0 || (u64)ni->data_size < table_size)
return -EINVAL;
chunk_data_size = (u64)ni->data_size - table_size;
if (chunk_count == 1) {
if (chunk_data_size > decomp_size)
return -EINVAL;
*chunk_offset = 0;
*chunk_size = chunk_data_size;
goto out;
}
byte_off = chunk_idx ? (chunk_idx - 1) * bytes_per_off : 0;
bytes_to_read = chunk_idx + 1 == chunk_count ?
bytes_per_off :
(chunk_idx ? 2 : 1) * bytes_per_off;
if (NInoNonResident(ni)) {
sector_t start_sector = byte_off >> 9;
u32 sector_off = byte_off & ((1 << 9) - 1);
u32 sectors = DIV_ROUND_UP(sector_off + bytes_to_read, 512);
if ((size_t)sectors << 9 > table_buf_size)
return -EINVAL;
buf = table_buf;
ret = ntfs_bdev_read_from_rl(ni->vol, &ni->runlist,
start_sector, sectors, buf);
if (ret)
return -EIO;
buf += sector_off;
} else {
struct ntfs_attr_search_ctx *ctx;
u32 value_length;
u16 value_offset;
if (bytes_to_read > table_buf_size)
return -EINVAL;
mutex_lock(&base_ni->mrec_lock);
ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
if (!ctx) {
ret = -ENOMEM;
goto out_unlock_mrec;
}
ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx);
if (ret)
goto out_put_ctx;
value_length =
le32_to_cpu(ctx->attr->data.resident.value_length);
value_offset =
le16_to_cpu(ctx->attr->data.resident.value_offset);
if (byte_off + bytes_to_read > value_length) {
ret = -EINVAL;
goto out_put_ctx;
}
memcpy(table_buf, (u8 *)ctx->attr + value_offset + byte_off,
bytes_to_read);
buf = table_buf;
out_put_ctx:
ntfs_attr_put_search_ctx(ctx);
out_unlock_mrec:
mutex_unlock(&base_ni->mrec_lock);
if (ret)
return ret;
}
if (bytes_per_off == sizeof(__le32)) {
off[0] = chunk_idx ? get_unaligned_le32(buf) : 0;
if (chunk_idx + 1 == chunk_count)
off[1] = chunk_data_size;
else if (chunk_idx)
off[1] = get_unaligned_le32(buf + bytes_per_off);
else
off[1] = get_unaligned_le32(buf);
} else {
off[0] = chunk_idx ? get_unaligned_le64(buf) : 0;
if (chunk_idx + 1 == chunk_count)
off[1] = chunk_data_size;
else if (chunk_idx)
off[1] = get_unaligned_le64(buf + bytes_per_off);
else
off[1] = get_unaligned_le64(buf);
}
if (off[1] <= off[0] || off[1] > chunk_data_size ||
off[1] - off[0] > decomp_size)
return -EINVAL;
*chunk_offset = table_size + off[0];
*chunk_size = off[1] - off[0];
out:
if (!*chunk_size)
return -EINVAL;
return 0;
}
static int ntfs_read_wof_chunk(struct ntfs_volume *vol,
struct ntfs_inode *wof_ni, u64 chunk_offset,
u32 chunk_size, void *input, size_t input_size,
char **chunk_mem)
{
struct ntfs_inode *base_ni = wof_ni->ext.base_ntfs_ino;
struct ntfs_attr_search_ctx *ctx;
u32 input_offset = chunk_offset & 511;
u32 input_size_aligned;
u32 value_length;
u16 value_offset;
int err;
input_size_aligned = round_up(chunk_size + input_offset, 512);
if (input_size_aligned > input_size)
return -EINVAL;
if (NInoNonResident(wof_ni)) {
err = ntfs_bdev_read_from_rl(vol, &wof_ni->runlist,
chunk_offset >> 9,
input_size_aligned >> 9, input);
if (err)
return err;
*chunk_mem = (u8 *)input + input_offset;
return 0;
}
mutex_lock(&base_ni->mrec_lock);
ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
if (!ctx) {
err = -ENOMEM;
goto out_unlock_mrec;
}
err = ntfs_attr_lookup(wof_ni->type, wof_ni->name, wof_ni->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx);
if (err)
goto out_put_ctx;
value_length = le32_to_cpu(ctx->attr->data.resident.value_length);
value_offset = le16_to_cpu(ctx->attr->data.resident.value_offset);
if (chunk_offset + chunk_size > value_length) {
err = -EINVAL;
goto out_put_ctx;
}
memcpy(input, (u8 *)ctx->attr + value_offset + chunk_offset,
chunk_size);
*chunk_mem = input;
out_put_ctx:
ntfs_attr_put_search_ctx(ctx);
out_unlock_mrec:
mutex_unlock(&base_ni->mrec_lock);
return err;
}
struct ntfs_wof_dest {
struct folio *folios[NTFS_WOF_MAX_PAGES];
struct page *pages[NTFS_WOF_MAX_PAGES];
unsigned int nr_folios;
unsigned int nr_pages;
};
static void ntfs_wof_release_dest(struct ntfs_wof_dest *dest,
struct folio *target, bool success)
{
unsigned int i;
for (i = 0; i < dest->nr_folios; i++) {
struct folio *folio = dest->folios[i];
if (folio == target)
continue;
if (success) {
flush_dcache_folio(folio);
folio_mark_uptodate(folio);
} else {
folio_clear_uptodate(folio);
}
folio_unlock(folio);
folio_put(folio);
}
}
static int ntfs_wof_collect_dest(struct address_space *mapping,
struct folio *target, loff_t chunk_start,
loff_t chunk_end, struct ntfs_wof_dest *dest)
{
pgoff_t index, last, page_index;
unsigned int i;
memset(dest, 0, sizeof(*dest));
index = chunk_start >> PAGE_SHIFT;
last = (chunk_end - 1) >> PAGE_SHIFT;
while (index <= last) {
struct folio *folio;
pgoff_t next;
bool is_target;
if (folio_contains(target, index)) {
folio = target;
is_target = true;
} else {
folio = __filemap_get_folio(
mapping, index,
FGP_LOCK | FGP_CREAT | FGP_NOFS | FGP_NOWAIT,
GFP_NOFS);
if (IS_ERR(folio))
return PTR_ERR(folio);
is_target = false;
if (folio_pos(folio) < chunk_start ||
folio_next_pos(folio) > chunk_end) {
folio_unlock(folio);
folio_put(folio);
return -EAGAIN;
}
}
if (dest->nr_folios == ARRAY_SIZE(dest->folios)) {
if (!is_target) {
folio_unlock(folio);
folio_put(folio);
}
return -EINVAL;
}
dest->folios[dest->nr_folios++] = folio;
next = folio->index + folio_nr_pages(folio);
if (next <= index)
return -EAGAIN;
index = next;
}
for (page_index = chunk_start >> PAGE_SHIFT; page_index <= last;
page_index++) {
struct folio *folio = NULL;
for (i = 0; i < dest->nr_folios; i++) {
if (folio_contains(dest->folios[i], page_index)) {
folio = dest->folios[i];
break;
}
}
if (!folio || dest->nr_pages == ARRAY_SIZE(dest->pages))
return -EAGAIN;
dest->pages[dest->nr_pages++] =
folio_page(folio, page_index - folio->index);
}
return 0;
}
static int ntfs_wof_decode(struct ntfs_wof_workspace *ws, const void *src,
u32 src_len, void *dst, u32 dst_len)
{
if (src_len == dst_len) {
memcpy(dst, src, dst_len);
return 0;
}
return ws->codec->decompress_chunk(ws->scratch, src, src_len, dst,
dst_len, ws->comp_unit);
}
static int ntfs_wof_decode_page_direct(struct ntfs_wof_workspace *ws,
struct folio *target, loff_t chunk_start,
const void *src, u32 src_len,
u32 dst_len)
{
unsigned int page_offset = offset_in_page(chunk_start);
struct page *page;
pgoff_t page_index;
void *addr;
int err;
page_index = chunk_start >> PAGE_SHIFT;
if (!folio_contains(target, page_index))
return -EAGAIN;
page = folio_page(target, page_index - target->index);
addr = kmap_local_page(page);
err = ntfs_wof_decode(ws, src, src_len, (u8 *)addr + page_offset,
dst_len);
kunmap_local(addr);
if (err)
return -EINVAL;
return 0;
}
static int ntfs_wof_decode_folios_direct(struct ntfs_wof_workspace *ws,
struct address_space *mapping,
struct folio *target,
loff_t chunk_start, loff_t chunk_end,
const void *src, u32 src_len,
u32 dst_len)
{
unsigned int page_offset = offset_in_page(chunk_start);
struct ntfs_wof_dest dest;
void *addr;
unsigned int nofs_flags;
int err;
err = ntfs_wof_collect_dest(mapping, target, chunk_start, chunk_end,
&dest);
if (err) {
ntfs_wof_release_dest(&dest, target, false);
return -EAGAIN;
}
nofs_flags = memalloc_nofs_save();
addr = vmap(dest.pages, dest.nr_pages, VM_MAP, PAGE_KERNEL);
memalloc_nofs_restore(nofs_flags);
if (!addr) {
ntfs_wof_release_dest(&dest, target, false);
return -EAGAIN;
}
err = ntfs_wof_decode(ws, src, src_len, (u8 *)addr + page_offset,
dst_len);
vunmap(addr);
if (err) {
ntfs_wof_release_dest(&dest, target, false);
return -EINVAL;
}
ntfs_wof_release_dest(&dest, target, true);
return 0;
}
static int ntfs_wof_try_direct(struct ntfs_wof_workspace *ws,
struct address_space *mapping,
struct folio *target, loff_t chunk_start,
loff_t chunk_end, const void *src, u32 src_len,
u32 dst_len)
{
unsigned int page_offset = offset_in_page(chunk_start);
if (dst_len <= PAGE_SIZE - page_offset)
return ntfs_wof_decode_page_direct(ws, target, chunk_start, src,
src_len, dst_len);
return ntfs_wof_decode_folios_direct(ws, mapping, target, chunk_start,
chunk_end, src, src_len, dst_len);
}
/*
* Decompress one chunk into @folio. Only this step needs the workspace, so it
* is the only step that takes the workspace lock.
*/
static int ntfs_wof_decompress_chunk(struct ntfs_wof_workspace *ws,
struct ntfs_volume *vol,
struct address_space *mapping,
struct folio *folio, loff_t folio_start,
loff_t folio_end, u64 chunk_file_offset,
char *chunk_mem, u32 chunk_size,
u32 decomp_size)
{
loff_t chunk_end = chunk_file_offset + decomp_size;
loff_t copy_start, copy_end;
int err;
mutex_lock(ws->lock);
err = ntfs_wof_workspace_prepare(ws);
if (err)
goto out_unlock;
err = ntfs_wof_try_direct(ws, mapping, folio, chunk_file_offset,
chunk_end, chunk_mem, chunk_size,
decomp_size);
if (err != -EAGAIN)
goto out_unlock;
err = ntfs_wof_decode(ws, chunk_mem, chunk_size, ws->output,
decomp_size);
if (err) {
ntfs_error(vol->sb, "Decompression failed: %d", err);
err = -EINVAL;
goto out_unlock;
}
copy_start = max_t(loff_t, folio_start, chunk_file_offset);
copy_end = min_t(loff_t, folio_end, chunk_file_offset + decomp_size);
memcpy_to_folio(folio, copy_start - folio_start,
ws->output + copy_start - chunk_file_offset,
copy_end - copy_start);
out_unlock:
mutex_unlock(ws->lock);
return err;
}
int ntfs_read_wof_compressed_block(struct folio *folio)
{
struct address_space *mapping = folio->mapping;
struct ntfs_inode *ni = NTFS_I(mapping->host), *wof_ni;
struct inode *wof_inode;
struct ntfs_volume *vol = ni->vol;
struct ntfs_wof_workspace *ws;
loff_t i_size = i_size_read(VFS_I(ni));
loff_t folio_start = folio_pos(folio);
loff_t folio_end = folio_next_pos(folio);
char *chunk_mem;
void *input;
size_t input_size;
u32 decomp_size;
u64 chunk_count, chunk_idx, last_chunk, chunk_offset;
int err = 0;
ws = ntfs_wof_workspace(ni->itype.compressed.block_size_bits);
if (!ws) {
err = -EOPNOTSUPP;
goto out;
}
if (folio_start >= i_size) {
folio_zero_segment(folio, 0, folio_size(folio));
goto out;
}
wof_inode = ntfs_attr_iget(VFS_I(ni), AT_DATA, (__le16 *)WOF_NAME,
WOF_NAME_LEN);
if (IS_ERR(wof_inode)) {
err = PTR_ERR(wof_inode);
goto out;
}
wof_ni = NTFS_I(wof_inode);
if (wof_ni->initialized_size != wof_ni->data_size) {
ntfs_error(vol->sb,
"WOF compressed stream is not fully initialized (init %lld, data %lld).",
wof_ni->initialized_size, wof_ni->data_size);
err = -EIO;
goto out_iput;
}
if (NInoNonResident(wof_ni) && !NInoFullyMapped(wof_ni)) {
down_write(&wof_ni->runlist.lock);
if (!NInoFullyMapped(wof_ni))
err = ntfs_attr_map_whole_runlist(wof_ni);
up_write(&wof_ni->runlist.lock);
if (err)
goto out_iput;
}
input_size = ntfs_wof_input_size(ws);
input = kvmalloc(input_size, GFP_NOFS);
if (!input) {
err = -ENOMEM;
goto out_iput;
}
chunk_idx = div_u64(folio_start, ws->comp_unit);
last_chunk =
div_u64(min_t(loff_t, folio_end, i_size) - 1, ws->comp_unit);
chunk_count = DIV_ROUND_UP_ULL(i_size, ws->comp_unit);
for (; chunk_idx <= last_chunk; chunk_idx++) {
u32 chunk_size;
decomp_size = chunk_idx + 1 == chunk_count ?
i_size - chunk_idx * ws->comp_unit :
ws->comp_unit;
err = parse_wof_chunk_table(ni, wof_ni, chunk_idx, chunk_count,
decomp_size, &chunk_offset,
&chunk_size, input, input_size);
if (err)
goto out_free_input;
err = ntfs_read_wof_chunk(vol, wof_ni, chunk_offset, chunk_size,
input, input_size, &chunk_mem);
if (err)
goto out_free_input;
err = ntfs_wof_decompress_chunk(ws, vol, mapping, folio,
folio_start, folio_end,
chunk_idx * ws->comp_unit,
chunk_mem, chunk_size,
decomp_size);
if (err)
goto out_free_input;
}
if (folio_end > i_size)
folio_zero_segment(folio, i_size - folio_start,
folio_size(folio));
out_free_input:
kvfree(input);
out_iput:
iput(wof_inode);
out:
if (!err) {
flush_dcache_folio(folio);
folio_mark_uptodate(folio);
} else {
folio_clear_uptodate(folio);
}
folio_unlock(folio);
return err;
}