Merge tag 'ntfs-for-7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs

Pull ntfs fixes from Namjae Jeon:

 - Serialize truncate, fallocate, and mmap fault paths with
   invalidate_lock, avoiding mmap failures during concurrent size
   changes and exposure of uninitialized data during allocation

 - Correct fallocate signal and zeroing error handling

 - Fix FITRIM range alignment to prevent discard requests from extending
   into allocated clusters

 - Fix free-cluster accounting when cluster-freeing rollback or bitmap
   clearing fails

 - Keep volumes marked dirty when ntfs errors have been recorded

 - Compute bi_sector in 512-byte units, preventing silent corruption on
   4Kn devices

 - Validate sectors_per_cluster values and prevent undefined shifts when
   parsing MFT and index record sizes

 - Bound $AttrDef traversal to the loaded table size

 - Fix MFT record resizing, memmove overlap, and kmap_local cleanup
   issues

 - Improve error propagation across attribute, EA, and reparse
   operations, including returning -ERANGE for undersized xattr buffers

 - Avoid modifying the HasEA flag when setxattr fails and return
   DT_UNKNOWN when directory inode lookup fails

 - Reduce contention in WOF decompression by performing block reads
   outside the decompression lock

* tag 'ntfs-for-7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs: (23 commits)
  ntfs: take invalidate_lock in ntfs_filemap_page_mkwrite()
  ntfs: take invalidate_lock in ntfs_setattr_size()
  ntfs: handle signal interruption in fallocate
  ntfs: fix FITRIM range alignment
  ntfs: read WOF chunks outside the decompression lock
  ntfs: leave HasEA flag untouched on setxattr failure
  ntfs: fix race between fallocate and mmap reads
  ntfs: fix memmove overlap in ntfs_new_attr_flags
  ntfs: compute bi_sector in 512-byte units
  ntfs: reject invalid sectors_per_cluster in the boot sector
  ntfs: bound $AttrDef table walk to the loaded table size
  ntfs: fix undefined behavior in mft/index record size calculation
  ntfs: treat any nonzero dio zero-range return as an error
  ntfs: fix incorrect MFT record pointer passed to ntfs_attr_record_resize
  ntfs: do not mark the volume clean in sync_fs when errors were recorded
  ntfs: skip free cluster decrement when rollback fails
  ntfs: only count successfully cleared runs when freeing clusters
  ntfs: fix kmap_local leak in write_mft_record_nolock() error paths
  ntfs: return real error from ntfs_non_resident_attr_record_add()
  ntfs: preserve error code in ntfs_resident_attr_record_add()
  ...
This commit is contained in:
Linus Torvalds
2026-09-03 08:10:04 -07:00
13 changed files with 196 additions and 127 deletions

View File

@@ -1737,8 +1737,8 @@ static struct attr_def *ntfs_attr_find_in_attrdef(const struct ntfs_volume *vol,
struct attr_def *ad;
WARN_ON(!type);
for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef <
vol->attrdef_size && ad->type; ++ad) {
for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef <=
vol->attrdef_size - (s32)sizeof(*ad) && ad->type; ++ad) {
/* We have not found it yet, carry on searching. */
if (likely(le32_to_cpu(ad->type) < le32_to_cpu(type)))
continue;
@@ -2500,7 +2500,7 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
return offset;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
return -EIO;
return err;
}
/*
@@ -2639,7 +2639,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
return offset;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
return -1;
return err;
}
/*
@@ -5704,12 +5704,12 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
lcn << vol->cluster_size_bits,
alloc_cnt <<
vol->cluster_size_bits);
if (err > 0)
if (err)
goto out;
}
if (signal_pending(current))
goto out;
goto signal_out;
vcn += alloc_cnt;
try_alloc_cnt -= alloc_cnt;
@@ -5730,7 +5730,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
up_write(&ni->runlist.lock);
mutex_unlock(&ni->mrec_lock);
if (err || signal_pending(current))
goto out;
goto signal_out;
vcn += alloc_cnt;
try_alloc_cnt -= alloc_cnt;
@@ -5756,4 +5756,8 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
mutex_unlock(&ni->mrec_lock);
out:
return err >= 0 ? 0 : err;
signal_out:
if (!err)
err = -EINTR;
goto out;
}

View File

@@ -34,7 +34,7 @@ int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t s
int error;
struct bio *bio;
blk_opf_t op;
sector_t sector = start >> SECTOR_SHIFT;
sector_t sector = ntfs_bytes_to_bio_sector(start);
if (start & (SECTOR_SIZE - 1))
return -EINVAL;

View File

@@ -64,7 +64,7 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range)
end = start_buf;
while (end < end_buf) {
u64 aligned_start, aligned_count;
u64 aligned_start, aligned_end, aligned_count;
u64 start = find_next_zero_bit(bitmap, end_buf - start_buf,
end - start_buf) + start_buf;
if (start >= end_buf)
@@ -74,8 +74,10 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range)
start - start_buf) + start_buf;
aligned_start = ALIGN(ntfs_cluster_to_bytes(vol, start), dq);
aligned_count =
ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end - start), dq);
aligned_end = ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end), dq);
if (aligned_start >= aligned_end)
continue;
aligned_count = aligned_end - aligned_start;
if (aligned_count >= range->minlen) {
ret = blkdev_issue_discard(vol->sb->s_bdev, aligned_start >> 9,
aligned_count >> 9, GFP_NOFS);

View File

@@ -1414,7 +1414,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
bio_pos = ntfs_cluster_to_bytes(vol, bio_lcn);
bio = bio_alloc(vol->sb->s_bdev, DIV_ROUND_UP(bio_size, PAGE_SIZE),
REQ_OP_WRITE, GFP_NOIO);
bio->bi_iter.bi_sector = ntfs_bytes_to_sector(vol, bio_pos);
bio->bi_iter.bi_sector = ntfs_bytes_to_bio_sector(bio_pos);
for (i = 0; bio_size; i++) {
unsigned int len = min_t(unsigned int, bio_size, PAGE_SIZE);

View File

@@ -404,10 +404,12 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
*packed_ea_size = p_ea_info->ea_length;
mark_mft_record_dirty(ni);
out:
if (ea_info_qsize > 0)
NInoSetHasEA(ni);
else
NInoClearHasEA(ni);
if (!err) {
if (ea_info_qsize > 0)
NInoSetHasEA(ni);
else
NInoClearHasEA(ni);
}
kvfree(ea_buf);
kvfree(old_ea_buf);
@@ -615,7 +617,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler,
if (!buffer) {
err = sizeof(u8);
} else if (size < sizeof(u8)) {
err = -ENODATA;
err = -ERANGE;
} else {
err = sizeof(u8);
*(u8 *)buffer = (u8)(le32_to_cpu(ni->flags) & 0x3F);
@@ -628,7 +630,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler,
if (!buffer) {
err = sizeof(u32);
} else if (size < sizeof(u32)) {
err = -ENODATA;
err = -ERANGE;
} else {
err = sizeof(u32);
*(u32 *)buffer = le32_to_cpu(ni->flags);
@@ -753,18 +755,39 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
old_arec_size = le32_to_cpu(a->length);
/*
* Move payloads before shrinking the record. Otherwise resizing moves
* Move payloads before shrinking the record. Otherwise resizing moves
* the following attribute over the old payload before it can be copied.
*
* When offsets increase, move mapping_pairs first to avoid name
* overwriting the start of mapping_pairs.
*/
if (arec_size < old_arec_size) {
if (a->name_length && name_ofs != old_name_ofs)
memmove((u8 *)a + name_ofs, (u8 *)a + old_name_ofs,
a->name_length * sizeof(__le16));
if (mp_ofs != old_mp_ofs)
memmove((u8 *)a + mp_ofs, (u8 *)a + old_mp_ofs, mp_size);
if (name_ofs > old_name_ofs) {
/* Payload offsets increased: move mapping pairs first. */
if (mp_ofs != old_mp_ofs)
memmove((u8 *)a + mp_ofs,
(u8 *)a + old_mp_ofs,
mp_size);
if (a->name_length && name_ofs != old_name_ofs)
memmove((u8 *)a + name_ofs,
(u8 *)a + old_name_ofs,
a->name_length *
sizeof(__le16));
} else {
/* Payload offsets decreased or unchanged: move name first. */
if (a->name_length && name_ofs != old_name_ofs)
memmove((u8 *)a + name_ofs,
(u8 *)a + old_name_ofs,
a->name_length *
sizeof(__le16));
if (mp_ofs != old_mp_ofs)
memmove((u8 *)a + mp_ofs,
(u8 *)a + old_mp_ofs,
mp_size);
}
}
err = ntfs_attr_record_resize(m, a, arec_size);
err = ntfs_attr_record_resize(ctx->mrec, a, arec_size);
if (unlikely(err))
goto err_out;

View File

@@ -270,18 +270,25 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr)
return err;
inode_dio_wait(vi);
/*
* Serialize with page faults and pagecache instantiation so that
* readers cannot observe the size change until the attribute
* updates below have completed.
*/
filemap_invalidate_lock(vi->i_mapping);
if (attr->ia_size > old_size) {
truncate_pagecache(vi, old_size);
i_size_write(vi, attr->ia_size);
pagecache_isize_extended(vi, old_size, attr->ia_size);
} else
} else {
truncate_setsize(vi, attr->ia_size);
}
err = ntfs_truncate_vfs(vi, attr->ia_size, old_size);
if (err) {
if (err)
i_size_write(vi, old_size);
return err;
}
filemap_invalidate_unlock(vi->i_mapping);
return err;
}
@@ -669,6 +676,7 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf)
{
struct inode *inode = file_inode(vmf->vma->vm_file);
struct address_space *mapping = inode->i_mapping;
vm_fault_t ret;
if (NInoWofCompressed(NTFS_I(inode)))
@@ -677,7 +685,14 @@ static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf)
sb_start_pagefault(inode->i_sb);
file_update_time(vmf->vma->vm_file);
/*
* Serialize against truncate/fallocate which hold the lock
* exclusively while invalidating pagecache and changing extents.
*/
filemap_invalidate_lock_shared(mapping);
ret = iomap_page_mkwrite(vmf, &ntfs_page_mkwrite_iomap_ops, NULL);
filemap_invalidate_unlock_shared(mapping);
sb_end_pagefault(inode->i_sb);
return ret;
}
@@ -1116,7 +1131,6 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
struct ntfs_volume *vol = ni->vol;
int err = 0;
loff_t old_size;
bool map_locked = false;
if (mode & ~(NTFS_FALLOC_FL_SUPPORTED))
return -EOPNOTSUPP;
@@ -1148,16 +1162,13 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
inode_lock(vi);
if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni)) {
err = -EOPNOTSUPP;
goto out;
inode_unlock(vi);
return -EOPNOTSUPP;
}
inode_dio_wait(vi);
if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE |
FALLOC_FL_INSERT_RANGE)) {
filemap_invalidate_lock(vi->i_mapping);
map_locked = true;
}
/* Take invalidate_lock for all fallocate operations to prevent races */
filemap_invalidate_lock(vi->i_mapping);
switch (mode & FALLOC_FL_MODE_MASK) {
case FALLOC_FL_ALLOCATE_RANGE:
@@ -1182,14 +1193,15 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
err = file_modified(file);
out:
if (map_locked)
filemap_invalidate_unlock(vi->i_mapping);
if (!err && mode == 0 && NInoNonResident(ni) &&
offset > old_size) {
truncate_pagecache(vi, old_size);
pagecache_isize_extended(vi, old_size, offset);
}
filemap_invalidate_unlock(vi->i_mapping);
if (!err) {
if (mode == 0 && NInoNonResident(ni) &&
offset > old_size) {
truncate_pagecache(vi, old_size);
pagecache_isize_extended(vi, old_size, offset);
}
NInoSetFileNameDirty(ni);
inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi));
mark_inode_dirty(vi);

View File

@@ -1852,7 +1852,7 @@ int ntfs_read_inode_mount(struct inode *vi)
struct mft_record *m = NULL;
struct attr_record *a;
struct ntfs_attr_search_ctx *ctx;
unsigned int i, nr_blocks;
unsigned int i;
int err;
size_t new_rl_count;
@@ -1896,11 +1896,6 @@ int ntfs_read_inode_mount(struct inode *vi)
goto err_out;
}
/* Determine the first block of the $MFT/$DATA attribute. */
nr_blocks = ntfs_bytes_to_sector(vol, vol->mft_record_size);
if (!nr_blocks)
nr_blocks = 1;
/* Load $MFT/$DATA's first mft record. */
err = ntfs_bdev_read(sb->s_bdev, (char *)m,
ntfs_cluster_to_bytes(vol, vol->mft_lcn), i);
@@ -3780,8 +3775,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE,
GFP_NOIO);
bio->bi_iter.bi_sector =
ntfs_bytes_to_sector(vol,
ntfs_cluster_to_bytes(vol, lcn) +
ntfs_bytes_to_bio_sector(ntfs_cluster_to_bytes(vol, lcn) +
lcn_folio_off);
length = min_t(unsigned long,

View File

@@ -53,10 +53,10 @@ int ntfs_cluster_free_from_rl_nolock(struct ntfs_volume *vol,
if (rl->lcn < 0)
continue;
err = ntfs_bitmap_clear_run(lcnbmp_vi, rl->lcn, rl->length);
if (unlikely(err && (!ret || ret == -ENOMEM) && ret != err))
ret = err;
else
if (likely(!err))
nr_freed += rl->length;
else if (!ret || ret == -ENOMEM)
ret = err;
}
ntfs_inc_free_clusters(vol, nr_freed);
ntfs_debug("Done.");
@@ -1045,8 +1045,9 @@ s64 __ntfs_cluster_free(struct ntfs_inode *ni, const s64 start_vcn, s64 count,
"Failed to rollback (error %i). Leaving inconsistent metadata! Unmount and run chkdsk.",
(int)delta);
NVolSetErrors(vol);
} else {
ntfs_dec_free_clusters(vol, delta);
}
ntfs_dec_free_clusters(vol, delta);
up_write(&vol->lcnbmp_lock);
memalloc_nofs_restore(memalloc_flags);
ntfs_error(vol->sb, "Aborting (error %i).", err);

View File

@@ -499,8 +499,8 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO);
bio->bi_iter.bi_sector =
NTFS_B_TO_SECTOR(vol, NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) +
lcn_folio_off + folio_ofs);
ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) +
lcn_folio_off + folio_ofs);
if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs))
err = submit_bio_wait(bio);
@@ -580,7 +580,7 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
err = pre_write_mst_fixup((struct ntfs_record *)fixup_m, vol->mft_record_size);
if (err) {
ntfs_error(vol->sb, "Failed to apply mst fixups!");
goto err_out;
goto unmap_err_out;
}
folio_size = vol->mft_record_size / ni->mft_lcn_count;
@@ -592,8 +592,8 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO);
bio->bi_iter.bi_sector =
NTFS_B_TO_SECTOR(vol, NTFS_CLU_TO_B(vol, ni->mft_lcn[i]) +
clu_off);
ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, ni->mft_lcn[i]) +
clu_off);
if (!bio_add_folio(bio, folio, folio_size,
ni->folio_ofs + offset)) {
@@ -645,6 +645,8 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
return 0;
put_bio_out:
bio_put(bio);
unmap_err_out:
kunmap_local(kaddr);
err_out:
/*
* The caller should mark the base inode as bad so no more I/O
@@ -2740,8 +2742,8 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE,
GFP_NOIO);
bio->bi_iter.bi_sector =
ntfs_bytes_to_sector(vol,
ntfs_cluster_to_bytes(vol, lcn) + off);
ntfs_bytes_to_bio_sector(
ntfs_cluster_to_bytes(vol, lcn) + off);
}
if (vol->cluster_size == NTFS_BLOCK_SIZE &&

View File

@@ -19,6 +19,7 @@
#include <linux/nls.h>
#include <linux/smp.h>
#include <linux/pagemap.h>
#include <linux/blk_types.h>
#include <linux/uidgid.h>
#include "volume.h"
@@ -71,8 +72,6 @@
#define NTFS_CLU_TO_POFS(vol, clu) (((u64)(clu) << (vol)->cluster_size_bits) & \
~PAGE_MASK)
#define NTFS_B_TO_SECTOR(vol, b) ((b) >> ((vol)->sb)->s_blocksize_bits)
enum {
NTFS_BLOCK_SIZE = 512,
NTFS_BLOCK_SIZE_BITS = 9,
@@ -154,11 +153,10 @@ static inline u64 ntfs_cluster_to_poff(const struct ntfs_volume *vol,
return (clu << vol->cluster_size_bits) & ~PAGE_MASK;
}
/* Convert byte offset to sector (block) number. */
static inline sector_t ntfs_bytes_to_sector(const struct ntfs_volume *vol,
u64 bytes)
/* Convert a byte offset on the volume to a bio sector number. */
static inline sector_t ntfs_bytes_to_bio_sector(u64 bytes)
{
return bytes >> vol->sb->s_blocksize_bits;
return bytes >> SECTOR_SHIFT;
}
/* Global variables. */

View File

@@ -405,7 +405,7 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr
vi = ntfs_iget(vol->sb, mref);
if (IS_ERR(vi))
return PTR_ERR(vi);
return DT_UNKNOWN;
reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi),
AT_REPARSE_POINT, NULL, 0, &attr_size);
@@ -694,8 +694,9 @@ static int update_reparse_data(struct ntfs_inode *ni, struct ntfs_index_context
goto put_rp_inode;
}
if (set_reparse_index(ni, xr, ((const struct reparse_point *)value)->reparse_tag) &&
oldsize > 0) {
err = set_reparse_index(ni, xr,
((const struct reparse_point *)value)->reparse_tag);
if (err && oldsize > 0) {
/*
* If cannot index, try to remove the reparse
* data and log the error. There will be an

View File

@@ -557,8 +557,8 @@ static bool is_boot_sector_ntfs(const struct super_block *sb,
* Check sectors per cluster value is valid and the cluster size
* is not above the maximum (2MB).
*/
if (b->bpb.sectors_per_cluster > 0x80 &&
b->bpb.sectors_per_cluster < 0xf4)
if (b->bpb.sectors_per_cluster < 0xf4 &&
!is_power_of_2(b->bpb.sectors_per_cluster))
goto not_ntfs;
/* Check reserved/unused fields are really zero. */
@@ -695,7 +695,7 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol,
* = -log2(mft_record_size) bytes. mft_record_size normaly is
* 1024 bytes, which is encoded as 0xF6 (-10 in decimal).
*/
vol->mft_record_size = 1 << -clusters_per_mft_record;
vol->mft_record_size = 1U << -clusters_per_mft_record;
vol->mft_record_size_mask = vol->mft_record_size - 1;
vol->mft_record_size_bits = ffs(vol->mft_record_size) - 1;
ntfs_debug("vol->mft_record_size = %i (0x%x)", vol->mft_record_size,
@@ -732,7 +732,7 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol,
* index_record_size normaly equals 4096 bytes, which is
* encoded as 0xF4 (-12 in decimal).
*/
vol->index_record_size = 1 << -clusters_per_index_record;
vol->index_record_size = 1U << -clusters_per_index_record;
vol->index_record_size_mask = vol->index_record_size - 1;
vol->index_record_size_bits = ffs(vol->index_record_size) - 1;
ntfs_debug("vol->index_record_size = %i (0x%x)",
@@ -1241,9 +1241,9 @@ static bool load_and_init_attrdef(struct ntfs_volume *vol)
goto failed;
}
NInoSetSparseDisabled(NTFS_I(ino));
/* The size of FILE_AttrDef must be above 0 and fit inside 31 bits. */
/* FILE_AttrDef must hold at least one entry and fit inside 31 bits. */
i_size = i_size_read(ino);
if (i_size <= 0 || i_size > 0x7fffffff)
if (i_size < (s64)sizeof(struct attr_def) || i_size > 0x7fffffff)
goto iput_failed;
vol->attrdef = kvzalloc(i_size, GFP_NOFS);
if (!vol->attrdef)
@@ -1862,7 +1862,8 @@ static int ntfs_sync_fs(struct super_block *sb, int wait)
return 0;
/* If there are some dirty buffers in the bdev inode */
if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) {
if (!NVolErrors(vol) &&
ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) {
ntfs_warning(sb, "Failed to clear dirty bit in volume information flags. Run chkdsk.");
err = -EIO;
}

View File

@@ -39,8 +39,6 @@ struct ntfs_wof_workspace {
struct mutex *lock;
const struct ntfs_codec_ops *codec;
u32 comp_unit;
void *input;
size_t input_size;
void *output;
void *scratch;
};
@@ -97,30 +95,36 @@ static struct ntfs_wof_workspace *ntfs_wof_workspace(u8 block_size_bits)
}
}
/*
* 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 *input, *output, *scratch;
void *output, *scratch;
size_t scratch_size;
if (ws->input)
if (ws->output)
return 0;
ws->input_size = round_up((size_t)ws->comp_unit + 511, 512);
scratch_size = ws->codec->scratch_size(ws->comp_unit);
if (!scratch_size)
return -EINVAL;
input = kvmalloc(ws->input_size, GFP_NOFS);
output = kvmalloc(ws->comp_unit, GFP_NOFS);
scratch = kvzalloc(scratch_size, GFP_NOFS);
if (!input || !output || !scratch) {
kvfree(input);
if (!output || !scratch) {
kvfree(output);
kvfree(scratch);
return -ENOMEM;
}
ws->input = input;
ws->output = output;
ws->scratch = scratch;
return 0;
@@ -134,10 +138,8 @@ void ntfs_wof_free_workspaces(void)
struct ntfs_wof_workspace *ws = ntfs_wof_workspaces[i];
mutex_lock(ws->lock);
kvfree(ws->input);
kvfree(ws->output);
kvfree(ws->scratch);
ws->input = NULL;
ws->output = NULL;
ws->scratch = NULL;
mutex_unlock(ws->lock);
@@ -602,6 +604,51 @@ static int ntfs_wof_try_direct(struct ntfs_wof_workspace *ws,
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;
@@ -613,6 +660,8 @@ int ntfs_read_wof_compressed_block(struct folio *folio)
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;
@@ -652,10 +701,12 @@ int ntfs_read_wof_compressed_block(struct folio *folio)
goto out_iput;
}
mutex_lock(ws->lock);
err = ntfs_wof_workspace_prepare(ws);
if (err)
goto out_unlock_ws;
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 =
@@ -663,55 +714,35 @@ int ntfs_read_wof_compressed_block(struct folio *folio)
chunk_count = DIV_ROUND_UP_ULL(i_size, ws->comp_unit);
for (; chunk_idx <= last_chunk; chunk_idx++) {
u32 chunk_size;
u64 chunk_file_offset;
loff_t chunk_end, copy_start, copy_end;
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, ws->input,
ws->input_size);
&chunk_size, input, input_size);
if (err)
goto out_unlock_ws;
goto out_free_input;
err = ntfs_read_wof_chunk(vol, wof_ni, chunk_offset, chunk_size,
ws->input, ws->input_size,
&chunk_mem);
input, input_size, &chunk_mem);
if (err)
goto out_unlock_ws;
goto out_free_input;
chunk_file_offset = chunk_idx * ws->comp_unit;
chunk_end = chunk_file_offset + decomp_size;
err = ntfs_wof_try_direct(ws, mapping, folio, chunk_file_offset,
chunk_end, chunk_mem, chunk_size,
decomp_size);
if (!err)
continue;
if (err != -EAGAIN)
goto out_unlock_ws;
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_ws;
}
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);
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_unlock_ws:
mutex_unlock(ws->lock);
out_free_input:
kvfree(input);
out_iput:
iput(wof_inode);
out: