summaryrefslogtreecommitdiff
path: root/fs/ntfs
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ntfs')
-rw-r--r--fs/ntfs/Kconfig11
-rw-r--r--fs/ntfs/Makefile5
-rw-r--r--fs/ntfs/aops.c42
-rw-r--r--fs/ntfs/attrib.c719
-rw-r--r--fs/ntfs/attrib.h13
-rw-r--r--fs/ntfs/attrlist.c251
-rw-r--r--fs/ntfs/attrlist.h2
-rw-r--r--fs/ntfs/bdev-io.c13
-rw-r--r--fs/ntfs/bitmap.c10
-rw-r--r--fs/ntfs/compress.c407
-rw-r--r--fs/ntfs/dir.c100
-rw-r--r--fs/ntfs/ea.c340
-rw-r--r--fs/ntfs/ea.h3
-rw-r--r--fs/ntfs/file.c214
-rw-r--r--fs/ntfs/index.c399
-rw-r--r--fs/ntfs/index.h12
-rw-r--r--fs/ntfs/inode.c269
-rw-r--r--fs/ntfs/inode.h7
-rw-r--r--fs/ntfs/iomap.c159
-rw-r--r--fs/ntfs/layout.h19
-rw-r--r--fs/ntfs/lcnalloc.c16
-rw-r--r--fs/ntfs/lib/decompress_common.c200
-rw-r--r--fs/ntfs/lib/decompress_common.h444
-rw-r--r--fs/ntfs/lib/lib.h29
-rw-r--r--fs/ntfs/lib/lzx_decompress.c631
-rw-r--r--fs/ntfs/lib/xpress_decompress.c154
-rw-r--r--fs/ntfs/logfile.c12
-rw-r--r--fs/ntfs/mft.c716
-rw-r--r--fs/ntfs/mft.h2
-rw-r--r--fs/ntfs/namei.c115
-rw-r--r--fs/ntfs/ntfs.h14
-rw-r--r--fs/ntfs/ntfs_codec.h54
-rw-r--r--fs/ntfs/quota.c95
-rw-r--r--fs/ntfs/quota.h15
-rw-r--r--fs/ntfs/reparse.c598
-rw-r--r--fs/ntfs/reparse.h8
-rw-r--r--fs/ntfs/runlist.c145
-rw-r--r--fs/ntfs/super.c299
-rw-r--r--fs/ntfs/volume.h26
-rw-r--r--fs/ntfs/wof.c757
40 files changed, 5652 insertions, 1673 deletions
diff --git a/fs/ntfs/Kconfig b/fs/ntfs/Kconfig
index 6a6acde9ba91..f88f4138f921 100644
--- a/fs/ntfs/Kconfig
+++ b/fs/ntfs/Kconfig
@@ -10,6 +10,17 @@ config NTFS_FS
To compile this as a module, choose M here: the module will be called
ntfs.
+config NTFS_FS_WOF_COMPRESSION
+ bool "NTFS Windows system compression support"
+ depends on NTFS_FS
+ help
+ Enable read support for Windows system-compressed files.
+ Windows more aggressively compresses system files such as binaries
+ and DLLs using system compression. It is optimized for files that
+ are rarely modified.
+ Enabling this option builds the decompression support into the NTFS
+ module.
+
config NTFS_DEBUG
bool "NTFS debugging support"
depends on NTFS_FS
diff --git a/fs/ntfs/Makefile b/fs/ntfs/Makefile
index 0ce4d9a9388a..ee8987e496a8 100644
--- a/fs/ntfs/Makefile
+++ b/fs/ntfs/Makefile
@@ -5,6 +5,9 @@ obj-$(CONFIG_NTFS_FS) += ntfs.o
ntfs-y := aops.o attrib.o collate.o dir.o file.o index.o inode.o \
mft.o mst.o namei.o runlist.o super.o unistr.o attrlist.o ea.o \
upcase.o bitmap.o lcnalloc.o logfile.o reparse.o compress.o \
- iomap.o debug.o sysctl.o quota.o object_id.o bdev-io.o
+ iomap.o debug.o sysctl.o object_id.o bdev-io.o
+
+ntfs-$(CONFIG_NTFS_FS_WOF_COMPRESSION) += wof.o \
+ lib/decompress_common.o lib/lzx_decompress.o lib/xpress_decompress.o
ccflags-$(CONFIG_NTFS_DEBUG) += -DDEBUG
diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c
index 1fbf832ad165..517d9a1563e7 100644
--- a/fs/ntfs/aops.c
+++ b/fs/ntfs/aops.c
@@ -38,11 +38,9 @@ static void ntfs_iomap_read_end_io(struct bio *bio)
}
static void ntfs_iomap_bio_submit_read(const struct iomap_iter *iter,
- struct iomap_read_folio_ctx *ctx)
+ struct iomap_read_folio_ctx *ctx)
{
- struct bio *bio = ctx->read_ctx;
- bio->bi_end_io = ntfs_iomap_read_end_io;
- submit_bio(bio);
+ iomap_bio_submit_read_endio(iter, ctx, ntfs_iomap_read_end_io);
}
static const struct iomap_read_ops ntfs_iomap_bio_read_ops = {
@@ -92,6 +90,14 @@ static int ntfs_read_folio(struct file *file, struct folio *folio)
folio_unlock(folio);
return -EOPNOTSUPP;
}
+ if (NInoWofCompressed(ni)) {
+#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
+ return ntfs_read_wof_compressed_block(folio);
+#else
+ folio_unlock(folio);
+ return -EOPNOTSUPP;
+#endif
+ }
/* Compressed data streams are handled in compress.c. */
if (NInoNonResident(ni) && NInoCompressed(ni))
return ntfs_read_compressed_block(folio);
@@ -138,11 +144,12 @@ static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
ntfs_debug("Entering for mft_no 0x%llx, logical block 0x%llx.",
ni->mft_no, (unsigned long long)block);
if (ni->type != AT_DATA || !NInoNonResident(ni) || NInoEncrypted(ni) ||
- NInoMstProtected(ni)) {
+ NInoWofCompressed(ni) || NInoMstProtected(ni)) {
ntfs_error(vol->sb, "BMAP does not make sense for %s attributes, returning 0.",
(ni->type != AT_DATA) ? "non-data" :
(!NInoNonResident(ni) ? "resident" :
- "encrypted"));
+ (NInoWofCompressed(ni) ? "WOF-compressed" :
+ "encrypted")));
return 0;
}
/* None of these can happen. */
@@ -236,7 +243,8 @@ static void ntfs_readahead(struct readahead_control *rac)
* Resident files are not cached in the page cache,
* and readahead is not implemented for compressed files.
*/
- if (!NInoNonResident(ni) || NInoCompressed(ni))
+ if (!NInoNonResident(ni) || NInoCompressed(ni) ||
+ NInoWofCompressed(ni))
return;
iomap_readahead(&ntfs_read_iomap_ops, &ctx, NULL);
}
@@ -251,6 +259,8 @@ static int ntfs_writepages(struct address_space *mapping,
.wbc = wbc,
.ops = &ntfs_writeback_ops,
};
+ bool need_iput = false;
+ int ret;
if (NVolShutdown(ni->vol))
return -EIO;
@@ -267,12 +277,28 @@ static int ntfs_writepages(struct address_space *mapping,
return -EOPNOTSUPP;
}
- return iomap_writepages(&wpc);
+ /*
+ * Prevent eviction in writeback to avoid deadlock in
+ * ntfs_drop_big_inode().
+ */
+ if ((ni->type == AT_DATA || ni->type == AT_INDEX_ALLOCATION) &&
+ igrab(inode))
+ need_iput = true;
+
+ ret = iomap_writepages(&wpc);
+
+ if (need_iput)
+ iput(inode);
+
+ return ret;
}
static int ntfs_swap_activate(struct swap_info_struct *sis,
struct file *swap_file, sector_t *span)
{
+ if (NInoWofCompressed(NTFS_I(file_inode(swap_file))))
+ return -EOPNOTSUPP;
+
return iomap_swapfile_activate(sis, swap_file, span,
&ntfs_read_iomap_ops);
}
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index 97b660eaa00c..c949ff765075 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -16,6 +16,7 @@
* Copyright (c) 2010 Erik Larsson
*/
+#include <linux/string_choices.h>
#include <linux/writeback.h>
#include <linux/iomap.h>
@@ -174,7 +175,10 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
err = -EIO;
goto err_out;
}
- WARN_ON(!ctx->attr->non_resident);
+ if (unlikely(!ctx->attr->non_resident)) {
+ err = -EIO;
+ goto err_out;
+ }
}
a = ctx->attr;
/*
@@ -583,29 +587,195 @@ static u32 ntfs_resident_attr_min_value_length(const __le32 type)
case AT_STANDARD_INFORMATION:
return offsetof(struct standard_information, ver) +
sizeof(((struct standard_information *)0)->ver.v1.reserved12);
- case AT_ATTRIBUTE_LIST:
- return offsetof(struct attr_list_entry, name);
case AT_FILE_NAME:
- return offsetof(struct file_name_attr, file_name);
- case AT_OBJECT_ID:
- return sizeof(struct guid);
- case AT_SECURITY_DESCRIPTOR:
- return sizeof(struct security_descriptor_relative);
+ return offsetof(struct file_name_attr, file_name) +
+ sizeof(__le16) * 1;
case AT_VOLUME_INFORMATION:
return sizeof(struct volume_information);
case AT_INDEX_ROOT:
return sizeof(struct index_root);
- case AT_REPARSE_POINT:
- return offsetof(struct reparse_point, reparse_data);
case AT_EA_INFORMATION:
return sizeof(struct ea_information);
- case AT_EA:
- return offsetof(struct ea_attr, ea_name) + 1;
default:
return 0;
}
}
+static bool ntfs_attr_type_is_resident_only(const __le32 type)
+{
+ switch (type) {
+ case AT_STANDARD_INFORMATION:
+ case AT_FILE_NAME:
+ case AT_OBJECT_ID:
+ case AT_VOLUME_NAME:
+ case AT_VOLUME_INFORMATION:
+ case AT_INDEX_ROOT:
+ case AT_EA_INFORMATION:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ntfs_file_name_attr_value_is_valid(const u8 *value, const u32 value_length)
+{
+ const struct file_name_attr *fn;
+ u32 file_name_size;
+
+ fn = (const struct file_name_attr *)value;
+ file_name_size = fn->file_name_length * sizeof(__le16);
+
+ return file_name_size <=
+ value_length - offsetof(struct file_name_attr, file_name);
+}
+
+static bool ntfs_volume_name_attr_value_is_valid(const u32 value_length)
+{
+ if (value_length & 1)
+ return false;
+
+ return value_length <= NTFS_MAX_LABEL_LEN * sizeof(__le16);
+}
+
+static bool ntfs_index_root_attr_value_is_valid(const u8 *value, const u32 value_length)
+{
+ const struct index_root *ir;
+ u32 index_size;
+ u32 entries_offset;
+ u32 index_length;
+ u32 allocated_size;
+
+ ir = (const struct index_root *)value;
+ index_size = value_length - offsetof(struct index_root, index);
+ entries_offset = le32_to_cpu(ir->index.entries_offset);
+ index_length = le32_to_cpu(ir->index.index_length);
+ allocated_size = le32_to_cpu(ir->index.allocated_size);
+
+ if ((entries_offset | index_length | allocated_size) & 7 ||
+ entries_offset < sizeof(struct index_header) ||
+ entries_offset > index_length ||
+ index_length > allocated_size ||
+ allocated_size > index_size ||
+ index_length - entries_offset < sizeof(struct index_entry_header))
+ return false;
+
+ return true;
+}
+
+struct ntfs_resident_attr_value {
+ const u8 *data;
+ u32 len;
+};
+
+static bool ntfs_resident_attr_value_get(const struct attr_record *a,
+ struct ntfs_resident_attr_value *value)
+{
+ u32 attr_len;
+ u16 value_offset;
+
+ attr_len = le32_to_cpu(a->length);
+ if (attr_len < offsetof(struct attr_record, data.resident.reserved) +
+ sizeof(a->data.resident.reserved))
+ return false;
+
+ value->len = le32_to_cpu(a->data.resident.value_length);
+ value_offset = le16_to_cpu(a->data.resident.value_offset);
+
+ if (value->len > attr_len || value_offset > attr_len - value->len)
+ return false;
+
+ value->data = (const u8 *)a + value_offset;
+ return true;
+}
+
+static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a)
+{
+ u32 attr_len;
+ u32 min_len;
+ u16 mp_offset;
+ u16 name_offset;
+ u32 name_end;
+
+ attr_len = le32_to_cpu(a->length);
+ min_len = offsetof(struct attr_record, data.non_resident.initialized_size) +
+ sizeof(a->data.non_resident.initialized_size);
+
+ /* Sparse and compressed attributes have the extra compressed_size field */
+ if (a->flags & (ATTR_IS_SPARSE | ATTR_COMPRESSION_MASK))
+ min_len += sizeof(a->data.non_resident.compressed_size);
+
+ if (attr_len < min_len)
+ return false;
+
+ mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
+ if (mp_offset < min_len || mp_offset > attr_len)
+ return false;
+
+ if (a->name_length) {
+ name_offset = le16_to_cpu(a->name_offset);
+
+ if (name_offset < min_len || name_offset >= attr_len)
+ return false;
+
+ name_end = name_offset + a->name_length * sizeof(__le16);
+ if (name_end > attr_len || name_end > mp_offset)
+ return false;
+ }
+
+ /* Ensure there's room for the compressed_size field if needed. */
+ if (!(a->flags & (ATTR_IS_SPARSE | ATTR_COMPRESSION_MASK)) &&
+ attr_len - mp_offset <
+ sizeof(a->data.non_resident.compressed_size))
+ return false;
+
+ return true;
+}
+
+static bool ntfs_attr_value_is_valid(struct ntfs_volume *vol,
+ const struct attr_record *a,
+ const u64 mft_no)
+{
+ struct ntfs_resident_attr_value value;
+ u32 min_len;
+
+ if (a->non_resident) {
+ if (ntfs_attr_type_is_resident_only(a->type))
+ goto corrupt;
+ if (!ntfs_non_resident_attr_value_is_valid(a))
+ goto corrupt;
+ return true;
+ }
+
+ if (!ntfs_resident_attr_value_get(a, &value))
+ goto corrupt;
+
+ min_len = ntfs_resident_attr_min_value_length(a->type);
+ if (min_len && value.len < min_len)
+ goto corrupt;
+
+ switch (a->type) {
+ case AT_FILE_NAME:
+ if (!ntfs_file_name_attr_value_is_valid(value.data, value.len))
+ goto corrupt;
+ break;
+ case AT_VOLUME_NAME:
+ if (!ntfs_volume_name_attr_value_is_valid(value.len))
+ goto corrupt;
+ break;
+ case AT_INDEX_ROOT:
+ if (!ntfs_index_root_attr_value_is_valid(value.data, value.len))
+ goto corrupt;
+ break;
+ }
+ return true;
+
+corrupt:
+ ntfs_error(vol->sb,
+ "Corrupt %#x attribute in MFT record %llu\n",
+ le32_to_cpu(a->type), mft_no);
+ return false;
+}
+
/*
* ntfs_attr_find - find (next) attribute in mft record
* @type: attribute type to find
@@ -672,6 +842,9 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
__le16 *upcase = vol->upcase;
u32 upcase_len = vol->upcase_len;
unsigned int space;
+ u16 name_offset;
+ u32 attr_len;
+ u32 name_size;
/*
* Iterate over attributes in mft record starting at @ctx->attr, or the
@@ -699,8 +872,25 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
return -ENOENT;
if (unlikely(!a->length))
break;
- if (type == AT_UNUSED)
+ if (a->name_length) {
+ name_offset = le16_to_cpu(a->name_offset);
+ attr_len = le32_to_cpu(a->length);
+ name_size = a->name_length * sizeof(__le16);
+
+ if (name_offset > attr_len ||
+ attr_len - name_offset < name_size) {
+ ntfs_error(vol->sb,
+ "Corrupt attribute name in MFT record %llu\n",
+ ctx->ntfs_ino->mft_no);
+ break;
+ }
+ }
+
+ if (type == AT_UNUSED) {
+ if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no))
+ break;
return 0;
+ }
if (a->type != type)
continue;
/*
@@ -712,14 +902,6 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
if (a->name_length)
return -ENOENT;
} else {
- if (a->name_length && ((le16_to_cpu(a->name_offset) +
- a->name_length * sizeof(__le16)) >
- le32_to_cpu(a->length))) {
- ntfs_error(vol->sb, "Corrupt attribute name in MFT record %llu\n",
- ctx->ntfs_ino->mft_no);
- break;
- }
-
if (!ntfs_are_names_equal(name, name_len,
(__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)),
a->name_length, ic, upcase, upcase_len)) {
@@ -749,37 +931,8 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
}
}
- /* Validate attribute's value offset/length */
- if (!a->non_resident) {
- u32 min_len;
- u32 value_length = le32_to_cpu(a->data.resident.value_length);
- u16 value_offset = le16_to_cpu(a->data.resident.value_offset);
-
- if (value_length > le32_to_cpu(a->length) ||
- value_offset > le32_to_cpu(a->length) - value_length)
- break;
-
- min_len = ntfs_resident_attr_min_value_length(a->type);
- if (min_len && value_length < min_len) {
- ntfs_error(vol->sb,
- "Too small %#x resident attribute value in MFT record %lld\n",
- le32_to_cpu(a->type), (long long)ctx->ntfs_ino->mft_no);
- break;
- }
- } else {
- u32 min_len;
- u16 mp_offset;
-
- min_len = offsetof(struct attr_record, data.non_resident.initialized_size) +
- sizeof(a->data.non_resident.initialized_size);
- if (le32_to_cpu(a->length) < min_len)
- break;
-
- mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
- if (mp_offset < min_len ||
- mp_offset > le32_to_cpu(a->length))
- break;
- }
+ if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no))
+ break;
/*
* The names match or @name not present and attribute is
@@ -845,11 +998,71 @@ char *ntfs_attr_name_get(const struct ntfs_volume *vol, const __le16 *uname,
return NULL;
}
+/*
+ * ntfs_attr_list_entry_is_valid - sanity check one $ATTRIBUTE_LIST entry
+ * @ale: the attribute-list entry to check
+ * @al_end: end of the attribute-list buffer @ale lives in
+ *
+ * Verify that @ale is a well-formed attr_list_entry wholly contained in
+ * [.., @al_end): its fixed header must lie in range before any field is
+ * dereferenced, its length must be a multiple of 8 that covers the fixed
+ * header plus the name, the name must lie within the buffer, the entry must
+ * be in use and carry a live MFT reference. Return true if valid.
+ */
+bool ntfs_attr_list_entry_is_valid(const struct attr_list_entry *ale,
+ const u8 *al_end)
+{
+ const u8 *al = (const u8 *)ale;
+ u16 ale_len;
+
+ /* The fixed header must be in bounds before it is parsed. */
+ if (al + offsetof(struct attr_list_entry, name) > al_end)
+ return false;
+ ale_len = le16_to_cpu(ale->length);
+ /* On-disk entries are 8-byte aligned (see struct attr_list_entry). */
+ if (ale_len & 7)
+ return false;
+ if (ale->name_offset != sizeof(struct attr_list_entry))
+ return false;
+ if ((u32)ale->name_offset +
+ (u32)ale->name_length * sizeof(__le16) > ale_len ||
+ al + ale_len > al_end)
+ return false;
+ if (ale->type == AT_UNUSED)
+ return false;
+ if (MSEQNO_LE(ale->mft_reference) == 0)
+ return false;
+ return true;
+}
+
+/*
+ * ntfs_attr_list_is_valid - sanity check an in-memory $ATTRIBUTE_LIST
+ * @al_start: start of the attribute list buffer
+ * @size: length of the attribute list in bytes
+ *
+ * Verify that [@al_start, @al_start + @size) is a sequence of valid
+ * attr_list_entry records (see ntfs_attr_list_entry_is_valid()) that tile the
+ * buffer exactly. Return true if valid, false otherwise.
+ */
+bool ntfs_attr_list_is_valid(const u8 *al_start, s64 size)
+{
+ const u8 *al = al_start;
+ const u8 *al_end = al_start + size;
+
+ while (al < al_end) {
+ const struct attr_list_entry *ale =
+ (const struct attr_list_entry *)al;
+
+ if (!ntfs_attr_list_entry_is_valid(ale, al_end))
+ return false;
+ al += le16_to_cpu(ale->length);
+ }
+ return al == al_end;
+}
+
int load_attribute_list(struct ntfs_inode *base_ni, u8 *al_start, const s64 size)
{
struct inode *attr_vi = NULL;
- u8 *al;
- struct attr_list_entry *ale;
if (!al_start || size <= 0)
return -EINVAL;
@@ -871,19 +1084,7 @@ int load_attribute_list(struct ntfs_inode *base_ni, u8 *al_start, const s64 size
}
iput(attr_vi);
- for (al = al_start; al < al_start + size; al += le16_to_cpu(ale->length)) {
- ale = (struct attr_list_entry *)al;
- if (ale->name_offset != sizeof(struct attr_list_entry))
- break;
- if (le16_to_cpu(ale->length) <= ale->name_offset + ale->name_length ||
- al + le16_to_cpu(ale->length) > al_start + size)
- break;
- if (ale->type == AT_UNUSED)
- break;
- if (MSEQNO_LE(ale->mft_reference) == 0)
- break;
- }
- if (al != al_start + size) {
+ if (!ntfs_attr_list_is_valid(al_start, size)) {
ntfs_error(base_ni->vol->sb, "Corrupt attribute list, mft = %llu",
base_ni->mft_no);
return -EIO;
@@ -1139,9 +1340,8 @@ find_attr_list_attr:
* we have reached the right one or the search has failed.
*/
if (lowest_vcn && (u8 *)next_al_entry >= al_start &&
- (u8 *)next_al_entry + 6 < al_end &&
- (u8 *)next_al_entry + le16_to_cpu(
- next_al_entry->length) <= al_end &&
+ ntfs_attr_list_entry_is_valid(next_al_entry,
+ al_end) &&
le64_to_cpu(next_al_entry->lowest_vcn) <=
lowest_vcn &&
next_al_entry->type == al_entry->type &&
@@ -1254,22 +1454,8 @@ do_next_attr_loop:
ctx->attr = a;
- if (a->non_resident) {
- u32 min_len;
- u16 mp_offset;
-
- min_len = offsetof(struct attr_record,
- data.non_resident.initialized_size) +
- sizeof(a->data.non_resident.initialized_size);
-
- if (le32_to_cpu(a->length) < min_len)
- break;
-
- mp_offset =
- le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
- if (mp_offset < min_len || mp_offset > attr_len)
- break;
- }
+ if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no))
+ break;
/*
* If no @val specified or @val specified and it matches, we
@@ -1281,19 +1467,6 @@ do_next_attr_loop:
u32 value_length = le32_to_cpu(a->data.resident.value_length);
u16 value_offset = le16_to_cpu(a->data.resident.value_offset);
- if (attr_len < offsetof(struct attr_record, data.resident.reserved) +
- sizeof(a->data.resident.reserved))
- break;
- if (value_length > attr_len || value_offset > attr_len - value_length)
- break;
-
- value_length = ntfs_resident_attr_min_value_length(a->type);
- if (value_length && le32_to_cpu(a->data.resident.value_length) <
- value_length) {
- pr_err("Too small resident attribute value in MFT record %lld, type %#x\n",
- (long long)ctx->ntfs_ino->mft_no, a->type);
- break;
- }
if (value_length == val_len &&
!memcmp((u8 *)a + value_offset, val, val_len)) {
attr_found:
@@ -1564,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;
@@ -1857,7 +2030,7 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
if (IS_ERR(rl)) {
err = PTR_ERR(rl);
ntfs_debug("Failed to allocate cluster%s, error code %i.",
- ntfs_bytes_to_cluster(vol, new_size) > 1 ? "s" : "",
+ str_plural(ntfs_bytes_to_cluster(vol, new_size)),
err);
goto folio_err_out;
}
@@ -1964,7 +2137,7 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
ni->runlist.count = 0;
write_lock_irqsave(&ni->size_lock, flags);
ni->allocated_size = new_size;
- if (NInoSparse(ni) || NInoCompressed(ni)) {
+ if ((NInoSparse(ni) && !NInoWofCompressed(ni)) || NInoCompressed(ni)) {
ni->itype.compressed.size = ni->allocated_size;
if (a->data.non_resident.compression_unit) {
ni->itype.compressed.block_size = 1U <<
@@ -2327,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;
}
/*
@@ -2466,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;
}
/*
@@ -2744,7 +2917,7 @@ retry:
attr_ni = NULL;
/* Allocate new extent. */
- err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL);
+ err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL, -1);
if (err) {
ntfs_error(sb, "Failed to allocate extent record");
goto err_out;
@@ -2924,12 +3097,12 @@ int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type,
struct ntfs_inode *base_ni;
int err;
- ntfs_debug("Entering for inode %lld, attr 0x%x.\n",
- (unsigned long long)ni->mft_no, type);
-
if (!ni || !ni->vol)
return -EINVAL;
+ ntfs_debug("Entering for inode %lld, attr 0x%x.\n",
+ ni->mft_no, type);
+
if (NInoAttr(ni))
base_ni = ni->ext.base_ntfs_ino;
else
@@ -3377,7 +3550,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
* new extent and move attribute to it.
*/
ni = NULL;
- err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL);
+ err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL, -1);
if (err) {
ntfs_error(sb, "Couldn't allocate MFT record, err : %d", err);
return err;
@@ -3385,8 +3558,13 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
unmap_mft_record(ni);
err = ntfs_attr_record_move_to(ctx, ni);
- if (err)
+ if (err) {
ntfs_error(sb, "Couldn't move attribute to MFT record");
+ if (ntfs_mft_record_free(base_ni->vol, ni))
+ ntfs_error(sb, "Couldn't free empty MFT record");
+ else
+ ntfs_inode_close(ni);
+ }
return err;
}
@@ -3396,7 +3574,8 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
* update allocated and compressed size.
*/
static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
- struct mft_record *m, struct ntfs_attr_search_ctx *ctx)
+ struct mft_record *m, struct ntfs_attr_search_ctx *ctx,
+ struct ntfs_inode *locked_ni, bool defer_attrlist)
{
int sparse, err = 0;
struct ntfs_inode *base_ni;
@@ -3432,6 +3611,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
le16_to_cpu(a->data.non_resident.mapping_pairs_offset) == 8) &&
!(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) {
+ if (defer_attrlist)
+ return -ENOSPC;
if (!NInoAttrList(base_ni)) {
err = ntfs_inode_add_attrlist(base_ni);
if (err)
@@ -3445,7 +3626,7 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
goto out;
}
- err = ntfs_attrlist_update(base_ni);
+ err = ntfs_attrlist_update_locked(base_ni, locked_ni);
if (err)
goto out;
err = -EAGAIN;
@@ -3525,6 +3706,8 @@ out:
* ntfs_attr_update_mapping_pairs - update mapping pairs for ntfs attribute
* @ni: non-resident ntfs inode for which we need update
* @from_vcn: update runlist starting this VCN
+ * @locked_ni: inode whose runlist write lock is already held
+ * @defer_attrlist: return -ENOSPC instead of updating an attribute list
*
* Build mapping pairs from @na->rl and write them to the disk. Also, this
* function updates sparse bit, allocated and compressed size (allocates/frees
@@ -3534,7 +3717,10 @@ out:
* call to this function. Vice-versa @na->compressed_size will be calculated and
* set to correct value during this function.
*/
-int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
+static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
+ s64 from_vcn,
+ struct ntfs_inode *locked_ni,
+ bool defer_attrlist)
{
struct ntfs_attr_search_ctx *ctx;
struct ntfs_inode *base_ni;
@@ -3626,7 +3812,8 @@ retry:
continue;
}
- err = ntfs_attr_update_meta(a, ni, m, ctx);
+ err = ntfs_attr_update_meta(a, ni, m, ctx, locked_ni,
+ defer_attrlist);
if (err < 0) {
if (err == -EAGAIN) {
ntfs_attr_put_search_ctx(ctx);
@@ -3666,18 +3853,28 @@ retry:
*/
if (ni->type == AT_ATTRIBUTE_LIST) {
ntfs_attr_put_search_ctx(ctx);
- if (ntfs_inode_free_space(base_ni, mp_size -
- cur_max_mp_size)) {
- ntfs_debug("Attribute list is too big. Defragment the volume\n");
- return -ENOSPC;
+ ctx = NULL;
+ if (locked_ni == ni || defer_attrlist) {
+ err = -ENOSPC;
+ goto put_err_out;
}
- if (ntfs_attrlist_update(base_ni))
- return -EIO;
+ err = ntfs_inode_free_space(base_ni, mp_size -
+ cur_max_mp_size);
+ if (err)
+ return err;
+ err = ntfs_attrlist_update_locked(
+ base_ni, locked_ni);
+ if (err)
+ return err;
goto retry;
}
/* Add attribute list if it isn't present, and retry. */
if (!NInoAttrList(base_ni)) {
+ if (defer_attrlist) {
+ err = -ENOSPC;
+ goto put_err_out;
+ }
ntfs_attr_put_search_ctx(ctx);
if (ntfs_inode_add_attrlist(base_ni)) {
ntfs_error(sb, "Can not add attrlist");
@@ -3705,13 +3902,21 @@ retry:
}
}
+ if (defer_attrlist &&
+ (ctx->ntfs_ino->nr_extents == -1 ||
+ NInoAttrList(ctx->ntfs_ino)) &&
+ ctx->attr->type != AT_ATTRIBUTE_LIST) {
+ err = -ENOSPC;
+ goto put_err_out;
+ }
+
/* Update lowest vcn. */
a->data.non_resident.lowest_vcn = cpu_to_le64(stop_vcn);
mark_mft_record_dirty(ctx->ntfs_ino);
if ((ctx->ntfs_ino->nr_extents == -1 || NInoAttrList(ctx->ntfs_ino)) &&
ctx->attr->type != AT_ATTRIBUTE_LIST) {
ctx->al_entry->lowest_vcn = cpu_to_le64(stop_vcn);
- err = ntfs_attrlist_update(base_ni);
+ err = ntfs_attrlist_update_locked(base_ni, locked_ni);
if (err)
goto put_err_out;
}
@@ -3798,7 +4003,10 @@ retry:
unsigned int de_cnt = 0;
/* Allocate new mft record. */
- err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL);
+ err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL,
+ base_ni->mft_no == FILE_MFT &&
+ ni->type == AT_DATA &&
+ ni->name == AT_UNNAMED ? stop_vcn : -1);
if (err) {
ntfs_error(sb, "Failed to allocate extent record");
goto put_err_out;
@@ -3883,6 +4091,19 @@ put_err_out:
return err;
}
+int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni,
+ s64 from_vcn,
+ struct ntfs_inode *locked_ni)
+{
+ return __ntfs_attr_update_mapping_pairs(ni, from_vcn, locked_ni,
+ false);
+}
+
+int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
+{
+ return ntfs_attr_update_mapping_pairs_locked(ni, from_vcn, NULL);
+}
+
/*
* ntfs_attr_make_resident - convert a non-resident to a resident attribute
* @ni: open ntfs attribute to make resident
@@ -4016,7 +4237,9 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc
*
* Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes.
*/
-static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize)
+static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni,
+ const s64 newsize,
+ struct ntfs_inode *locked_ni)
{
struct ntfs_volume *vol;
struct ntfs_attr_search_ctx *ctx;
@@ -4024,6 +4247,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
s64 nr_freed_clusters;
int err;
struct ntfs_inode *base_ni;
+ bool runlist_locked = locked_ni == ni;
ntfs_debug("Inode 0x%llx attr 0x%x new size %lld\n",
(unsigned long long)ni->mft_no, ni->type, (long long)newsize);
@@ -4069,18 +4293,24 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
* clusters if there is a change.
*/
if (ntfs_bytes_to_cluster(vol, ni->allocated_size) != first_free_vcn) {
- struct ntfs_attr_search_ctx *ctx;
+ /*
+ * ntfs_cluster_free() and ntfs_rl_truncate_nolock()
+ * both require this lock.
+ */
+ if (!runlist_locked)
+ down_write(&ni->runlist.lock);
err = ntfs_attr_map_whole_runlist(ni);
if (err) {
ntfs_debug("Eeek! ntfs_attr_map_whole_runlist failed.\n");
- return err;
+ goto unlock_runlist;
}
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx) {
ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
- return -ENOMEM;
+ err = -ENOMEM;
+ goto unlock_runlist;
}
/* Deallocate all clusters starting with the first free one. */
@@ -4088,7 +4318,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
if (nr_freed_clusters < 0) {
ntfs_debug("Eeek! Freeing of clusters failed. Aborting...\n");
ntfs_attr_put_search_ctx(ctx);
- return (int)nr_freed_clusters;
+ err = (int)nr_freed_clusters;
+ goto unlock_runlist;
}
ntfs_attr_put_search_ctx(ctx);
@@ -4101,7 +4332,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
kvfree(ni->runlist.rl);
ni->runlist.rl = NULL;
ntfs_error(vol->sb, "Eeek! Run list truncation failed.\n");
- return -EIO;
+ err = -EIO;
+ goto unlock_runlist;
}
/* Prepare to mapping pairs update. */
@@ -4117,11 +4349,13 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9;
/* Write mapping pairs for new runlist. */
- err = ntfs_attr_update_mapping_pairs(ni, 0 /*first_free_vcn*/);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err) {
ntfs_debug("Eeek! Mapping pairs update failed. Leaving inconstant metadata. Run chkdsk.\n");
- return err;
+ goto unlock_runlist;
}
+ if (!runlist_locked)
+ up_write(&ni->runlist.lock);
}
/* Get the first attribute record. */
@@ -4147,13 +4381,27 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
ni->initialized_size = newsize;
ctx->attr->data.non_resident.initialized_size = cpu_to_le64(newsize);
}
+
+ /*
+ * Drop any page-cache folios that now lie beyond the shrunk
+ * attribute. The clusters backing them have just been freed and the
+ * runlist truncated, so leaving stale dirty folios around makes a
+ * later writeback map a vcn past the new allocation, which fails with
+ * -ENOENT and loses the write.
+ */
+ truncate_inode_pages(VFS_I(ni)->i_mapping, newsize);
+
/* Update data size in the index. */
if (ni->type == AT_DATA && ni->name == AT_UNNAMED)
NInoSetFileNameDirty(ni);
/* If the attribute now has zero size, make it resident. */
if (!newsize && !NInoEncrypted(ni) && !NInoCompressed(ni)) {
+ if (!runlist_locked)
+ down_write(&ni->runlist.lock);
err = ntfs_attr_make_resident(ni, ctx);
+ if (!runlist_locked)
+ up_write(&ni->runlist.lock);
if (err) {
/* If couldn't make resident, just continue. */
if (err != -EPERM)
@@ -4170,6 +4418,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
put_err_out:
ntfs_attr_put_search_ctx(ctx);
return err;
+
+unlock_runlist:
+ if (!runlist_locked)
+ up_write(&ni->runlist.lock);
+ return err;
}
/*
@@ -4178,13 +4431,14 @@ put_err_out:
* @prealloc_size: preallocation size (in bytes) to which to expand the attribute
* @newsize: new size (in bytes) to which to expand the attribute
* @holes: how to create a hole if expanding
- * @need_lock: whether mrec lock is needed or not
+ * @locked_ni: inode whose runlist lock is already held
*
* Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes,
* by allocating new clusters.
*/
static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsize,
- const s64 prealloc_size, unsigned int holes, bool need_lock)
+ const s64 prealloc_size, unsigned int holes,
+ struct ntfs_inode *locked_ni)
{
s64 lcn_seek_from;
s64 first_free_vcn;
@@ -4331,13 +4585,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
ntfs_bytes_to_cluster(vol, ni->allocated_size),
first_free_vcn -
ntfs_bytes_to_cluster(vol, ni->allocated_size),
- lcn_seek_from, DATA_ZONE, false, false, false);
+ lcn_seek_from, DATA_ZONE, false,
+ ni->type == AT_ATTRIBUTE_LIST, false);
if (IS_ERR(rl)) {
ntfs_debug("Cluster allocation failed (%lld)",
(long long)first_free_vcn -
ntfs_bytes_to_cluster(vol, ni->allocated_size));
return PTR_ERR(rl);
}
+ /*
+ * A contiguous ATTRIBUTE_LIST allocation keeps its mapping
+ * pairs small enough to fit in the base MFT record. The
+ * allocator can return a short run when contiguity was
+ * requested, so discard it and retry normally if necessary.
+ */
+ if (ni->type == AT_ATTRIBUTE_LIST &&
+ (rl->vcn != ntfs_bytes_to_cluster(vol,
+ ni->allocated_size) ||
+ rl->length != first_free_vcn -
+ ntfs_bytes_to_cluster(vol, ni->allocated_size) ||
+ rl[1].length)) {
+ ntfs_cluster_free_from_rl(vol, rl);
+ kvfree(rl);
+ rl = ntfs_cluster_alloc(vol,
+ ntfs_bytes_to_cluster(vol,
+ ni->allocated_size),
+ first_free_vcn -
+ ntfs_bytes_to_cluster(vol,
+ ni->allocated_size),
+ lcn_seek_from, DATA_ZONE, false,
+ false, false);
+ if (IS_ERR(rl))
+ return PTR_ERR(rl);
+ }
}
if (!NInoCompressed(ni)) {
@@ -4356,7 +4636,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
/* Prepare to mapping pairs update. */
ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(
+ ni, 0, locked_ni);
if (err) {
ntfs_debug("Mapping pairs update failed");
goto rollback;
@@ -4400,11 +4681,11 @@ rollback:
ntfs_debug("Leaking clusters");
/* Now, truncate the runlist itself. */
- if (need_lock)
+ if (ni != locked_ni)
down_write(&ni->runlist.lock);
err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist,
ntfs_bytes_to_cluster(vol, org_alloc_size));
- if (need_lock)
+ if (ni != locked_ni)
up_write(&ni->runlist.lock);
if (err2) {
/*
@@ -4418,11 +4699,11 @@ rollback:
/* Prepare to mapping pairs update. */
ni->allocated_size = org_alloc_size;
/* Restore mapping pairs. */
- if (need_lock)
+ if (ni != locked_ni)
down_read(&ni->runlist.lock);
- if (ntfs_attr_update_mapping_pairs(ni, 0))
+ if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true))
ntfs_error(sb, "Failed to restore old mapping pairs");
- if (need_lock)
+ if (ni != locked_ni)
up_read(&ni->runlist.lock);
if (NInoSparse(ni) || NInoCompressed(ni)) {
@@ -4527,7 +4808,8 @@ attr_resize_again:
mark_mft_record_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
/* Resize non-resident attribute */
- return ntfs_non_resident_attr_expand(attr_ni, newsize, prealloc_size, holes, true);
+ return ntfs_non_resident_attr_expand(
+ attr_ni, newsize, prealloc_size, holes, NULL);
} else if (err != -ENOSPC && err != -EPERM) {
ntfs_error(sb, "Failed to make attribute non-resident");
goto put_err_out;
@@ -4538,10 +4820,12 @@ attr_resize_again:
while (!(err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx))) {
struct inode *tvi;
struct attr_record *a;
+ u32 value_len;
a = ctx->attr;
if (a->non_resident || a->type == AT_ATTRIBUTE_LIST)
continue;
+ value_len = le32_to_cpu(a->data.resident.value_length);
if (ntfs_attr_can_be_non_resident(vol, a->type))
continue;
@@ -4553,6 +4837,8 @@ attr_resize_again:
if (le32_to_cpu(a->length) <= (sizeof(struct attr_record) - sizeof(s64)) +
((a->name_length * sizeof(__le16) + 7) & ~7) + 8)
continue;
+ if (a->type == AT_DATA && !value_len)
+ continue;
if (a->type == AT_DATA)
tvi = ntfs_iget(sb, base_ni->mft_no);
@@ -4565,8 +4851,7 @@ attr_resize_again:
continue;
}
- if (ntfs_attr_make_non_resident(NTFS_I(tvi),
- le32_to_cpu(ctx->attr->data.resident.value_length))) {
+ if (ntfs_attr_make_non_resident(NTFS_I(tvi), value_len)) {
iput(tvi);
continue;
}
@@ -4645,7 +4930,7 @@ attr_resize_again:
}
/* Allocate new mft record. */
- err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL);
+ err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL, -1);
if (err) {
ntfs_error(sb, "Couldn't allocate MFT record");
goto put_err_out;
@@ -4699,13 +4984,14 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
if (NInoNonResident(ni)) {
if (newsize > i_size) {
down_write(&ni->runlist.lock);
- err = ntfs_non_resident_attr_expand(ni, newsize, 0,
- NVolDisableSparse(ni->vol) ?
- HOLES_NO : HOLES_OK,
- false);
+ err = ntfs_non_resident_attr_expand(
+ ni, newsize, 0,
+ NVolDisableSparse(ni->vol) ?
+ HOLES_NO : HOLES_OK, ni);
up_write(&ni->runlist.lock);
} else
- err = ntfs_non_resident_attr_shrink(ni, newsize);
+ err = ntfs_non_resident_attr_shrink(
+ ni, newsize, NULL);
} else
err = ntfs_resident_attr_resize(ni, newsize, 0,
NVolDisableSparse(ni->vol) ?
@@ -4714,7 +5000,9 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
return err;
}
-int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size)
+int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize,
+ const s64 prealloc_size,
+ struct ntfs_inode *locked_ni)
{
int err = 0;
@@ -4727,7 +5015,8 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n",
(unsigned long long)ni->mft_no, ni->type, newsize);
- if (ni->data_size == newsize) {
+ if (ni->data_size == newsize &&
+ (!prealloc_size || prealloc_size <= ni->allocated_size)) {
ntfs_debug("Size is already ok\n");
return 0;
}
@@ -4742,10 +5031,11 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
}
if (NInoNonResident(ni)) {
- if (newsize > ni->data_size)
- err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size,
- NVolDisableSparse(ni->vol) ?
- HOLES_NO : HOLES_OK, true);
+ if (newsize > ni->data_size || prealloc_size > ni->allocated_size)
+ err = ntfs_non_resident_attr_expand(
+ ni, newsize, prealloc_size,
+ NVolDisableSparse(ni->vol) ?
+ HOLES_NO : HOLES_OK, locked_ni);
} else
err = ntfs_resident_attr_resize(ni, newsize, prealloc_size,
NVolDisableSparse(ni->vol) ?
@@ -4756,6 +5046,12 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
return err;
}
+int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize,
+ const s64 prealloc_size)
+{
+ return ntfs_attr_expand_locked(ni, newsize, prealloc_size, NULL);
+}
+
/*
* ntfs_attr_truncate_i - resize an ntfs attribute
* @ni: open ntfs inode to resize
@@ -4768,7 +5064,9 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
* newly allocated space is marked as not initialised and no real allocation
* on disk is performed.
*/
-int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes)
+int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize,
+ unsigned int holes,
+ struct ntfs_inode *locked_ni)
{
int err;
@@ -4802,15 +5100,23 @@ int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int
if (NInoNonResident(ni)) {
if (newsize > ni->data_size)
- err = ntfs_non_resident_attr_expand(ni, newsize, 0, holes, true);
+ err = ntfs_non_resident_attr_expand(
+ ni, newsize, 0, holes, locked_ni);
else
- err = ntfs_non_resident_attr_shrink(ni, newsize);
+ err = ntfs_non_resident_attr_shrink(
+ ni, newsize, locked_ni);
} else
err = ntfs_resident_attr_resize(ni, newsize, 0, holes);
ntfs_debug("Return status %d\n", err);
return err;
}
+int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize,
+ unsigned int holes)
+{
+ return ntfs_attr_truncate_i_locked(ni, newsize, holes, NULL);
+}
+
/*
* Resize an attribute, creating a hole if relevant
*/
@@ -4828,10 +5134,11 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
struct ntfs_volume *vol = ni->vol;
struct ntfs_attr_search_ctx *ctx;
struct runlist_element *rl, *rlc;
+ struct runlist_element *old_rl = NULL;
s64 vcn = vcn_start, lcn, clu_count;
s64 lcn_seek_from = -1;
int err = 0;
- size_t new_rl_count;
+ size_t new_rl_count, old_rl_count;
err = ntfs_attr_map_whole_runlist(ni);
if (err)
@@ -4924,6 +5231,19 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
WARN_ON(rlc->vcn != vcn);
lcn = rlc->lcn;
clu_count = rlc->length;
+ old_rl_count = ni->runlist.count;
+ old_rl = kmemdup(ni->runlist.rl,
+ old_rl_count * sizeof(*old_rl), GFP_NOFS);
+ if (!old_rl) {
+ err = -ENOMEM;
+ if (ntfs_cluster_free_from_rl(vol, rlc)) {
+ ntfs_error(vol->sb,
+ "Failed to free cluster allocation after runlist backup failure.");
+ NVolSetErrors(vol);
+ }
+ kvfree(rlc);
+ goto out;
+ }
rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count);
if (IS_ERR(rl)) {
@@ -4947,15 +5267,32 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
if (update_mp) {
ntfs_attr_reinit_search_ctx(ctx);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err) {
int err2;
err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx);
- if (err2 < 0)
+ if (err2 < 0 || err2 != clu_count) {
ntfs_error(vol->sb,
- "Failed to free cluster allocation. Leaving inconstant metadata.\n");
- goto out;
+ "Failed to free cluster allocation. Leaving inconsistent metadata.\n");
+ NVolSetErrors(vol);
+ goto out;
+ }
+
+ /*
+ * Restore the runlist before repairing the on-disk
+ * mapping pairs.
+ */
+ kvfree(ni->runlist.rl);
+ ni->runlist.rl = old_rl;
+ ni->runlist.count = old_rl_count;
+ old_rl = NULL;
+ if (ntfs_attr_update_mapping_pairs_locked(
+ ni, 0, ni)) {
+ ntfs_error(vol->sb,
+ "Failed to restore mapping pairs after allocation rollback.\n");
+ NVolSetErrors(vol);
+ }
}
} else {
VFS_I(ni)->i_blocks += clu_count << (vol->cluster_size_bits - 9);
@@ -4967,6 +5304,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
*lcn_count = clu_count;
*balloc = true;
out:
+ kvfree(old_rl);
ntfs_attr_put_search_ctx(ctx);
return err;
}
@@ -5105,6 +5443,7 @@ int ntfs_attr_remove(struct ntfs_inode *ni, const __le32 type, __le16 *name,
* On success a buffer is allocated with the content of the attribute
* and which needs to be freed when it's not needed anymore. If the
* @data_size parameter is non-NULL then the data size is set there.
+ * On error, an ERR_PTR() containing the negative error code is returned.
*/
void *ntfs_attr_readall(struct ntfs_inode *ni, const __le32 type,
__le16 *name, u32 name_len, s64 *data_size)
@@ -5119,6 +5458,7 @@ void *ntfs_attr_readall(struct ntfs_inode *ni, const __le32 type,
bmp_vi = ntfs_attr_iget(VFS_I(ni), type, name, name_len);
if (IS_ERR(bmp_vi)) {
+ ret = ERR_PTR(PTR_ERR(bmp_vi));
ntfs_debug("ntfs_attr_iget failed");
goto err_exit;
}
@@ -5128,17 +5468,21 @@ void *ntfs_attr_readall(struct ntfs_inode *ni, const __le32 type,
(bmp_ni->type != AT_BITMAP ||
bmp_ni->data_size > ((ni->vol->nr_clusters + 7) >> 3))) {
ntfs_error(sb, "Invalid attribute data size");
+ ret = ERR_PTR(-EIO);
goto out;
}
data = kvmalloc(bmp_ni->data_size, GFP_NOFS);
- if (!data)
+ if (!data) {
+ ret = ERR_PTR(-ENOMEM);
goto out;
+ }
size = ntfs_inode_attr_pread(VFS_I(bmp_ni), 0, bmp_ni->data_size,
(u8 *)data);
if (size != bmp_ni->data_size) {
ntfs_error(sb, "ntfs_attr_pread failed");
+ ret = size < 0 ? ERR_PTR((int)size) : ERR_PTR(-EIO);
kvfree(data);
goto out;
}
@@ -5179,6 +5523,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
ret = ntfs_attr_map_whole_runlist(ni);
if (ret) {
up_write(&ni->runlist.lock);
+ kfree(hole_rl);
return ret;
}
@@ -5203,7 +5548,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
ni->data_size += ntfs_cluster_to_bytes(vol, len);
if (ntfs_cluster_to_bytes(vol, start_vcn) < ni->initialized_size)
ni->initialized_size += ntfs_cluster_to_bytes(vol, len);
- ret = ntfs_attr_update_mapping_pairs(ni, 0);
+ ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
if (ret)
return ret;
@@ -5288,7 +5633,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
}
if (ni->allocated_size > 0) {
- ret = ntfs_attr_update_mapping_pairs(ni, 0);
+ ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (ret) {
up_write(&ni->runlist.lock);
goto out_rl;
@@ -5366,7 +5711,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
ni->runlist.rl = rl;
ni->runlist.count = new_rl_count;
- ret = ntfs_attr_update_mapping_pairs(ni, 0);
+ ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
if (ret) {
kvfree(punch_rl);
@@ -5390,6 +5735,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
s64 old_data_size;
s64 vcn_start, vcn_end, vcn_uninit, vcn, try_alloc_cnt;
s64 lcn, alloc_cnt;
+ s64 rl_lcn, rl_length, rl_vcn;
int err = 0;
struct runlist_element *rl;
bool balloc;
@@ -5469,19 +5815,23 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
while (vcn < vcn_uninit) {
down_read(&ni->runlist.lock);
rl = ntfs_attr_find_vcn_nolock(ni, vcn, NULL);
- up_read(&ni->runlist.lock);
if (IS_ERR(rl)) {
+ up_read(&ni->runlist.lock);
err = PTR_ERR(rl);
goto out;
}
+ rl_lcn = rl->lcn;
+ rl_length = rl->length;
+ rl_vcn = rl->vcn;
+ up_read(&ni->runlist.lock);
- if (rl->lcn > 0) {
- vcn += rl->length - (vcn - rl->vcn);
- } else if (rl->lcn == LCN_DELALLOC || rl->lcn == LCN_HOLE) {
- try_alloc_cnt = min(rl->length - (vcn - rl->vcn),
+ if (rl_lcn > 0) {
+ vcn += rl_length - (vcn - rl_vcn);
+ } else if (rl_lcn == LCN_DELALLOC || rl_lcn == LCN_HOLE) {
+ try_alloc_cnt = min(rl_length - (vcn - rl_vcn),
vcn_uninit - vcn);
- if (rl->lcn == LCN_DELALLOC) {
+ if (rl_lcn == LCN_DELALLOC) {
vcn += try_alloc_cnt;
continue;
}
@@ -5496,14 +5846,17 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
if (err)
goto out;
- err = ntfs_dio_zero_range(VFS_I(ni),
- lcn << vol->cluster_size_bits,
- alloc_cnt << vol->cluster_size_bits);
- if (err > 0)
- goto out;
+ if (balloc) {
+ err = ntfs_dio_zero_range(VFS_I(ni),
+ lcn << vol->cluster_size_bits,
+ alloc_cnt <<
+ vol->cluster_size_bits);
+ if (err)
+ goto out;
+ }
if (signal_pending(current))
- goto out;
+ goto signal_out;
vcn += alloc_cnt;
try_alloc_cnt -= alloc_cnt;
@@ -5524,7 +5877,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;
@@ -5534,7 +5887,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
if (NInoRunlistDirty(ni)) {
mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
down_write(&ni->runlist.lock);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err)
ntfs_error(ni->vol->sb, "Updating mapping pairs failed");
else
@@ -5550,4 +5903,8 @@ out_unmap:
mutex_unlock(&ni->mrec_lock);
out:
return err >= 0 ? 0 : err;
+signal_out:
+ if (!err)
+ err = -EINTR;
+ goto out;
}
diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h
index f7acc7986b09..6b4fa9f57640 100644
--- a/fs/ntfs/attrib.h
+++ b/fs/ntfs/attrib.h
@@ -71,6 +71,10 @@ int ntfs_attr_lookup(const __le32 type, const __le16 *name,
const u32 name_len, const u32 ic,
const s64 lowest_vcn, const u8 *val, const u32 val_len,
struct ntfs_attr_search_ctx *ctx);
+bool ntfs_attr_list_entry_is_valid(const struct attr_list_entry *ale,
+ const u8 *al_end);
+bool ntfs_attr_list_is_valid(const u8 *al_start, s64 size);
+
int load_attribute_list(struct ntfs_inode *base_ni,
u8 *al_start, const s64 size);
@@ -108,7 +112,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
const s64 i_size);
int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size);
+int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize,
+ const s64 prealloc_size,
+ struct ntfs_inode *locked_ni);
int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes);
+int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize,
+ unsigned int holes,
+ struct ntfs_inode *locked_ni);
int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize);
int ntfs_attr_rm(struct ntfs_inode *ni);
int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name,
@@ -129,6 +139,9 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
__le16 *name, u8 name_len, u8 *val, u32 size,
__le16 flags);
int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn);
+int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni,
+ s64 from_vcn,
+ struct ntfs_inode *locked_ni);
struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn);
/*
diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c
index bd501e8a628c..bb191953dcb1 100644
--- a/fs/ntfs/attrlist.c
+++ b/fs/ntfs/attrlist.c
@@ -12,6 +12,9 @@
#include "mft.h"
#include "attrib.h"
#include "attrlist.h"
+#include "lcnalloc.h"
+
+#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024)
/*
* ntfs_attrlist_need - check whether inode need attribute list
@@ -51,11 +54,164 @@ int ntfs_attrlist_need(struct ntfs_inode *ni)
return 0;
}
-int ntfs_attrlist_update(struct ntfs_inode *base_ni)
+/*
+ * Repack the $MFT/$ATTRIBUTE_LIST data into one run.
+ *
+ * The mapping pairs for an $ATTRIBUTE_LIST must remain in the base MFT
+ * record. Once that record has no room left, extending a fragmented list
+ * can require one more mapping-pairs byte than the record can hold. There
+ * is no attribute that can legally be moved out in that state: $STANDARD_
+ * INFORMATION, $ATTRIBUTE_LIST, and the first $MFT/$DATA extent all have to
+ * stay in the base record. Move the list data to one contiguous run. The
+ * caller supplies the minimum allocation size so a recovery can use the
+ * smallest useful run while normal updates can still request the maximum
+ * legal list size as a reserve.
+ */
+static int ntfs_attrlist_repack(struct inode *attr_vi,
+ struct ntfs_inode *attr_ni, s64 min_alloc_size,
+ struct ntfs_inode *locked_ni)
+{
+ struct ntfs_volume *vol = attr_ni->vol;
+ struct runlist_element *old_rl, *new_rl;
+ u8 *data = NULL;
+ s64 data_size, alloc_size, nr_clusters, written;
+ s64 old_alloc_size;
+ size_t old_rl_count, new_rl_count;
+ unsigned long flags;
+ int err, restore_err;
+ if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) ||
+ min_alloc_size < 0)
+ return -EINVAL;
+ /* The buffered I/O below can reacquire the attribute runlist lock. */
+ if (attr_ni == locked_ni)
+ return -ENOSPC;
+
+ err = ntfs_attr_map_whole_runlist(attr_ni);
+ if (err)
+ return err;
+
+ data_size = attr_ni->data_size;
+ if (data_size < 0)
+ return -EIO;
+
+ if (data_size) {
+ data = kvmalloc(data_size, GFP_NOFS);
+ if (!data)
+ return -ENOMEM;
+
+ written = ntfs_inode_attr_pread(attr_vi, 0, data_size, data);
+ if (written != data_size) {
+ err = written < 0 ? (int)written : -EIO;
+ goto out_free_data;
+ }
+ }
+
+ old_alloc_size = attr_ni->allocated_size;
+ alloc_size = max_t(s64, old_alloc_size, min_alloc_size);
+ nr_clusters = ntfs_bytes_to_cluster(vol,
+ alloc_size + vol->cluster_size - 1);
+ if (nr_clusters <= 0) {
+ err = -EFBIG;
+ goto out_free_data;
+ }
+
+ /* A single run keeps the mapping pairs at the minimum size. */
+ new_rl = ntfs_cluster_alloc(vol, 0, nr_clusters, -1, DATA_ZONE,
+ true, true, false);
+ if (IS_ERR(new_rl)) {
+ err = PTR_ERR(new_rl);
+ goto out_free_data;
+ }
+
+ new_rl_count = 0;
+ if (new_rl->vcn == 0 && new_rl->length == nr_clusters &&
+ !new_rl[1].length)
+ new_rl_count = 2;
+
+ if (new_rl_count != 2) {
+ ntfs_cluster_free_from_rl(vol, new_rl);
+ kvfree(new_rl);
+ err = -ENOSPC;
+ goto out_free_data;
+ }
+ old_rl = attr_ni->runlist.rl;
+ old_rl_count = attr_ni->runlist.count;
+ down_write(&attr_ni->runlist.lock);
+ attr_ni->runlist.rl = new_rl;
+ attr_ni->runlist.count = new_rl_count;
+ up_write(&attr_ni->runlist.lock);
+
+ write_lock_irqsave(&attr_ni->size_lock, flags);
+ attr_ni->allocated_size = ntfs_cluster_to_bytes(vol, nr_clusters);
+ write_unlock_irqrestore(&attr_ni->size_lock, flags);
+
+ /* Populate the replacement extent before publishing its mapping pairs. */
+ if (data_size) {
+ written = ntfs_inode_attr_pwrite(attr_vi, 0, data_size, data, true);
+ if (written != data_size) {
+ err = written < 0 ? (int)written : -EIO;
+ goto restore_old_runlist;
+ }
+ }
+
+ err = ntfs_attr_update_mapping_pairs_locked(attr_ni, 0, locked_ni);
+ if (err)
+ goto restore_old_runlist;
+
+ /* The new mapping is now authoritative; release the old data runs. */
+ if (ntfs_cluster_free_from_rl(vol, old_rl)) {
+ ntfs_error(vol->sb,
+ "Failed to free old ATTRIBUTE_LIST extent: inode %#llx",
+ (long long)attr_ni->mft_no);
+ NVolSetErrors(vol);
+ }
+ kvfree(old_rl);
+ kvfree(data);
+ return 0;
+
+restore_old_runlist:
+ down_write(&attr_ni->runlist.lock);
+ attr_ni->runlist.rl = old_rl;
+ attr_ni->runlist.count = old_rl_count;
+ up_write(&attr_ni->runlist.lock);
+
+ write_lock_irqsave(&attr_ni->size_lock, flags);
+ attr_ni->allocated_size = old_alloc_size;
+ write_unlock_irqrestore(&attr_ni->size_lock, flags);
+
+ restore_err = ntfs_attr_update_mapping_pairs_locked(
+ attr_ni, 0, locked_ni);
+ if (restore_err) {
+ ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)",
+ restore_err);
+ NVolSetErrors(vol);
+ }
+
+ ntfs_cluster_free_from_rl(vol, new_rl);
+ kvfree(new_rl);
+ err = err ? err : restore_err;
+
+out_free_data:
+ kvfree(data);
+ return err;
+}
+
+int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni,
+ struct ntfs_inode *locked_ni)
{
struct inode *attr_vi;
struct ntfs_inode *attr_ni;
- int err;
+ s64 written;
+ int err, retry_err;
+
+ /*
+ * generic_shutdown_super() clears SB_ACTIVE before evicting cached
+ * inodes. Do not look up the attribute-list inode after SB_ACTIVE has
+ * been cleared; it may already be I_FREEING, and waiting on it can
+ * self-deadlock.
+ */
+ if (!(VFS_I(base_ni)->i_sb->s_flags & SB_ACTIVE))
+ return -EIO;
attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0);
if (IS_ERR(attr_vi)) {
@@ -63,23 +219,66 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
return err;
}
attr_ni = NTFS_I(attr_vi);
+ /* Truncation and page-cache writes can reacquire this runlist lock. */
+ if (attr_ni == locked_ni) {
+ iput(attr_vi);
+ return -ENOSPC;
+ }
- err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO);
- if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) {
- err = ntfs_attr_truncate(attr_ni, 0);
- if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) {
+ err = ntfs_attr_truncate_i_locked(
+ attr_ni, base_ni->attr_list_size, HOLES_NO, locked_ni);
+ if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT &&
+ NInoNonResident(attr_ni)) {
+ retry_err = ntfs_attrlist_repack(attr_vi, attr_ni,
+ base_ni->attr_list_size, locked_ni);
+ if (retry_err) {
+ ntfs_error(base_ni->vol->sb, "Failed to repack attribute list");
iput(attr_vi);
+ return retry_err;
+ }
+
+ retry_err = ntfs_attr_truncate_i_locked(
+ attr_ni, base_ni->attr_list_size,
+ HOLES_NO, locked_ni);
+ if (retry_err) {
ntfs_error(base_ni->vol->sb,
- "Failed to truncate attribute list of inode %#llx",
- (long long)base_ni->mft_no);
- return -EIO;
+ "Failed to resize attribute list after repack");
+ iput(attr_vi);
+ return retry_err;
}
} else if (err) {
iput(attr_vi);
ntfs_error(base_ni->vol->sb,
"Failed to truncate attribute list of inode %#llx",
(long long)base_ni->mft_no);
- return -EIO;
+ return err;
+ }
+
+ /*
+ * Reserve the maximum legal list size while the MFT metadata area is
+ * still easy to allocate contiguously. This prevents a later list entry
+ * from needing another mapping-pairs byte in the full base MFT record.
+ * Failure to obtain the optional reserve must not reject the current
+ * metadata update; the repack retry above remains available if needed.
+ */
+ if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) &&
+ attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) {
+ retry_err = ntfs_attr_expand_locked(
+ attr_ni, base_ni->attr_list_size,
+ NTFS_MAX_ATTR_LIST_SIZE, locked_ni);
+ if (retry_err == -ENOSPC) {
+ retry_err = ntfs_attrlist_repack(
+ attr_vi, attr_ni,
+ NTFS_MAX_ATTR_LIST_SIZE, locked_ni);
+ if (retry_err == -ENOSPC)
+ retry_err = 0;
+ }
+ if (retry_err) {
+ ntfs_error(base_ni->vol->sb,
+ "Failed to reserve attribute list space");
+ iput(attr_vi);
+ return retry_err;
+ }
}
i_size_write(attr_vi, base_ni->attr_list_size);
@@ -87,14 +286,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
NInoSetAttrListNonResident(base_ni);
- if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
- base_ni->attr_list, false) !=
- base_ni->attr_list_size) {
+ written = ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
+ base_ni->attr_list, false);
+ if (written != base_ni->attr_list_size) {
+ err = written < 0 ? (int)written : -EIO;
iput(attr_vi);
ntfs_error(base_ni->vol->sb,
"Failed to write attribute list of inode %#llx",
(long long)base_ni->mft_no);
- return -EIO;
+ return err;
}
NInoSetAttrListDirty(base_ni);
@@ -102,6 +302,11 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
return 0;
}
+int ntfs_attrlist_update(struct ntfs_inode *base_ni)
+{
+ return ntfs_attrlist_update_locked(base_ni, NULL);
+}
+
/*
* ntfs_attrlist_entry_add - add an attribute list attribute entry
* @ni: opened ntfs inode, which contains that attribute
@@ -118,16 +323,16 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
int entry_len, entry_offset, err;
struct mft_record *ni_mrec;
u8 *old_al;
-
- ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n",
- (long long) ni->mft_no,
- (unsigned int) le32_to_cpu(attr->type));
+ __le64 lowest_vcn;
if (!ni || !attr) {
ntfs_debug("Invalid arguments.\n");
return -EINVAL;
}
+ ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n",
+ ni->mft_no, (unsigned int) le32_to_cpu(attr->type));
+
ni_mrec = map_mft_record(ni);
if (IS_ERR(ni_mrec)) {
ntfs_debug("Invalid arguments.\n");
@@ -159,17 +364,21 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
ntfs_error(ni->vol->sb, "Failed to get search context");
goto err_out;
}
+ if (attr->non_resident)
+ lowest_vcn = attr->data.non_resident.lowest_vcn;
+ else
+ lowest_vcn = 0;
err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *)
((u8 *)attr + le16_to_cpu(attr->name_offset)) :
AT_UNNAMED, attr->name_length, CASE_SENSITIVE,
- (attr->non_resident) ? le64_to_cpu(attr->data.non_resident.lowest_vcn) :
- 0, (attr->non_resident) ? NULL : ((u8 *)attr +
+ le64_to_cpu(lowest_vcn),
+ (attr->non_resident) ? NULL : ((u8 *)attr +
le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ?
0 : le32_to_cpu(attr->data.resident.value_length), ctx);
if (!err) {
/* Found some extent, check it to be before new extent. */
- if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) {
+ if (ctx->al_entry->lowest_vcn == lowest_vcn) {
err = -EEXIST;
ntfs_debug("Such attribute already present in the attribute list.\n");
ntfs_attr_put_search_ctx(ctx);
diff --git a/fs/ntfs/attrlist.h b/fs/ntfs/attrlist.h
index 1892a3934d3a..10cc2cc8e208 100644
--- a/fs/ntfs/attrlist.h
+++ b/fs/ntfs/attrlist.h
@@ -16,5 +16,7 @@ int ntfs_attrlist_need(struct ntfs_inode *ni);
int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr);
int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx);
int ntfs_attrlist_update(struct ntfs_inode *base_ni);
+int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni,
+ struct ntfs_inode *locked_ni);
#endif /* defined _NTFS_ATTRLIST_H */
diff --git a/fs/ntfs/bdev-io.c b/fs/ntfs/bdev-io.c
index 67e65c88d681..4f27eed3b072 100644
--- a/fs/ntfs/bdev-io.c
+++ b/fs/ntfs/bdev-io.c
@@ -33,8 +33,8 @@ int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t s
unsigned int done = 0, added;
int error;
struct bio *bio;
- enum req_op op;
- sector_t sector = start >> SECTOR_SHIFT;
+ blk_opf_t op;
+ sector_t sector = ntfs_bytes_to_bio_sector(start);
if (start & (SECTOR_SIZE - 1))
return -EINVAL;
@@ -66,7 +66,7 @@ int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t s
error = submit_bio_wait(bio);
bio_put(bio);
- if (op == REQ_OP_READ)
+ if ((op & REQ_OP_MASK) == REQ_OP_READ)
invalidate_kernel_vmap_range(data, size);
return error;
}
@@ -97,6 +97,8 @@ int ntfs_bdev_write(struct super_block *sb, void *buf, loff_t start, size_t size
idx_end++;
for (; idx < idx_end; idx++, from = 0) {
+ u32 len;
+
folio = read_mapping_folio(sb->s_bdev->bd_mapping, idx, NULL);
if (IS_ERR(folio)) {
ntfs_error(sb, "Unable to read %ld page", idx);
@@ -105,9 +107,10 @@ int ntfs_bdev_write(struct super_block *sb, void *buf, loff_t start, size_t size
offset = (loff_t)idx << PAGE_SHIFT;
to = min_t(u32, end - offset, PAGE_SIZE);
+ len = to - from;
- memcpy_to_folio(folio, from, buf + buf_off, to);
- buf_off += to;
+ memcpy_to_folio(folio, from, buf + buf_off, len);
+ buf_off += len;
folio_mark_uptodate(folio);
folio_mark_dirty(folio);
folio_put(folio);
diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c
index b1436b3151b9..5a4457551306 100644
--- a/fs/ntfs/bitmap.c
+++ b/fs/ntfs/bitmap.c
@@ -40,7 +40,7 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range)
end_cluster = vol->nr_clusters;
}
- ra = kzalloc(sizeof(*ra), GFP_NOFS);
+ ra = kzalloc_obj(*ra, GFP_NOFS);
if (!ra)
return -ENOMEM;
@@ -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);
diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c
index 76bd806b41ed..075b57fc1de6 100644
--- a/fs/ntfs/compress.c
+++ b/fs/ntfs/compress.c
@@ -21,6 +21,7 @@
#include <linux/slab.h>
#include "attrib.h"
+#include "ntfs_codec.h"
#include "inode.h"
#include "debug.h"
#include "ntfs.h"
@@ -97,26 +98,6 @@ void free_compression_buffers(void)
}
/*
- * zero_partial_compressed_page - zero out of bounds compressed page region
- * @page: page to zero
- * @initialized_size: initialized size of the attribute
- */
-static void zero_partial_compressed_page(struct page *page,
- const s64 initialized_size)
-{
- u8 *kp = page_address(page);
- unsigned int kp_ofs;
-
- ntfs_debug("Zeroing page region outside initialized size.");
- if (((s64)page->__folio_index << PAGE_SHIFT) >= initialized_size) {
- clear_page(kp);
- return;
- }
- kp_ofs = initialized_size & ~PAGE_MASK;
- memset(kp + kp_ofs, 0, PAGE_SIZE - kp_ofs);
-}
-
-/*
* handle_bounds_compressed_page - test for&handle out of bounds compressed page
* @page: page to check and handle
* @i_size: file size
@@ -125,9 +106,21 @@ static void zero_partial_compressed_page(struct page *page,
static inline void handle_bounds_compressed_page(struct page *page,
const loff_t i_size, const s64 initialized_size)
{
- if ((page->__folio_index >= (initialized_size >> PAGE_SHIFT)) &&
- (initialized_size < i_size))
- zero_partial_compressed_page(page, initialized_size);
+ loff_t pos = page_offset(page);
+
+ if ((pos + PAGE_SIZE > initialized_size) &&
+ (initialized_size < i_size)) {
+ size_t offset;
+
+ ntfs_debug("Zeroing page region outside initialized size.");
+ if (pos >= initialized_size)
+ offset = 0;
+ else
+ offset = offset_in_page(initialized_size);
+ zero_user_segment(page, offset, PAGE_SIZE);
+ } else {
+ flush_dcache_page(page);
+ }
}
/*
@@ -185,6 +178,7 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
/* Variables for uncompressed data / destination. */
struct page *dp; /* Current destination page being worked on. */
+ u8 *dp_kaddr; /* Local kmap for the current destination page. */
u8 *dp_addr; /* Current pointer into dp. */
u8 *dp_sb_start; /* Start of current sub-block in dp. */
u8 *dp_sb_end; /* End of current sb in dp (dp_sb_start + NTFS_SB_SIZE). */
@@ -199,6 +193,7 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
/* Default error code. */
int err = -EOVERFLOW;
+ dp_kaddr = NULL;
ntfs_debug("Entering, cb_size = 0x%x.", cb_size);
do_next_sb:
ntfs_debug("Beginning sub-block at offset = 0x%zx in the cb.",
@@ -231,8 +226,6 @@ return_error:
*/
handle_bounds_compressed_page(dp, i_size,
initialized_size);
- flush_dcache_page(dp);
- kunmap_local(page_address(dp));
SetPageUptodate(dp);
unlock_page(dp);
if (di == xpage)
@@ -278,7 +271,8 @@ return_error:
}
/* We have a valid destination page. Setup the destination pointers. */
- dp_addr = (u8 *)page_address(dp) + do_sb_start;
+ dp_kaddr = kmap_local_page(dp);
+ dp_addr = dp_kaddr + do_sb_start;
/* Now, we are ready to process the current sub-block (sb). */
if (!(le16_to_cpup((__le16 *)cb) & NTFS_SB_IS_COMPRESSED)) {
@@ -299,6 +293,8 @@ return_error:
/* Advance destination position to next sub-block. */
*dest_ofs += NTFS_SB_SIZE;
*dest_ofs &= ~PAGE_MASK;
+ kunmap_local(dp_kaddr);
+ dp_kaddr = NULL;
if (!(*dest_ofs)) {
finalize_page:
/*
@@ -333,6 +329,8 @@ do_next_tag:
}
/* We have finished the current sub-block. */
*dest_ofs &= ~PAGE_MASK;
+ kunmap_local(dp_kaddr);
+ dp_kaddr = NULL;
if (!(*dest_ofs))
goto finalize_page;
goto do_next_sb;
@@ -352,7 +350,7 @@ do_next_tag:
u8 *dp_back_addr;
/* Check if we are done / still in range. */
- if (cb >= cb_sb_end || dp_addr > dp_sb_end)
+ if (cb >= cb_sb_end || dp_addr >= dp_sb_end)
break;
/* Determine token type and parse appropriately.*/
@@ -438,6 +436,8 @@ do_next_tag:
goto do_next_tag;
return_overflow:
+ if (dp_kaddr)
+ kunmap_local(dp_kaddr);
ntfs_error(NULL, "Failed. Returning -EOVERFLOW.");
goto return_error;
}
@@ -465,14 +465,14 @@ int ntfs_read_compressed_block(struct folio *folio)
struct page *page = &folio->page;
loff_t i_size;
s64 initialized_size;
- struct address_space *mapping = page->mapping;
+ struct address_space *mapping = folio->mapping;
struct ntfs_inode *ni = NTFS_I(mapping->host);
struct ntfs_volume *vol = ni->vol;
struct super_block *sb = vol->sb;
struct runlist_element *rl;
unsigned long flags;
u8 *cb, *cb_pos, *cb_end;
- unsigned long offset, index = page->__folio_index;
+ unsigned long offset, index = folio->index;
u32 cb_size = ni->itype.compressed.block_size;
u64 cb_size_mask = cb_size - 1UL;
s64 vcn;
@@ -514,8 +514,8 @@ int ntfs_read_compressed_block(struct folio *folio)
return -EIO;
}
- pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_NOFS);
- completed_pages = kmalloc_array(nr_pages + 1, sizeof(int), GFP_NOFS);
+ pages = kmalloc_objs(struct page *, nr_pages, GFP_NOFS);
+ completed_pages = kmalloc_objs(int, nr_pages + 1, GFP_NOFS);
if (unlikely(!pages || !completed_pages)) {
kfree(pages);
@@ -566,7 +566,6 @@ int ntfs_read_compressed_block(struct folio *folio)
* least wasting our time.
*/
if (!PageDirty(page) && (!PageUptodate(page))) {
- kmap_local_page(page);
continue;
}
unlock_page(page);
@@ -652,8 +651,7 @@ lock_retry_remap:
}
lock_page(lpage);
- memcpy(cb_pos, page_address(lpage) + page_ofs,
- vol->cluster_size);
+ memcpy_from_page(cb_pos, lpage, page_ofs, vol->cluster_size);
unlock_page(lpage);
put_page(lpage);
cb_pos += vol->cluster_size;
@@ -692,14 +690,7 @@ lock_retry_remap:
for (; cur_page < cb_max_page; cur_page++) {
page = pages[cur_page];
if (page) {
- if (likely(!cur_ofs))
- clear_page(page_address(page));
- else
- memset(page_address(page) + cur_ofs, 0,
- PAGE_SIZE -
- cur_ofs);
- flush_dcache_page(page);
- kunmap_local(page_address(page));
+ memzero_page(page, cur_ofs, PAGE_SIZE - cur_ofs);
SetPageUptodate(page);
unlock_page(page);
if (cur_page == xpage)
@@ -717,8 +708,7 @@ lock_retry_remap:
if (cb_max_ofs && cb_pos < cb_end) {
page = pages[cur_page];
if (page)
- memset(page_address(page) + cur_ofs, 0,
- cb_max_ofs - cur_ofs);
+ memzero_page(page, cur_ofs, cb_max_ofs - cur_ofs);
/*
* No need to update cb_pos at this stage:
* cb_pos += cb_max_ofs - cur_ofs;
@@ -739,7 +729,7 @@ lock_retry_remap:
for (; cur_page < cb_max_page; cur_page++) {
page = pages[cur_page];
if (page)
- memcpy(page_address(page) + cur_ofs, cb_pos,
+ memcpy_to_page(page, cur_ofs, cb_pos,
PAGE_SIZE - cur_ofs);
cb_pos += PAGE_SIZE - cur_ofs;
cur_ofs = 0;
@@ -750,7 +740,7 @@ lock_retry_remap:
if (cb_max_ofs && cb_pos < cb_end) {
page = pages[cur_page];
if (page)
- memcpy(page_address(page) + cur_ofs, cb_pos,
+ memcpy_to_page(page, cur_ofs, cb_pos,
cb_max_ofs - cur_ofs);
cb_pos += cb_max_ofs - cur_ofs;
cur_ofs = cb_max_ofs;
@@ -767,8 +757,6 @@ lock_retry_remap:
*/
handle_bounds_compressed_page(page, i_size,
initialized_size);
- flush_dcache_page(page);
- kunmap_local(page_address(page));
SetPageUptodate(page);
unlock_page(page);
if (cur2_page == xpage)
@@ -787,7 +775,7 @@ lock_retry_remap:
unsigned int prev_cur_page = cur_page;
ntfs_debug("Found compressed compression block.");
- err = ntfs_decompress(pages, completed_pages, &cur_page,
+ err = ntfs_lznt1_codec_ops.decompress_pages(pages, completed_pages, &cur_page,
&cur_ofs, cb_max_page, cb_max_ofs, xpage,
&xpage_done, cb_pos, cb_size - (cb_pos - cb),
i_size, initialized_size);
@@ -804,7 +792,6 @@ lock_retry_remap:
page = pages[prev_cur_page];
if (page) {
flush_dcache_page(page);
- kunmap_local(page_address(page));
unlock_page(page);
if (prev_cur_page != xpage)
put_page(page);
@@ -822,14 +809,15 @@ lock_retry_remap:
for (cur_page = 0; cur_page < max_page; cur_page++) {
page = pages[cur_page];
if (page) {
+ folio = page_folio(page);
+
ntfs_error(vol->sb,
"Still have pages left! Terminating them with extreme prejudice. Inode 0x%llx, page index 0x%lx.",
- ni->mft_no, page->__folio_index);
- flush_dcache_page(page);
- kunmap_local(page_address(page));
- unlock_page(page);
+ ni->mft_no, folio->index);
+ flush_dcache_folio(folio);
+ folio_unlock(folio);
if (cur_page != xpage)
- put_page(page);
+ folio_put(folio);
pages[cur_page] = NULL;
}
}
@@ -864,7 +852,6 @@ err_out:
page = pages[i];
if (page) {
flush_dcache_page(page);
- kunmap_local(page_address(page));
unlock_page(page);
if (i != xpage)
put_page(page);
@@ -908,6 +895,12 @@ struct compress_context {
s16 prev[NTFS_SB_SIZE];
};
+struct ntfs_compress_workspace {
+ struct page **pages;
+ char *outbuf;
+ unsigned int nr_pages;
+};
+
/*
* Hash the next 3-byte sequence in the input buffer
*/
@@ -1084,12 +1077,11 @@ static void ntfs_skip_position(struct compress_context *pctx, const int i)
*
* Returns the size of the compressed block, including the
* header (minimal size is 2, maximum size is 4098)
- * 0 if an error has been met.
+ * A negative error code if an error has been met.
*/
-static unsigned int ntfs_compress_block(const char *inbuf, const int bufsize,
- char *outbuf)
+static int ntfs_compress_block(struct compress_context *pctx,
+ const char *inbuf, const int bufsize, char *outbuf)
{
- struct compress_context *pctx;
int i; /* current position */
int j; /* end of best match from current position */
int k; /* end of best match from next position */
@@ -1104,10 +1096,6 @@ static unsigned int ntfs_compress_block(const char *inbuf, const int bufsize,
int tag; /* current value of tag */
int ntag; /* count of bits still undefined in tag */
- pctx = kvzalloc(sizeof(struct compress_context), GFP_NOFS);
- if (!pctx)
- return -ENOMEM;
-
/*
* All hash chains start as empty. The special value '-1' indicates the
* end of each hash chain.
@@ -1263,22 +1251,76 @@ static unsigned int ntfs_compress_block(const char *inbuf, const int bufsize,
xout = NTFS_SB_SIZE + 2;
}
- /*
- * Free the compression context and return the total number of bytes
- * written to 'outbuf'.
- */
- kvfree(pctx);
return xout;
}
+static int ntfs_compress_workspace_init(struct ntfs_inode *ni,
+ struct ntfs_compress_workspace *ws)
+{
+ unsigned int size, i;
+
+ size = ni->itype.compressed.block_size + 2 *
+ (ni->itype.compressed.block_size / NTFS_SB_SIZE) + 2;
+ ws->nr_pages = DIV_ROUND_UP(size, PAGE_SIZE);
+ ws->pages = kzalloc_objs(*ws->pages, ws->nr_pages, GFP_NOFS);
+ if (!ws->pages)
+ return -ENOMEM;
+
+ for (i = 0; i < ws->nr_pages; i++) {
+ ws->pages[i] = alloc_page(GFP_NOFS);
+ if (!ws->pages[i])
+ goto free_pages;
+ }
+
+ ws->outbuf = vmap(ws->pages, ws->nr_pages, VM_MAP, PAGE_KERNEL);
+ if (!ws->outbuf)
+ goto free_pages;
+ return 0;
+
+free_pages:
+ while (i)
+ put_page(ws->pages[--i]);
+ kfree(ws->pages);
+ return -ENOMEM;
+}
+
+static void ntfs_compress_workspace_free(struct ntfs_compress_workspace *ws)
+{
+ unsigned int i;
+
+ vunmap(ws->outbuf);
+ for (i = 0; i < ws->nr_pages; i++)
+ put_page(ws->pages[i]);
+ kfree(ws->pages);
+}
+
+static void ntfs_copy_cb(struct page **pages, int pages_per_cb,
+ unsigned int page_offset,
+ struct ntfs_compress_workspace *ws, unsigned int bytes)
+{
+ unsigned int copied = 0, i;
+
+ for (i = 0; i < pages_per_cb && copied < bytes; i++) {
+ unsigned int offset = i ? 0 : page_offset;
+ unsigned int len = min(bytes - copied, PAGE_SIZE - offset);
+ void *addr = kmap_local_page(pages[i]);
+
+ memcpy(ws->outbuf + copied, addr + offset, len);
+ kunmap_local(addr);
+ copied += len;
+ }
+}
+
static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
- int pages_per_cb)
+ int pages_per_cb, unsigned int page_offset,
+ struct compress_context *ctx, struct ntfs_compress_workspace *ws)
{
struct ntfs_volume *vol = ni->vol;
- char *outbuf = NULL, *pbuf, *inbuf;
- u32 compsz, p, insz = pages_per_cb << PAGE_SHIFT;
+ char *outbuf = ws->outbuf, *pbuf;
+ u32 compsz, p, insz = ni->itype.compressed.block_size;
s32 rounded, bio_size;
- unsigned int sz, bsz;
+ int sz;
+ unsigned int bsz;
bool fail = false, allzeroes;
/* a single compressed zero */
static char onezero[] = {0x01, 0xb0, 0x00, 0x00};
@@ -1286,54 +1328,36 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
static char twozeroes[] = {0x02, 0xb0, 0x00, 0x00, 0x00};
/* more compressed zeroes, to be followed by some count */
static char morezeroes[] = {0x03, 0xb0, 0x02, 0x00};
- struct page **pages_disk = NULL, *pg;
- s64 bio_lcn;
+ s64 bio_lcn, bio_pos;
struct runlist_element *rlc, *rl;
int i, err;
- int pages_count = (round_up(ni->itype.compressed.block_size + 2 *
- (ni->itype.compressed.block_size / NTFS_SB_SIZE) + 2, PAGE_SIZE)) / PAGE_SIZE;
+ u32 cb_clusters = ni->itype.compressed.block_clusters;
size_t new_rl_count;
struct bio *bio = NULL;
- loff_t new_length;
+ loff_t cb_pos, new_length;
s64 new_vcn;
- inbuf = vmap(pages, pages_per_cb, VM_MAP, PAGE_KERNEL_RO);
- if (!inbuf)
- return -ENOMEM;
-
- /* may need 2 extra bytes per block and 2 more bytes */
- pages_disk = kcalloc(pages_count, sizeof(struct page *), GFP_NOFS);
- if (!pages_disk) {
- vunmap(inbuf);
- return -ENOMEM;
- }
-
- for (i = 0; i < pages_count; i++) {
- pg = alloc_page(GFP_KERNEL);
- if (!pg) {
- err = -ENOMEM;
- goto out;
- }
- pages_disk[i] = pg;
- lock_page(pg);
- kmap_local_page(pg);
- }
-
- outbuf = vmap(pages_disk, pages_count, VM_MAP, PAGE_KERNEL);
- if (!outbuf) {
- err = -ENOMEM;
- goto out;
- }
-
compsz = 0;
allzeroes = true;
for (p = 0; (p < insz) && !fail; p += NTFS_SB_SIZE) {
+ unsigned int input_offset = page_offset + p;
+ unsigned int page_idx = input_offset >> PAGE_SHIFT;
+ const char *input;
+ void *addr;
+
if ((p + NTFS_SB_SIZE) < insz)
bsz = NTFS_SB_SIZE;
else
bsz = insz - p;
pbuf = &outbuf[compsz];
- sz = ntfs_compress_block(&inbuf[p], bsz, pbuf);
+ addr = kmap_local_page(pages[page_idx]);
+ input = addr + offset_in_page(input_offset);
+ sz = ntfs_lznt1_codec_ops.compress_subblock(ctx, input, bsz, pbuf);
+ kunmap_local(addr);
+ if (sz < 0) {
+ err = sz;
+ goto out;
+ }
/* fail if all the clusters (or more) are needed */
if (!sz || ((compsz + sz + vol->cluster_size + 2) >
ni->itype.compressed.block_size))
@@ -1360,28 +1384,25 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
}
}
+ cb_pos = pos & ~((loff_t)ni->itype.compressed.block_size - 1);
+ new_vcn = ntfs_bytes_to_cluster(vol, cb_pos);
+
if (!fail && !allzeroes) {
outbuf[compsz++] = 0;
outbuf[compsz++] = 0;
rounded = ((compsz - 1) | (vol->cluster_size - 1)) + 1;
memset(&outbuf[compsz], 0, rounded - compsz);
bio_size = rounded;
- pages = pages_disk;
} else if (allzeroes) {
- err = 0;
+ err = ntfs_non_resident_attr_punch_hole(ni, new_vcn, cb_clusters);
goto out;
} else {
+ ntfs_copy_cb(pages, pages_per_cb, page_offset, ws, insz);
bio_size = insz;
}
- new_vcn = ntfs_bytes_to_cluster(vol,
- pos & ~((loff_t)ni->itype.compressed.block_size - 1));
new_length = ntfs_bytes_to_cluster(vol, round_up(bio_size, vol->cluster_size));
- err = ntfs_non_resident_attr_punch_hole(ni, new_vcn, ni->itype.compressed.block_clusters);
- if (err < 0)
- goto out;
-
rlc = ntfs_cluster_alloc(vol, new_vcn, new_length, -1, DATA_ZONE,
false, true, true);
if (IS_ERR(rlc)) {
@@ -1390,74 +1411,56 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
}
bio_lcn = rlc->lcn;
+ 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_bio_sector(bio_pos);
+
+ for (i = 0; bio_size; i++) {
+ unsigned int len = min_t(unsigned int, bio_size, PAGE_SIZE);
+
+ if (bio_add_page(bio, ws->pages[i], len, 0) != len) {
+ err = -EIO;
+ bio_put(bio);
+ goto free_rlc;
+ }
+ bio_size -= len;
+ }
+
+ err = submit_bio_wait(bio);
+ bio_put(bio);
+ if (err)
+ goto free_rlc;
+
+ /* Do not discard the old compression block until the new one is safe. */
+ err = ntfs_non_resident_attr_punch_hole(ni, new_vcn, cb_clusters);
+ if (err)
+ goto free_rlc;
+
down_write(&ni->runlist.lock);
rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count);
if (IS_ERR(rl)) {
up_write(&ni->runlist.lock);
ntfs_error(vol->sb, "Failed to merge runlists");
err = PTR_ERR(rl);
- if (ntfs_cluster_free_from_rl(vol, rlc))
- ntfs_error(vol->sb, "Failed to free hot clusters.");
- kvfree(rlc);
- goto out;
+ goto free_rlc;
}
ni->runlist.count = new_rl_count;
ni->runlist.rl = rl;
+ rlc = NULL;
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
- if (err) {
+ if (err)
err = -EIO;
- goto out;
- }
-
- i = 0;
- while (bio_size > 0) {
- int page_size;
-
- if (bio_size >= PAGE_SIZE) {
- page_size = PAGE_SIZE;
- bio_size -= PAGE_SIZE;
- } else {
- page_size = bio_size;
- bio_size = 0;
- }
-
-setup_bio:
- if (!bio) {
- 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, bio_lcn + i));
- }
-
- if (!bio_add_page(bio, pages[i], page_size, 0)) {
- err = submit_bio_wait(bio);
- bio_put(bio);
- if (err)
- goto out;
- bio = NULL;
- goto setup_bio;
- }
- i++;
- }
+ goto out;
- err = submit_bio_wait(bio);
- bio_put(bio);
+free_rlc:
+ if (ntfs_cluster_free_from_rl(vol, rlc))
+ ntfs_error(vol->sb, "Failed to free hot clusters.");
+ kvfree(rlc);
out:
- vunmap(outbuf);
- for (i = 0; i < pages_count; i++) {
- pg = pages_disk[i];
- if (pg) {
- kunmap_local(page_address(pg));
- unlock_page(pg);
- put_page(pg);
- }
- }
- kfree(pages_disk);
- vunmap(inbuf);
NInoSetFileNameDirty(ni);
mark_mft_record_dirty(ni);
@@ -1467,31 +1470,39 @@ out:
int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
struct iov_iter *from)
{
+ struct ntfs_compress_workspace ws = {};
+ struct compress_context *ctx;
struct folio *folio;
struct page **pages = NULL, *page;
- int pages_per_cb = ni->itype.compressed.block_size >> PAGE_SHIFT;
+ int pages_per_cb;
int cb_size = ni->itype.compressed.block_size, cb_off, err = 0;
int i, ip;
size_t written = 0;
struct address_space *mapping = VFS_I(ni)->i_mapping;
- if (NInoCompressed(ni) && pos + count > ni->allocated_size) {
- int err;
- loff_t end = pos + count;
-
- err = ntfs_attr_expand(ni, end,
- round_up(end, ni->itype.compressed.block_size));
- if (err)
- return err;
- }
+ pages_per_cb = DIV_ROUND_UP(offset_in_page(pos & ~(cb_size - 1)) +
+ cb_size, PAGE_SIZE);
- pages = kmalloc_array(pages_per_cb, sizeof(struct page *), GFP_NOFS);
+ pages = kmalloc_objs(struct page *, pages_per_cb, GFP_NOFS);
if (!pages)
return -ENOMEM;
+ ctx = kvzalloc_obj(*ctx, GFP_NOFS);
+ if (!ctx) {
+ kfree(pages);
+ return -ENOMEM;
+ }
+ err = ntfs_compress_workspace_init(ni, &ws);
+ if (err) {
+ kvfree(ctx);
+ kfree(pages);
+ return err;
+ }
while (count) {
pgoff_t index;
size_t copied, bytes;
+ unsigned int page_offset;
+ bool full_cb;
int off;
off = pos & (cb_size - 1);
@@ -1500,7 +1511,11 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
bytes = count;
cb_off = pos & ~(cb_size - 1);
+ page_offset = offset_in_page(cb_off);
+ pages_per_cb = DIV_ROUND_UP(page_offset + cb_size, PAGE_SIZE);
index = cb_off >> PAGE_SHIFT;
+ full_cb = !off && bytes == cb_size && !page_offset &&
+ !(cb_size & (PAGE_SIZE - 1));
if (unlikely(fault_in_iov_iter_readable(from, bytes))) {
err = -EFAULT;
@@ -1508,7 +1523,10 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
}
for (i = 0; i < pages_per_cb; i++) {
- folio = read_mapping_folio(mapping, index + i, NULL);
+ if (full_cb)
+ folio = filemap_grab_folio(mapping, index + i);
+ else
+ folio = read_mapping_folio(mapping, index + i, NULL);
if (IS_ERR(folio)) {
for (ip = 0; ip < i; ip++) {
folio_unlock(page_folio(pages[ip]));
@@ -1518,7 +1536,8 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
goto out;
}
- folio_lock(folio);
+ if (!full_cb)
+ folio_lock(folio);
pages[i] = folio_page(folio, 0);
}
@@ -1548,13 +1567,26 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
}
}
- err = ntfs_write_cb(ni, pos, pages, pages_per_cb);
+ if (!copied) {
+ err = -EFAULT;
+ goto release_pages;
+ }
+
+ err = ntfs_write_cb(ni, pos, pages, pages_per_cb, page_offset, ctx, &ws);
+ if (!err && pos + copied > ni->initialized_size) {
+ mutex_lock(&ni->mrec_lock);
+ err = ntfs_attr_set_initialized_size(ni, pos + copied);
+ mutex_unlock(&ni->mrec_lock);
+ }
+release_pages:
for (i = 0; i < pages_per_cb; i++) {
folio = page_folio(pages[i]);
- if (i < ip) {
+ if (!err) {
folio_clear_dirty(folio);
folio_mark_uptodate(folio);
+ } else {
+ folio_clear_uptodate(folio);
}
folio_unlock(folio);
folio_put(folio);
@@ -1570,9 +1602,18 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
}
out:
+ ntfs_compress_workspace_free(&ws);
+ kvfree(ctx);
kfree(pages);
if (err < 0)
written = err;
return written;
}
+
+const struct ntfs_codec_ops ntfs_lznt1_codec_ops = {
+ .id = NTFS_CODEC_LZNT1,
+ .name = "lznt1",
+ .decompress_pages = ntfs_decompress,
+ .compress_subblock = ntfs_compress_block,
+};
diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c
index 20f5c7074bdd..df60138f9b2d 100644
--- a/fs/ntfs/dir.c
+++ b/fs/ntfs/dir.c
@@ -23,6 +23,13 @@
__le16 I30[5] = { cpu_to_le16('$'), cpu_to_le16('I'),
cpu_to_le16('3'), cpu_to_le16('0'), 0 };
+static inline u64 ntfs_check_mref(u64 mref)
+{
+ if (IS_ERR_MREF(mref))
+ return ERR_MREF(-EIO);
+ return mref;
+}
+
/*
* ntfs_lookup_inode_by_name - find an inode in a directory given its name
* @dir_ni: ntfs inode of the directory in which to search for the name
@@ -135,10 +142,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
/* Key length should not be zero if it is not last entry. */
if (!ie->key_length)
goto dir_err_out;
- /* Check the consistency of an index entry */
- if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME,
- dir_ni->mft_no))
- goto dir_err_out;
/*
* We perform a case sensitive comparison and if that matches
* we are done and return the mft reference of the inode (i.e.
@@ -163,8 +166,8 @@ found_it:
*/
if (ie->key.file_name.file_name_type == FILE_NAME_DOS) {
if (!name) {
- name = kmalloc(sizeof(struct ntfs_name),
- GFP_NOFS);
+ name = kmalloc_obj(struct ntfs_name,
+ GFP_NOFS);
if (!name) {
err = -ENOMEM;
goto err_out;
@@ -182,7 +185,7 @@ found_it:
mref = le64_to_cpu(ie->data.dir.indexed_file);
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(dir_ni);
- return mref;
+ return ntfs_check_mref(mref);
}
/*
* For a case insensitive mount, we also perform a case
@@ -277,7 +280,7 @@ found_it:
if (name) {
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(dir_ni);
- return name->mref;
+ return ntfs_check_mref(name->mref);
}
ntfs_debug("Entry not found.");
err = -ENOENT;
@@ -342,43 +345,20 @@ fast_descend_into_child_node:
dir_ni->mft_no);
goto unm_err_out;
}
- /* Catch multi sector transfer fixup errors. */
- if (unlikely(!ntfs_is_indx_record(ia->magic))) {
- ntfs_error(sb,
- "Directory index record with vcn 0x%llx is corrupt. Corrupt inode 0x%llx. Run chkdsk.",
- vcn, dir_ni->mft_no);
- goto unm_err_out;
- }
- if (le64_to_cpu(ia->index_block_vcn) != vcn) {
- ntfs_error(sb,
- "Actual VCN (0x%llx) of index buffer is different from expected VCN (0x%llx). Directory inode 0x%llx is corrupt or driver bug.",
- le64_to_cpu(ia->index_block_vcn),
- vcn, dir_ni->mft_no);
- goto unm_err_out;
- }
- if (le32_to_cpu(ia->index.allocated_size) + 0x18 !=
- dir_ni->itype.index.block_size) {
- ntfs_error(sb,
- "Index buffer (VCN 0x%llx) of directory inode 0x%llx has a size (%u) differing from the directory specified size (%u). Directory inode is corrupt or driver bug.",
- vcn, dir_ni->mft_no,
- le32_to_cpu(ia->index.allocated_size) + 0x18,
- dir_ni->itype.index.block_size);
- goto unm_err_out;
- }
index_end = (u8 *)ia + dir_ni->itype.index.block_size;
if (index_end > kaddr + PAGE_SIZE) {
ntfs_error(sb,
- "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.",
- vcn, dir_ni->mft_no);
+ "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.",
+ vcn, dir_ni->mft_no);
goto unm_err_out;
}
- index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length);
- if (index_end > (u8 *)ia + dir_ni->itype.index.block_size) {
- ntfs_error(sb,
- "Size of index buffer (VCN 0x%llx) of directory inode 0x%llx exceeds maximum size.",
- vcn, dir_ni->mft_no);
+ err = ntfs_index_block_inconsistent(vol, ia,
+ dir_ni->itype.index.block_size,
+ vcn, COLLATION_FILE_NAME,
+ dir_ni->mft_no);
+ if (err)
goto unm_err_out;
- }
+ index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length);
/* The first index entry. */
ie = (struct index_entry *)((u8 *)&ia->index +
le32_to_cpu(ia->index.entries_offset));
@@ -388,15 +368,6 @@ fast_descend_into_child_node:
* reach the last entry.
*/
for (;; ie = (struct index_entry *)((u8 *)ie + le16_to_cpu(ie->length))) {
- /* Bounds checks. */
- if ((u8 *)ie < (u8 *)ia ||
- (u8 *)ie + sizeof(struct index_entry_header) > index_end ||
- (u8 *)ie + sizeof(struct index_entry_header) + le16_to_cpu(ie->key_length) >
- index_end || (u8 *)ie + le16_to_cpu(ie->length) > index_end) {
- ntfs_error(sb, "Index entry out of bounds in directory inode 0x%llx.",
- dir_ni->mft_no);
- goto unm_err_out;
- }
/*
* The last entry cannot contain a name. It can however contain
* a pointer to a child node in the B+tree so we just break out.
@@ -406,10 +377,6 @@ fast_descend_into_child_node:
/* Key length should not be zero if it is not last entry. */
if (!ie->key_length)
goto unm_err_out;
- /* Check the consistency of an index entry */
- if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME,
- dir_ni->mft_no))
- goto unm_err_out;
/*
* We perform a case sensitive comparison and if that matches
* we are done and return the mft reference of the inode (i.e.
@@ -434,8 +401,8 @@ found_it2:
*/
if (ie->key.file_name.file_name_type == FILE_NAME_DOS) {
if (!name) {
- name = kmalloc(sizeof(struct ntfs_name),
- GFP_NOFS);
+ name = kmalloc_obj(struct ntfs_name,
+ GFP_NOFS);
if (!name) {
err = -ENOMEM;
goto unm_err_out;
@@ -453,7 +420,7 @@ found_it2:
mref = le64_to_cpu(ie->data.dir.indexed_file);
kfree(kaddr);
iput(ia_vi);
- return mref;
+ return ntfs_check_mref(mref);
}
/*
* For a case insensitive mount, we also perform a case
@@ -578,7 +545,7 @@ found_it2:
if (name) {
kfree(kaddr);
iput(ia_vi);
- return name->mref;
+ return ntfs_check_mref(name->mref);
}
ntfs_debug("Entry not found.");
err = -ENOENT;
@@ -733,7 +700,7 @@ static int ntfs_ia_blocks_readahead(struct ntfs_inode *ia_ni, loff_t pos)
if (dir_start_index >= dir_end_index)
return 0;
- dir_ra = kzalloc(sizeof(*dir_ra), GFP_NOFS);
+ dir_ra = kzalloc_obj(*dir_ra, GFP_NOFS);
if (!dir_ra)
return -ENOMEM;
@@ -810,7 +777,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
return -ENOMEM;
}
- ra = kzalloc(sizeof(struct file_ra_state), GFP_NOFS);
+ ra = kzalloc_obj(struct file_ra_state, GFP_NOFS);
if (!ra) {
kfree(name);
ntfs_index_ctx_put(ictx);
@@ -846,7 +813,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
goto out;
}
} else if (!private) {
- private = kzalloc(sizeof(struct ntfs_file_private), GFP_KERNEL);
+ private = kzalloc_obj(struct ntfs_file_private);
if (!private) {
err = -ENOMEM;
goto out;
@@ -892,6 +859,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
ictx->vcn_size_bits = vol->cluster_size_bits;
else
ictx->vcn_size_bits = NTFS_BLOCK_SIZE_BITS;
+ ictx->cr = ir->collation_rule;
/* The first index entry. */
next = (struct index_entry *)((u8 *)&ir->index +
@@ -929,13 +897,6 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
if (!next)
break;
nextdir:
- /* Check the consistency of an index entry */
- if (ntfs_index_entry_inconsistent(ictx, vol, next, COLLATION_FILE_NAME,
- ndir->mft_no)) {
- err = -EIO;
- goto out;
- }
-
if (ie_pos < actor->pos) {
ie_pos += le16_to_cpu(next->length);
continue;
@@ -988,7 +949,7 @@ nextdir:
}
if (!nir) {
- nir = kzalloc(sizeof(struct ntfs_index_ra), GFP_KERNEL);
+ nir = kzalloc_obj(struct ntfs_index_ra);
if (nir) {
nir->start_index = index;
nir->count = 1;
@@ -1005,13 +966,14 @@ filldir:
*/
private = file->private_data;
kfree(private->key);
- private->key = kmalloc(le16_to_cpu(next->key_length), GFP_KERNEL);
+ private->key = kmemdup(&next->key.file_name,
+ le16_to_cpu(next->key_length),
+ GFP_KERNEL);
if (!private->key) {
err = -ENOMEM;
goto out;
}
- memcpy(private->key, &next->key.file_name, le16_to_cpu(next->key_length));
private->key_length = next->key_length;
break;
}
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
index c4a4a3e3e599..b4fcfbe2da4c 100644
--- a/fs/ntfs/ea.c
+++ b/fs/ntfs/ea.c
@@ -53,11 +53,11 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name,
loff_t offset, p_ea_size;
unsigned int next;
- if (ea_buf_size < sizeof(struct ea_attr))
- goto out;
-
offset = 0;
do {
+ if (ea_buf_size - offset < sizeof(struct ea_attr))
+ break;
+
p_ea = (const struct ea_attr *)&ea_buf[offset];
next = le32_to_cpu(p_ea->next_entry_offset);
p_ea_size = next ? next : (ea_buf_size - offset);
@@ -122,17 +122,19 @@ static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len,
p_ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
&ea_info_size);
- if (!p_ea_info || ea_info_size != sizeof(struct ea_information)) {
+ if (IS_ERR(p_ea_info))
+ return PTR_ERR(p_ea_info);
+ if (ea_info_size != sizeof(struct ea_information)) {
kvfree(p_ea_info);
- return -ENODATA;
+ return -EIO;
}
ea_info_qlen = le32_to_cpu(p_ea_info->ea_query_length);
kvfree(p_ea_info);
ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size);
- if (!ea_buf)
- return -ENODATA;
+ if (IS_ERR(ea_buf))
+ return PTR_ERR(ea_buf);
if (ea_info_qlen > all_ea_size) {
err = -EIO;
@@ -196,6 +198,9 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
struct ea_attr *p_ea;
u32 ea_info_qsize = 0;
char *ea_buf = NULL;
+ char *new_ea_buf;
+ char *old_ea_buf = NULL;
+ struct ea_information old_ea_info;
size_t new_ea_size = ALIGN(struct_size(p_ea, ea_name, 1 + name_len + val_size), 4);
s64 ea_off, ea_info_size, all_ea_size, ea_size;
@@ -205,10 +210,22 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
if (ntfs_attr_exist(ni, AT_EA_INFORMATION, AT_UNNAMED, 0)) {
p_ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
&ea_info_size);
- if (!p_ea_info || ea_info_size != sizeof(struct ea_information))
+ if (IS_ERR(p_ea_info)) {
+ err = PTR_ERR(p_ea_info);
+ p_ea_info = NULL;
+ goto out;
+ }
+ if (ea_info_size != sizeof(struct ea_information)) {
+ err = -EIO;
goto out;
+ }
ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size);
+ if (IS_ERR(ea_buf)) {
+ err = PTR_ERR(ea_buf);
+ ea_buf = NULL;
+ goto out;
+ }
if (!ea_buf) {
ea_info_qsize = 0;
kvfree(p_ea_info);
@@ -218,7 +235,7 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
ea_info_qsize = le32_to_cpu(p_ea_info->ea_query_length);
} else {
create_ea_info:
- p_ea_info = kzalloc(sizeof(struct ea_information), GFP_NOFS);
+ p_ea_info = kzalloc_obj(struct ea_information, GFP_NOFS);
if (!p_ea_info)
return -ENOMEM;
@@ -249,6 +266,22 @@ create_ea_info:
err = -EEXIST;
goto out;
}
+ if ((flags & XATTR_REPLACE) && !val_size) {
+ old_ea_info = *p_ea_info;
+ old_ea_buf = kvmemdup(ea_buf, all_ea_size, GFP_NOFS);
+ if (!old_ea_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+ }
+
+ /* Check the final $EA size before removing the old entry. */
+ if (val_size &&
+ ntfs_attr_size_bounds_check(ni->vol, AT_EA,
+ ea_info_qsize - ea_size + new_ea_size)) {
+ err = -EFBIG;
+ goto out;
+ }
p_ea = (struct ea_attr *)(ea_buf + ea_off);
@@ -267,17 +300,39 @@ create_ea_info:
ea_info_qsize -= ea_size;
p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize);
- err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
- sizeof(struct ea_information), false);
- if (err)
- goto out;
+ if ((flags & XATTR_REPLACE) && !val_size && !ea_info_qsize) {
+ err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0);
+ if (err)
+ goto out;
- err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true);
- if (err)
+ err = ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0);
+ if (err) {
+ /* Restore the original $EA if $EA_INFORMATION removal failed. */
+ ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, old_ea_buf,
+ all_ea_size);
+ ea_info_qsize = le32_to_cpu(old_ea_info.ea_query_length);
+ }
goto out;
+ }
if ((flags & XATTR_REPLACE) && !val_size) {
- /* Remove xattr. */
+ err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize,
+ true);
+ if (err) {
+ ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
+ all_ea_size, false);
+ goto out;
+ }
+
+ err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info,
+ 0, sizeof(struct ea_information), false);
+ if (err) {
+ ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
+ all_ea_size, false);
+ ntfs_write_ea(ni, AT_EA_INFORMATION,
+ (char *)&old_ea_info, 0,
+ sizeof(old_ea_info), false);
+ }
goto out;
}
} else {
@@ -285,22 +340,30 @@ create_ea_info:
err = -ENODATA;
goto out;
}
- }
- kvfree(ea_buf);
+ if (ntfs_attr_size_bounds_check(ni->vol, AT_EA,
+ ea_info_qsize + new_ea_size)) {
+ err = -EFBIG;
+ goto out;
+ }
+ }
alloc_new_ea:
- ea_buf = kzalloc(new_ea_size, GFP_NOFS);
- if (!ea_buf) {
+ new_ea_buf = kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS);
+ if (!new_ea_buf) {
err = -ENOMEM;
goto out;
}
+ if (ea_info_qsize)
+ memcpy(new_ea_buf, ea_buf, ea_info_qsize);
+ kvfree(ea_buf);
+ ea_buf = new_ea_buf;
+ p_ea = (struct ea_attr *)(ea_buf + ea_info_qsize);
/*
* EA and REPARSE_POINT compatibility not checked any more,
* required by Windows 10, but having both may lead to
* problems with earlier versions.
*/
- p_ea = (struct ea_attr *)ea_buf;
memcpy(p_ea->ea_name, name, name_len);
p_ea->ea_name_length = name_len;
p_ea->ea_name[name_len] = 0;
@@ -312,8 +375,7 @@ alloc_new_ea:
p_ea_info->ea_length = cpu_to_le16(ea_packed);
p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize + new_ea_size);
- if (ea_packed > 0xffff ||
- ntfs_attr_size_bounds_check(ni->vol, AT_EA, new_ea_size)) {
+ if (ea_packed > 0xffff) {
err = -EFBIG;
goto out;
}
@@ -322,13 +384,13 @@ alloc_new_ea:
* no EA or EA_INFORMATION : add them
*/
if (!ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
- err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, (char *)p_ea,
- new_ea_size);
+ err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, ea_buf,
+ ea_info_qsize + new_ea_size);
if (err)
goto out;
} else {
- err = ntfs_write_ea(ni, AT_EA, (char *)p_ea, ea_info_qsize,
- new_ea_size, false);
+ err = ntfs_write_ea(ni, AT_EA, ea_buf, 0,
+ ea_info_qsize + new_ea_size, true);
if (err)
goto out;
}
@@ -342,12 +404,15 @@ alloc_new_ea:
*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);
kvfree(p_ea_info);
return err;
@@ -357,37 +422,35 @@ out:
* Check for the presence of an EA "$LXDEV" (used by WSL)
* and return its value as a device address
*/
-int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags)
+int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags,
+ bool *has_lxmod)
{
int err;
__le32 v;
+ *has_lxmod = false;
+
if (!(flags & NTFS_VOL_UID)) {
/* Load uid to lxuid EA */
err = ntfs_get_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v,
sizeof(v));
- if (err < 0)
- return err;
- if (err != sizeof(v))
- return -EIO;
- i_uid_write(inode, le32_to_cpu(v));
+ if (err == sizeof(v))
+ i_uid_write(inode, le32_to_cpu(v));
}
if (!(flags & NTFS_VOL_GID)) {
/* Load gid to lxgid EA */
err = ntfs_get_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v,
sizeof(v));
- if (err < 0)
- return err;
- if (err != sizeof(v))
- return -EIO;
- i_gid_write(inode, le32_to_cpu(v));
+ if (err == sizeof(v))
+ i_gid_write(inode, le32_to_cpu(v));
}
/* Load mode to lxmod EA */
err = ntfs_get_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v, sizeof(v));
if (err == sizeof(v)) {
inode->i_mode = le32_to_cpu(v);
+ *has_lxmod = true;
} else {
/* Everyone gets all permissions. */
inode->i_mode |= 0777;
@@ -467,25 +530,35 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
mutex_lock(&NTFS_I(inode)->mrec_lock);
ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
&ea_info_size);
- if (!ea_info || ea_info_size != sizeof(struct ea_information))
+ if (IS_ERR(ea_info)) {
+ err = PTR_ERR(ea_info);
+ ea_info = NULL;
goto out;
+ }
+ if (ea_info_size != sizeof(struct ea_information)) {
+ err = -EIO;
+ goto out;
+ }
ea_info_qsize = le32_to_cpu(ea_info->ea_query_length);
ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &ea_buf_size);
- if (!ea_buf)
+ if (IS_ERR(ea_buf)) {
+ err = PTR_ERR(ea_buf);
+ ea_buf = NULL;
goto out;
+ }
if (ea_info_qsize > ea_buf_size || ea_info_qsize == 0)
goto out;
- if (ea_info_qsize < sizeof(struct ea_attr)) {
- err = -EIO;
- goto out;
- }
-
offset = 0;
do {
+ if (ea_info_qsize - offset < sizeof(struct ea_attr)) {
+ err = -EIO;
+ goto out;
+ }
+
p_ea = (const struct ea_attr *)&ea_buf[offset];
next = le32_to_cpu(p_ea->next_entry_offset);
ea_size = next ? next : (ea_info_qsize - offset);
@@ -544,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);
@@ -557,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);
@@ -581,7 +654,8 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
struct mft_record *m;
struct attr_record *a;
__le16 new_aflags;
- int mp_size, mp_ofs, name_ofs, arec_size, err;
+ u16 old_name_ofs, old_mp_ofs;
+ int mp_size, mp_ofs, name_ofs, old_arec_size, arec_size, err;
m = map_mft_record(ni);
if (IS_ERR(m))
@@ -613,8 +687,10 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
else
new_aflags &= ~ATTR_IS_COMPRESSED;
- if (new_aflags == a->flags)
- return 0;
+ if (new_aflags == a->flags) {
+ err = 0;
+ goto err_out;
+ }
if ((new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) ==
(ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) {
@@ -623,15 +699,42 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
goto err_out;
}
- if (!a->non_resident)
- goto out;
+ if (!a->non_resident) {
+ if (!(new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))) {
+ err = 0;
+ goto err_out;
+ }
- if (a->data.non_resident.data_size) {
- pr_err("Can't change sparsed/compressed for non-empty file\n");
- err = -EOPNOTSUPP;
- goto err_out;
+ if (le32_to_cpu(a->data.resident.value_length)) {
+ pr_err("Can't change sparse/compressed for non-empty file");
+ err = -EOPNOTSUPP;
+ goto err_out;
+ }
+
+ err = ntfs_attr_make_non_resident(ni, 0);
+ if (err)
+ goto err_out;
+
+ ntfs_attr_reinit_search_ctx(ctx);
+ err = ntfs_attr_lookup(ni->type, ni->name,
+ ni->name_len, CASE_SENSITIVE,
+ 0, NULL, 0, ctx);
+ if (err) {
+ err = -EINVAL;
+ goto err_out;
+ }
+ a = ctx->attr;
+ } else {
+ if (a->data.non_resident.data_size) {
+ pr_err("Can't change sparsed/compressed for non-empty file");
+ err = -EOPNOTSUPP;
+ goto err_out;
+ }
}
+ old_name_ofs = le16_to_cpu(a->name_offset);
+ old_mp_ofs = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
+
if (new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))
name_ofs = (offsetof(struct attr_record,
data.non_resident.compressed_size) +
@@ -649,11 +752,58 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
mp_ofs = (name_ofs + a->name_length * sizeof(__le16) + 7) & ~7;
arec_size = (mp_ofs + mp_size + 7) & ~7;
+ old_arec_size = le32_to_cpu(a->length);
+
+ /*
+ * 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 (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;
+ /*
+ * When compressed/sparse state changes, the non-resident header grows or
+ * shrinks by the compressed_size field. Update the in-record payload layout
+ * to match the new offsets before exposing the new mapping_pairs_offset.
+ */
+ if (arec_size > old_arec_size) {
+ if (mp_ofs != old_mp_ofs)
+ memmove((u8 *)a + mp_ofs, (u8 *)a + old_mp_ofs, mp_size);
+ if (a->name_length)
+ memmove((u8 *)a + name_ofs, (u8 *)a + old_name_ofs,
+ a->name_length * sizeof(__le16));
+ }
+
if (new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) {
a->data.non_resident.compression_unit = 0;
if (new_aflags & ATTR_IS_COMPRESSED || ni->vol->major_ver < 3)
@@ -674,28 +824,31 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
ni->itype.compressed.block_size_bits = 0;
ni->itype.compressed.block_clusters = 0;
}
-
- if (new_aflags & ATTR_IS_SPARSE) {
- NInoSetSparse(ni);
- ni->flags |= FILE_ATTR_SPARSE_FILE;
- }
-
- if (new_aflags & ATTR_IS_COMPRESSED) {
- NInoSetCompressed(ni);
- ni->flags |= FILE_ATTR_COMPRESSED;
- }
} else {
- ni->flags &= ~(FILE_ATTR_SPARSE_FILE | FILE_ATTR_COMPRESSED);
a->data.non_resident.compression_unit = 0;
- NInoClearSparse(ni);
- NInoClearCompressed(ni);
}
a->name_offset = cpu_to_le16(name_ofs);
a->data.non_resident.mapping_pairs_offset = cpu_to_le16(mp_ofs);
-out:
a->flags = new_aflags;
+
+ if (new_aflags & ATTR_IS_SPARSE) {
+ NInoSetSparse(ni);
+ ni->flags |= FILE_ATTR_SPARSE_FILE;
+ } else {
+ NInoClearSparse(ni);
+ ni->flags &= ~FILE_ATTR_SPARSE_FILE;
+ }
+
+ if (new_aflags & ATTR_IS_COMPRESSED) {
+ NInoSetCompressed(ni);
+ ni->flags |= FILE_ATTR_COMPRESSED;
+ } else {
+ NInoClearCompressed(ni);
+ ni->flags &= ~FILE_ATTR_COMPRESSED;
+ }
+
mark_mft_record_dirty(ctx->ntfs_ino);
err_out:
if (ctx)
@@ -704,6 +857,23 @@ err_out:
return err;
}
+static bool ntfs_is_reserved_lxattr(const char *name)
+{
+ return !strcmp(name, "$LXUID") || !strcmp(name, "$LXGID") ||
+ !strcmp(name, "$LXMOD") || !strcmp(name, "$LXDEV");
+}
+
+static int ntfs_validate_fattr(struct ntfs_inode *ni, __le32 fattr)
+{
+ const __le32 wof_flags = FILE_ATTR_SPARSE_FILE |
+ FILE_ATTR_REPARSE_POINT;
+
+ if (NInoWofCompressed(ni) && ((ni->flags ^ fattr) & wof_flags))
+ return -EPERM;
+
+ return 0;
+}
+
static int ntfs_setxattr(const struct xattr_handler *handler,
struct mnt_idmap *idmap, struct dentry *unused,
struct inode *inode, const char *name, const void *value,
@@ -716,12 +886,16 @@ static int ntfs_setxattr(const struct xattr_handler *handler,
if (NVolShutdown(ni->vol))
return -EIO;
+ if (ntfs_is_reserved_lxattr(name) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
if (!strcmp(name, SYSTEM_DOS_ATTRIB)) {
if (sizeof(u8) != size) {
err = -EINVAL;
goto out;
}
- fattr = cpu_to_le32(*(u8 *)value);
+ fattr = cpu_to_le32((le32_to_cpu(ni->flags) & ~0xffU) |
+ *(u8 *)value);
goto set_fattr;
}
@@ -736,6 +910,10 @@ static int ntfs_setxattr(const struct xattr_handler *handler,
else
fattr = cpu_to_le32(*(u32 *)value);
+ err = ntfs_validate_fattr(ni, fattr);
+ if (err)
+ goto out;
+
if (S_ISREG(inode->i_mode)) {
mutex_lock(&ni->mrec_lock);
err = ntfs_new_attr_flags(ni, fattr);
@@ -750,6 +928,10 @@ set_fattr:
else
fattr &= ~FILE_ATTR_DIRECTORY;
+ err = ntfs_validate_fattr(ni, fattr);
+ if (err)
+ goto out;
+
if (ni->flags != fattr) {
ni->flags = fattr;
if (fattr & FILE_ATTR_READONLY)
@@ -768,8 +950,10 @@ set_fattr:
mutex_unlock(&ni->mrec_lock);
out:
- inode_set_ctime_current(inode);
- mark_inode_dirty(inode);
+ if (!err) {
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+ }
return err;
}
diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h
index 1f63bd55e057..acb39c2a6fbc 100644
--- a/fs/ntfs/ea.h
+++ b/fs/ntfs/ea.h
@@ -10,7 +10,8 @@
extern const struct xattr_handler *const ntfs_xattr_handlers[];
int ntfs_ea_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode, dev_t dev);
-int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags);
+int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags,
+ bool *has_lxmod);
int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size,
unsigned int flags);
ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index e8bea22b81a7..007d1614b9ac 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -22,6 +22,7 @@
#include "ea.h"
#include "iomap.h"
#include "bitmap.h"
+#include "volume.h"
#include <linux/filelock.h>
@@ -110,7 +111,8 @@ static int ntfs_trim_prealloc(struct inode *vi)
ntfs_error(vol->sb, "Preallocated block rollback failed");
} else {
ni->allocated_size = ntfs_cluster_to_bytes(vol, vcn_tr);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(
+ ni, 0, ni);
if (err)
ntfs_error(vol->sb,
"Failed to rollback mapping pairs for prealloc");
@@ -127,7 +129,8 @@ out_unlock:
static int ntfs_file_release(struct inode *vi, struct file *filp)
{
- if (!NInoCompressed(NTFS_I(vi)))
+ if (!NInoCompressed(NTFS_I(vi)) &&
+ !NInoWofCompressed(NTFS_I(vi)))
return ntfs_trim_prealloc(vi);
return 0;
@@ -255,10 +258,11 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr)
int err;
loff_t old_size = vi->i_size;
- if (NInoCompressed(ni) || NInoEncrypted(ni)) {
- ntfs_warning(vi->i_sb,
- "Changes in inode size are not supported yet for %s files, ignoring.",
- NInoCompressed(ni) ? "compressed" : "encrypted");
+ if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni)) {
+ ntfs_warning(
+ vi->i_sb,
+ "Changes in inode size are not supported yet for %s files.",
+ NInoEncrypted(ni) ? "encrypted" : "compressed");
return -EOPNOTSUPP;
}
@@ -267,23 +271,26 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr)
return err;
inode_dio_wait(vi);
- truncate_setsize(vi, attr->ia_size);
- err = ntfs_truncate_vfs(vi, attr->ia_size, old_size);
- if (err) {
- i_size_write(vi, old_size);
- return err;
- }
- if (NInoNonResident(ni) && attr->ia_size > old_size &&
- old_size % PAGE_SIZE != 0) {
- loff_t len = min_t(loff_t,
- round_up(old_size, PAGE_SIZE) - old_size,
- attr->ia_size - old_size);
- err = iomap_zero_range(vi, old_size, len,
- NULL, &ntfs_seek_iomap_ops,
- &ntfs_iomap_folio_ops, NULL);
+ /*
+ * 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 {
+ truncate_setsize(vi, attr->ia_size);
}
+ err = ntfs_truncate_vfs(vi, attr->ia_size, old_size);
+ if (err)
+ i_size_write(vi, old_size);
+ filemap_invalidate_unlock(vi->i_mapping);
+
return err;
}
@@ -311,6 +318,13 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if (err)
goto out;
+ if ((ia_valid & ATTR_SIZE) &&
+ (NInoCompressed(ni) || NInoEncrypted(ni) ||
+ NInoWofCompressed(ni))) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
@@ -345,14 +359,12 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if (ia_valid & ATTR_MODE)
flags |= NTFS_EA_MODE;
- if (S_ISDIR(vi->i_mode))
- vi->i_mode &= ~vol->dmask;
- else
- vi->i_mode &= ~vol->fmask;
-
mutex_lock(&ni->mrec_lock);
- ntfs_ea_set_wsl_inode(vi, 0, NULL, flags);
+ err = ntfs_ea_set_wsl_inode(vi, 0, NULL, flags);
mutex_unlock(&ni->mrec_lock);
+ if (err)
+ goto out;
+
}
mark_inode_dirty(vi);
@@ -375,7 +387,7 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
stat->result_mask |= STATX_BTIME;
stat->btime = NTFS_I(inode)->i_crtime;
- if (NInoCompressed(ni))
+ if (NInoCompressed(ni) || NInoWofCompressed(ni))
stat->attributes |= STATX_ATTR_COMPRESSED;
if (NInoEncrypted(ni))
@@ -400,7 +412,8 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
bdev_logical_block_size(inode->i_sb->s_bdev);
stat->result_mask |= STATX_DIOALIGN;
- if (!NInoCompressed(ni) && !NInoEncrypted(ni)) {
+ if (!NInoCompressed(ni) && !NInoEncrypted(ni) &&
+ !NInoWofCompressed(ni)) {
stat->dio_mem_align = align;
stat->dio_offset_align = align;
}
@@ -413,6 +426,10 @@ static loff_t ntfs_file_llseek(struct file *file, loff_t offset, int whence)
{
struct inode *inode = file->f_mapping->host;
+ if (NInoWofCompressed(NTFS_I(inode)) &&
+ (whence == SEEK_HOLE || whence == SEEK_DATA))
+ return -EOPNOTSUPP;
+
switch (whence) {
case SEEK_HOLE:
inode_lock_shared(inode);
@@ -443,7 +460,8 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
if (NVolShutdown(NTFS_SB(sb)))
return -EIO;
- if (NInoCompressed(NTFS_I(vi)) && iocb->ki_flags & IOCB_DIRECT)
+ if ((NInoCompressed(NTFS_I(vi)) || NInoWofCompressed(NTFS_I(vi))) &&
+ iocb->ki_flags & IOCB_DIRECT)
return -EOPNOTSUPP;
inode_lock_shared(vi);
@@ -534,6 +552,31 @@ out:
return ret;
}
+static int ntfs_expand_for_write(struct ntfs_inode *ni, loff_t end)
+{
+ struct ntfs_volume *vol = ni->vol;
+ loff_t prealloc_size = 0;
+ int err;
+
+ if (end <= ni->data_size)
+ return 0;
+
+ if (NInoCompressed(ni)) {
+ if (end > ni->allocated_size)
+ prealloc_size = round_up(end,
+ ni->itype.compressed.block_size);
+ } else if (end > ni->allocated_size &&
+ end < ni->allocated_size + vol->preallocated_size) {
+ prealloc_size = ni->allocated_size + vol->preallocated_size;
+ }
+
+ mutex_lock(&ni->mrec_lock);
+ err = ntfs_attr_expand(ni, end, prealloc_size);
+ mutex_unlock(&ni->mrec_lock);
+
+ return err;
+}
+
static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
@@ -542,13 +585,16 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
struct ntfs_volume *vol = ni->vol;
ssize_t ret;
ssize_t count;
- loff_t pos;
+ loff_t pos, end;
int err;
loff_t old_data_size, old_init_size;
if (NVolShutdown(vol))
return -EIO;
+ if (NInoWofCompressed(ni))
+ return -EOPNOTSUPP;
+
if (NInoEncrypted(ni)) {
ntfs_error(vi->i_sb, "Writing for %s files is not supported yet",
NInoCompressed(ni) ? "Compressed" : "Encrypted");
@@ -579,10 +625,24 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
pos = iocb->ki_pos;
count = ret;
+ end = pos + count;
old_data_size = ni->data_size;
old_init_size = ni->initialized_size;
+ if (end > old_data_size) {
+ ret = ntfs_expand_for_write(ni, end);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (NInoNonResident(ni) && !NInoCompressed(ni) &&
+ end > old_init_size) {
+ ret = ntfs_extend_initialized_size(vi, pos, end);
+ if (ret < 0)
+ goto out;
+ }
+
if (NInoNonResident(ni) && NInoCompressed(ni)) {
ret = ntfs_compress_write(ni, pos, count, from);
if (ret > 0)
@@ -617,12 +677,23 @@ out_lock:
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)))
+ return VM_FAULT_SIGBUS;
+
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;
}
@@ -641,7 +712,7 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
if (NVolShutdown(NTFS_SB(file->f_mapping->host->i_sb)))
return -EIO;
- if (NInoCompressed(NTFS_I(inode)))
+ if (NInoCompressed(NTFS_I(inode)) || NInoWofCompressed(NTFS_I(inode)))
return -EOPNOTSUPP;
if (vma_desc_test_all(desc, VMA_SHARED_BIT, VMA_MAYWRITE_BIT)) {
@@ -654,7 +725,7 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
from + desc->end - desc->start);
if (NTFS_I(inode)->initialized_size < to) {
- err = ntfs_extend_initialized_size(inode, to, to, false);
+ err = ntfs_extend_initialized_size(inode, to, to);
if (err)
return err;
}
@@ -669,16 +740,38 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
static int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
u64 start, u64 len)
{
+ if (NInoWofCompressed(NTFS_I(inode)))
+ return -EOPNOTSUPP;
+
return iomap_fiemap(inode, fieinfo, start, len, &ntfs_read_iomap_ops);
}
static const char *ntfs_get_link(struct dentry *dentry, struct inode *inode,
struct delayed_call *done)
{
- if (!NTFS_I(inode)->target)
+ struct ntfs_inode *ni = NTFS_I(inode);
+ char *target;
+ int err;
+
+ if (!dentry)
+ return ERR_PTR(-ECHILD);
+
+ if (!ni->target)
return ERR_PTR(-EINVAL);
- return NTFS_I(inode)->target;
+ if (ni->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT ||
+ (ni->reparse_tag == IO_REPARSE_TAG_SYMLINK &&
+ !(ni->reparse_flags & cpu_to_le32(SYMLINK_FLAG_RELATIVE)))) {
+ if (NVolNativeSymlinkRel(ni->vol)) {
+ err = ntfs_translate_symlink_path(dentry, ni->target, &target);
+ if (err < 0)
+ return ERR_PTR(err);
+ set_delayed_call(done, kfree_link, target);
+ return target;
+ }
+ }
+
+ return ni->target;
}
static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos,
@@ -707,12 +800,21 @@ static int ntfs_ioctl_get_volume_label(struct file *filp, unsigned long arg)
{
struct ntfs_volume *vol = NTFS_SB(file_inode(filp)->i_sb);
char __user *buf = (char __user *)arg;
+ char label[FSLABEL_MAX];
+ ssize_t len;
+ mutex_lock(&vol->volume_label_lock);
if (!vol->volume_label) {
- if (copy_to_user(buf, "", 1))
- return -EFAULT;
- } else if (copy_to_user(buf, vol->volume_label,
- MIN(FSLABEL_MAX, strlen(vol->volume_label) + 1)))
+ label[0] = '\0';
+ len = 0;
+ } else {
+ len = strscpy(label, vol->volume_label, sizeof(label));
+ if (len == -E2BIG)
+ len = FSLABEL_MAX - 1;
+ }
+ mutex_unlock(&vol->volume_label_lock);
+
+ if (copy_to_user(buf, label, len + 1))
return -EFAULT;
return 0;
}
@@ -1030,11 +1132,13 @@ 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;
+ if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni))
+ return -EOPNOTSUPP;
+
if (!NVolFreeClusterKnown(vol))
wait_event(vol->free_waitq, NVolFreeClusterKnown(vol));
@@ -1058,17 +1162,14 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
old_size = i_size_read(vi);
inode_lock(vi);
- if (NInoCompressed(ni) || NInoEncrypted(ni)) {
- err = -EOPNOTSUPP;
- goto out;
+ if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni)) {
+ 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:
@@ -1093,18 +1194,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 && old_size % PAGE_SIZE != 0) {
- loff_t len = min_t(loff_t,
- round_up(old_size, PAGE_SIZE) - old_size,
- offset - old_size);
- err = iomap_zero_range(vi, old_size, len, NULL,
- &ntfs_seek_iomap_ops,
- &ntfs_iomap_folio_ops, NULL);
- }
NInoSetFileNameDirty(ni);
inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi));
mark_inode_dirty(vi);
diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index a547bdcfa456..580998990bc9 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -28,41 +28,10 @@
* length must have been checked beforehand to not overflow from the
* index record.
*/
-int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx,
- struct ntfs_volume *vol, const struct index_entry *ie,
- __le32 collation_rule, u64 inum)
+static int ntfs_index_entry_inconsistent(const struct ntfs_volume *vol,
+ const struct index_entry *ie,
+ __le32 collation_rule, u64 inum)
{
- if (icx) {
- struct index_header *ih;
- u8 *ie_start, *ie_end;
-
- if (icx->is_in_root)
- ih = &icx->ir->index;
- else
- ih = &icx->ib->index;
-
- if ((le32_to_cpu(ih->index_length) > le32_to_cpu(ih->allocated_size)) ||
- (le32_to_cpu(ih->index_length) > icx->block_size)) {
- ntfs_error(vol->sb, "%s Index entry(0x%p)'s length is too big.",
- icx->is_in_root ? "Index root" : "Index block",
- (u8 *)icx->entry);
- return -EINVAL;
- }
-
- ie_start = (u8 *)ih + le32_to_cpu(ih->entries_offset);
- ie_end = (u8 *)ih + le32_to_cpu(ih->index_length);
-
- if (ie_start > (u8 *)ie ||
- ie_end <= (u8 *)ie + le16_to_cpu(ie->length) ||
- le16_to_cpu(ie->length) > le32_to_cpu(ih->allocated_size) ||
- le16_to_cpu(ie->length) > icx->block_size) {
- ntfs_error(vol->sb, "Index entry(0x%p) is out of range from %s",
- (u8 *)icx->entry,
- icx->is_in_root ? "index root" : "index block");
- return -EIO;
- }
- }
-
if (ie->key_length &&
((le16_to_cpu(ie->key_length) + offsetof(struct index_entry, key)) >
le16_to_cpu(ie->length))) {
@@ -141,6 +110,10 @@ static int ntfs_ib_write(struct ntfs_index_context *icx, struct index_block *ib)
ret = ntfs_inode_attr_pwrite(VFS_I(icx->ia_ni),
ntfs_ib_vcn_to_pos(icx, vcn), icx->block_size,
(u8 *)ib, icx->sync_write);
+
+ /* Perform data restoration before returning */
+ post_write_mst_fixup((struct ntfs_record *)ib);
+
if (ret != icx->block_size) {
ntfs_debug("Failed to write index block %lld, inode %llu",
vcn, (unsigned long long)icx->idx_ni->mft_no);
@@ -178,7 +151,6 @@ int ntfs_icx_ib_sync_write(struct ntfs_index_context *icx)
icx->ib = NULL;
icx->ib_dirty = false;
} else {
- post_write_mst_fixup((struct ntfs_record *)icx->ib);
icx->sync_write = false;
}
@@ -303,6 +275,93 @@ static int ntfs_ie_end(struct index_entry *ie)
return ie->flags & INDEX_ENTRY_END || !ie->length;
}
+static int ntfs_index_header_inconsistent(struct ntfs_volume *vol,
+ const struct index_header *ih,
+ u32 bytes_available, u64 inum)
+{
+ u32 entries_offset, index_length, allocated_size;
+
+ if (bytes_available < sizeof(struct index_header)) {
+ ntfs_error(vol->sb,
+ "index block in inode %llu is smaller than an index header.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ entries_offset = le32_to_cpu(ih->entries_offset);
+ index_length = le32_to_cpu(ih->index_length);
+ allocated_size = le32_to_cpu(ih->allocated_size);
+
+ if (entries_offset < sizeof(struct index_header) ||
+ entries_offset > bytes_available) {
+ ntfs_error(vol->sb,
+ "Invalid index entry offset in inode %llu.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ if (index_length <= entries_offset) {
+ ntfs_error(vol->sb,
+ "No space for index entries in inode %llu.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ if (allocated_size < index_length) {
+ ntfs_error(vol->sb,
+ "Index entries overflow in inode %llu.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ if (allocated_size > bytes_available || index_length > bytes_available) {
+ ntfs_error(vol->sb,
+ "Index entries in inode %llu exceed the available buffer.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol,
+ const struct index_header *ih,
+ __le32 collation_rule, u64 inum)
+{
+ struct index_entry *ie;
+ u8 *index_end = (u8 *)ih + le32_to_cpu(ih->index_length);
+
+ for (ie = ntfs_ie_get_first((struct index_header *)ih);
+ ; ie = ntfs_ie_get_next(ie)) {
+ if ((u8 *)ie + sizeof(struct index_entry_header) > index_end ||
+ (u8 *)ie + le16_to_cpu(ie->length) > index_end) {
+ ntfs_error(vol->sb,
+ "Index entry out of bounds in inode %llu.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ if (le16_to_cpu(ie->length) < sizeof(struct index_entry_header)) {
+ ntfs_error(vol->sb,
+ "Index entry too small in inode %llu.",
+ inum);
+ return -EIO;
+ }
+
+ if (ntfs_ie_end(ie))
+ break;
+
+ if (!ie->key_length)
+ return -EIO;
+
+ if (ntfs_index_entry_inconsistent(vol, ie,
+ collation_rule, inum))
+ return -EIO;
+ }
+
+ return 0;
+}
+
/*
* Find the last entry in the index block
*/
@@ -437,7 +496,7 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie)
* The size of block is assumed to have been checked to be what is
* defined in the index root.
*
- * Returns 0 if no error was found -1 otherwise (with errno unchanged)
+ * Returns 0 if no error was found, -EIO otherwise
*
* |<--->| offsetof(struct index_block, index)
* | |<--->| sizeof(struct index_header)
@@ -452,21 +511,21 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie)
*
* size(struct index_header) <= ent_offset < ind_length <= alloc_size < bk_size
*/
-static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx,
- struct index_block *ib, s64 vcn)
+int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
+ const struct index_block *ib,
+ u32 block_size, s64 vcn, __le32 cr,
+ u64 inum)
{
u32 ib_size = (unsigned int)le32_to_cpu(ib->index.allocated_size) +
offsetof(struct index_block, index);
- struct super_block *sb = icx->idx_ni->vol->sb;
- unsigned long long inum = icx->idx_ni->mft_no;
+ struct super_block *sb = vol->sb;
ntfs_debug("Entering\n");
if (!ntfs_is_indx_record(ib->magic)) {
-
ntfs_error(sb, "Corrupt index block signature: vcn %lld inode %llu\n",
- vcn, (unsigned long long)icx->idx_ni->mft_no);
- return -1;
+ vcn, (unsigned long long)inum);
+ return -EIO;
}
if (le64_to_cpu(ib->index_block_vcn) != vcn) {
@@ -474,32 +533,42 @@ static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx,
"Corrupt index block: s64 (%lld) is different from expected s64 (%lld) in inode %llu\n",
(long long)le64_to_cpu(ib->index_block_vcn),
vcn, inum);
- return -1;
+ return -EIO;
}
- if (ib_size != icx->block_size) {
+ if (ib_size != block_size) {
ntfs_error(sb,
- "Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n",
- vcn, inum, ib_size, icx->block_size);
- return -1;
+ "Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n",
+ vcn, inum, ib_size, block_size);
+ return -EIO;
}
- if (le32_to_cpu(ib->index.entries_offset) < sizeof(struct index_header)) {
- ntfs_error(sb, "Invalid index entry offset in inode %lld\n", inum);
- return -1;
- }
- if (le32_to_cpu(ib->index.index_length) <=
- le32_to_cpu(ib->index.entries_offset)) {
- ntfs_error(sb, "No space for index entries in inode %lld\n", inum);
- return -1;
- }
- if (le32_to_cpu(ib->index.allocated_size) <
- le32_to_cpu(ib->index.index_length)) {
- ntfs_error(sb, "Index entries overflow in inode %lld\n", inum);
- return -1;
+ if (ntfs_index_header_inconsistent(vol, &ib->index,
+ block_size -
+ offsetof(struct index_block, index),
+ inum))
+ return -EIO;
+ if (ntfs_index_entries_inconsistent(vol, &ib->index, cr, inum))
+ return -EIO;
+ return 0;
+}
+
+int ntfs_index_root_inconsistent(struct ntfs_volume *vol,
+ const struct attr_record *a,
+ const struct index_root *ir, u64 inum)
+{
+ u32 value_length = le32_to_cpu(a->data.resident.value_length);
+
+ if (value_length < offsetof(struct index_root, index)) {
+ ntfs_error(vol->sb, "$INDEX_ROOT in inode %llu is too small.",
+ (unsigned long long)inum);
+ return -EIO;
}
- return 0;
+ return ntfs_index_header_inconsistent(vol, &ir->index,
+ value_length -
+ offsetof(struct index_root, index),
+ inum);
}
static struct index_root *ntfs_ir_lookup(struct ntfs_inode *ni, __le16 *name,
@@ -547,6 +616,31 @@ static struct index_root *ntfs_ir_lookup2(struct ntfs_inode *ni, __le16 *name, u
return ir;
}
+static int ntfs_ir_move_to_base(struct ntfs_index_context *icx)
+{
+ struct ntfs_attr_search_ctx *ctx = NULL;
+ struct index_root *ir;
+ bool moved = false;
+ int ret = 0;
+
+ ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx);
+ if (!ir)
+ return -ENOENT;
+
+ if (ctx->ntfs_ino->mft_no != icx->idx_ni->mft_no) {
+ ret = ntfs_attr_record_move_to(ctx, icx->idx_ni);
+ if (!ret) {
+ moved = true;
+ ret = ntfs_attrlist_update(icx->idx_ni);
+ }
+ }
+
+ ntfs_attr_put_search_ctx(ctx);
+ if (!ret && moved)
+ ret = ntfs_inode_free_empty_extents(icx->idx_ni);
+ return ret;
+}
+
/*
* Find a key in the index block.
*/
@@ -665,23 +759,24 @@ static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_bl
else
ntfs_error(icx->idx_ni->vol->sb,
"Failed to read full index block at %lld\n", pos);
- return -1;
+ return -EIO;
}
post_read_mst_fixup((struct ntfs_record *)((u8 *)dst), icx->block_size);
- if (ntfs_index_block_inconsistent(icx, dst, vcn))
- return -1;
-
+ if (ntfs_index_block_inconsistent(icx->idx_ni->vol, dst,
+ icx->block_size, vcn, icx->cr,
+ icx->idx_ni->mft_no))
+ return -EIO;
return 0;
}
static int ntfs_icx_parent_inc(struct ntfs_index_context *icx)
{
- icx->pindex++;
- if (icx->pindex >= MAX_PARENT_VCN) {
+ if (icx->pindex >= MAX_PARENT_VCN - 1) {
ntfs_error(icx->idx_ni->vol->sb, "Index is over %d level deep", MAX_PARENT_VCN);
return -EOPNOTSUPP;
}
+ icx->pindex++;
return 0;
}
@@ -919,6 +1014,7 @@ static s64 ntfs_ibm_pos_to_vcn(struct ntfs_index_context *icx, s64 pos)
static int ntfs_ibm_add(struct ntfs_index_context *icx)
{
u8 bmp[8];
+ int ret;
ntfs_debug("Entering\n");
@@ -928,10 +1024,11 @@ static int ntfs_ibm_add(struct ntfs_index_context *icx)
* AT_BITMAP must be at least 8 bytes.
*/
memset(bmp, 0, sizeof(bmp));
- if (ntfs_attr_add(icx->idx_ni, AT_BITMAP, icx->name, icx->name_len,
- bmp, sizeof(bmp))) {
+ ret = ntfs_attr_add(icx->idx_ni, AT_BITMAP, icx->name, icx->name_len,
+ bmp, sizeof(bmp));
+ if (ret) {
ntfs_error(icx->idx_ni->vol->sb, "Failed to add AT_BITMAP");
- return -EINVAL;
+ return ret;
}
return 0;
@@ -1004,14 +1101,15 @@ static s64 ntfs_ibm_get_free(struct ntfs_index_context *icx)
{
u8 *bm;
int bit;
+ int ret;
s64 vcn, byte, size;
ntfs_debug("Entering\n");
bm = ntfs_attr_readall(icx->idx_ni, AT_BITMAP, icx->name, icx->name_len,
&size);
- if (!bm)
- return (s64)-1;
+ if (IS_ERR(bm))
+ return PTR_ERR(bm);
for (byte = 0; byte < size; byte++) {
if (bm[byte] == 255)
@@ -1029,10 +1127,12 @@ static s64 ntfs_ibm_get_free(struct ntfs_index_context *icx)
out:
ntfs_debug("allocated vcn: %lld\n", vcn);
- if (ntfs_ibm_set(icx, vcn))
- vcn = (s64)-1;
+ ret = ntfs_ibm_set(icx, vcn);
kvfree(bm);
+ if (ret)
+ return ret;
+
return vcn;
}
@@ -1042,6 +1142,7 @@ static struct index_block *ntfs_ir_to_ib(struct index_root *ir, s64 ib_vcn)
struct index_entry *ie_last;
char *ies_start, *ies_end;
int i;
+ u32 ib_cap;
ntfs_debug("Entering\n");
@@ -1057,6 +1158,16 @@ static struct index_block *ntfs_ir_to_ib(struct index_root *ir, s64 ib_vcn)
* as well, which can never have any data.
*/
i = (char *)ie_last - ies_start + le16_to_cpu(ie_last->length);
+
+ /* Entries must fit in the allocated index block */
+ ib_cap = le32_to_cpu(ib->index.allocated_size) -
+ le32_to_cpu(ib->index.entries_offset);
+ if ((u32)i > ib_cap) {
+ ntfs_error(NULL, "Entries (%d B) exceed IB capacity", i);
+ kvfree(ib);
+ return NULL;
+ }
+
memcpy(ntfs_ie_get_first(&ib->index), ies_start, i);
ib->index.flags = ir->index.flags;
@@ -1173,6 +1284,8 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
struct index_entry *ie;
struct index_block *ib = NULL;
s64 new_ib_vcn;
+ u32 index_length;
+ u32 old_value_length;
int ix_root_size;
int ret = 0;
@@ -1192,7 +1305,7 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
new_ib_vcn = ntfs_ibm_get_free(icx);
if (new_ib_vcn < 0) {
- ret = -EINVAL;
+ ret = (int)new_ib_vcn;
goto out;
}
@@ -1220,6 +1333,21 @@ retry:
goto clear_bmp;
}
+ old_value_length = le32_to_cpu(ctx->attr->data.resident.value_length);
+ index_length = le32_to_cpu(ir->index.entries_offset) +
+ sizeof(struct index_entry_header) + sizeof(s64);
+ ix_root_size = offsetof(struct index_root, index) + index_length;
+ /* Grow the resident value before publishing the larger root header. */
+ if (ix_root_size > old_value_length) {
+ ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size);
+ if (ret)
+ goto resize_failed;
+
+ icx->idx_ni->data_size = ix_root_size;
+ icx->idx_ni->initialized_size = ix_root_size;
+ icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7;
+ }
+
ntfs_ir_nill(ir);
ie = ntfs_ie_get_first(&ir->index);
@@ -1228,48 +1356,72 @@ retry:
ir->index.flags = LARGE_INDEX;
NInoSetIndexAllocPresent(icx->idx_ni);
- ir->index.index_length = cpu_to_le32(le32_to_cpu(ir->index.entries_offset) +
- le16_to_cpu(ie->length));
+ ir->index.index_length = cpu_to_le32(index_length);
ir->index.allocated_size = ir->index.index_length;
- ix_root_size = sizeof(struct index_root) - sizeof(struct index_header) +
- le32_to_cpu(ir->index.allocated_size);
- ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size);
- if (ret) {
- /*
- * When there is no space to build a non-resident
- * index, we may have to move the root to an extent
- */
- if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) {
+ if (ix_root_size <= old_value_length) {
+ ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size);
+ if (ret)
+ goto resize_failed;
+
+ icx->idx_ni->data_size = ix_root_size;
+ icx->idx_ni->initialized_size = ix_root_size;
+ icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7;
+ }
+ ntfs_ie_set_vcn(ie, new_ib_vcn);
+ goto err_out;
+
+resize_failed:
+ /*
+ * When there is no space to build a non-resident
+ * index, we may have to move the root to an extent
+ */
+ if (ret == -ENOSPC) {
+ if (!ctx->al_entry) {
+ ret = ntfs_inode_add_attrlist(icx->idx_ni);
+ if (ret)
+ goto clear_bmp;
+
ntfs_attr_put_search_ctx(ctx);
ctx = NULL;
- ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx);
- if (ir && !ntfs_attr_record_move_away(ctx, ix_root_size -
- le32_to_cpu(ctx->attr->data.resident.value_length))) {
- if (ntfs_attrlist_update(ctx->base_ntfs_ino ?
- ctx->base_ntfs_ino : ctx->ntfs_ino))
- goto clear_bmp;
- ntfs_attr_put_search_ctx(ctx);
- ctx = NULL;
- goto retry;
- }
+ goto retry;
}
- goto clear_bmp;
- } else {
- icx->idx_ni->data_size = icx->idx_ni->initialized_size = ix_root_size;
- icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7;
- }
- ntfs_ie_set_vcn(ie, new_ib_vcn);
+ if (ctx->ntfs_ino->mft_no != icx->idx_ni->mft_no)
+ goto clear_bmp;
+
+ ret = ntfs_attr_record_move_away(ctx, ix_root_size -
+ le32_to_cpu(ctx->attr->data.resident.value_length));
+ if (ret)
+ goto clear_bmp;
+
+ ret = ntfs_attrlist_update(icx->idx_ni);
+ if (ret) {
+ int rollback_ret;
+
+ ntfs_attr_put_search_ctx(ctx);
+ ctx = NULL;
+ rollback_ret = ntfs_ir_move_to_base(icx);
+ if (rollback_ret)
+ ntfs_error(icx->idx_ni->vol->sb,
+ "Failed to roll back INDEX_ROOT relocation: %d",
+ rollback_ret);
+ goto clear_bmp;
+ }
+
+ ntfs_attr_put_search_ctx(ctx);
+ ctx = NULL;
+ goto retry;
+ }
+clear_bmp:
+ ntfs_ibm_clear(icx, new_ib_vcn);
+ goto err_out;
err_out:
kvfree(ib);
if (ctx)
ntfs_attr_put_search_ctx(ctx);
out:
return ret;
-clear_bmp:
- ntfs_ibm_clear(icx, new_ib_vcn);
- goto err_out;
}
/*
@@ -1280,9 +1432,16 @@ clear_bmp:
static int ntfs_ir_truncate(struct ntfs_index_context *icx, int data_size)
{
int ret;
+ u32 old_allocated_size;
+ bool shrink;
ntfs_debug("Entering\n");
+ old_allocated_size = le32_to_cpu(icx->ir->index.allocated_size);
+ shrink = data_size < old_allocated_size;
+ if (shrink)
+ icx->ir->index.allocated_size = cpu_to_le32(data_size);
+
/*
* INDEX_ROOT must be resident and its entries can be moved to
* struct index_block, so ENOSPC isn't a real error.
@@ -1294,9 +1453,14 @@ static int ntfs_ir_truncate(struct ntfs_index_context *icx, int data_size)
if (!icx->ir)
return -ENOENT;
- icx->ir->index.allocated_size = cpu_to_le32(data_size);
- } else if (ret != -ENOSPC)
- ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT");
+ if (!shrink)
+ icx->ir->index.allocated_size = cpu_to_le32(data_size);
+ } else {
+ if (shrink)
+ icx->ir->index.allocated_size = cpu_to_le32(old_allocated_size);
+ if (ret != -ENOSPC)
+ ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT");
+ }
return ret;
}
@@ -1479,7 +1643,7 @@ resplit:
median = ntfs_ie_get_median(&ib->index);
new_vcn = ntfs_ibm_get_free(icx);
if (new_vcn < 0) {
- ret = -EINVAL;
+ ret = (int)new_vcn;
goto out;
}
@@ -1496,7 +1660,7 @@ resplit:
goto out;
}
} else {
- si = kzalloc(sizeof(struct split_info), GFP_NOFS);
+ si = kzalloc_obj(struct split_info, GFP_NOFS);
if (!si) {
ntfs_ibm_clear(icx, new_vcn);
ret = -ENOMEM;
@@ -1970,6 +2134,7 @@ struct index_entry *ntfs_index_walk_down(struct index_entry *ie, struct ntfs_ind
{
struct index_entry *entry;
struct index_block *ib;
+ int err;
s64 vcn;
entry = ie;
@@ -1979,14 +2144,20 @@ struct index_entry *ntfs_index_walk_down(struct index_entry *ie, struct ntfs_ind
ib = kvzalloc(ictx->block_size, GFP_NOFS);
if (!ib)
return ERR_PTR(-ENOMEM);
- /* down from level zero */
+ /*
+ * Descending from root index (level 0) to the first
+ * child level. is_in_root == true implies pindex == 0,
+ * so advance to level 1.
+ */
+ ictx->pindex = 1;
ictx->ir = NULL;
ictx->ib = ib;
- ictx->pindex = 1;
ictx->is_in_root = false;
} else {
/* down from non-zero level */
- ictx->pindex++;
+ err = ntfs_icx_parent_inc(ictx);
+ if (err)
+ return ERR_PTR(err);
}
ictx->parent_pos[ictx->pindex] = 0;
diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h
index e68d6fabaf9f..9a03f53bba47 100644
--- a/fs/ntfs/index.h
+++ b/fs/ntfs/index.h
@@ -89,8 +89,16 @@ struct ntfs_index_context {
bool sync_write;
};
-int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, struct ntfs_volume *vol,
- const struct index_entry *ie, __le32 collation_rule, u64 inum);
+int ntfs_index_root_inconsistent(struct ntfs_volume *vol,
+ const struct attr_record *a,
+ const struct index_root *ir, u64 inum);
+int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
+ const struct index_block *ib,
+ u32 block_size, s64 vcn,
+ __le32 cr, u64 inum);
+int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol,
+ const struct index_header *ih,
+ __le32 collation_rule, u64 inum);
struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, __le16 *name,
u32 name_len);
void ntfs_index_ctx_put(struct ntfs_index_context *ictx);
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 16890d411194..a777de8a80c7 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -170,16 +170,19 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no)
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_inode(vi);
- unlock_new_inode(vi);
+ if (err) {
+ remove_inode_hash(vi);
+ discard_new_inode(vi);
+ } else
+ unlock_new_inode(vi);
}
/*
- * There is no point in keeping bad inodes around if the failure was
- * due to ENOMEM. We want to be able to retry again later.
+ * There is no point in keeping bad inodes around. This also
+ * simplifies things in that we never need to check for bad inodes
+ * elsewhere.
*/
- if (unlikely(err == -ENOMEM)) {
- iput(vi);
+ if (unlikely(err))
vi = ERR_PTR(err);
- }
return vi;
}
@@ -230,17 +233,19 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type,
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_attr_inode(base_vi, vi);
- unlock_new_inode(vi);
+ if (err) {
+ remove_inode_hash(vi);
+ discard_new_inode(vi);
+ } else
+ unlock_new_inode(vi);
}
/*
* There is no point in keeping bad attribute inodes around. This also
* simplifies things in that we never need to check for bad attribute
* inodes elsewhere.
*/
- if (unlikely(err)) {
- iput(vi);
+ if (unlikely(err))
vi = ERR_PTR(err);
- }
return vi;
}
@@ -285,17 +290,19 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name,
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_index_inode(base_vi, vi);
- unlock_new_inode(vi);
+ if (err) {
+ remove_inode_hash(vi);
+ discard_new_inode(vi);
+ } else
+ unlock_new_inode(vi);
}
/*
* There is no point in keeping bad index inodes around. This also
* simplifies things in that we never need to check for bad index
* inodes elsewhere.
*/
- if (unlikely(err)) {
- iput(vi);
+ if (unlikely(err))
vi = ERR_PTR(err);
- }
return vi;
}
@@ -488,6 +495,8 @@ void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni)
ni->flags = 0;
ni->mft_lcn[0] = LCN_RL_NOT_MAPPED;
ni->mft_lcn_count = 0;
+ ni->reparse_tag = 0;
+ ni->reparse_flags = 0;
ni->target = NULL;
ni->i_dealloc_clusters = 0;
}
@@ -680,6 +689,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
unsigned int name_len = 4, flags = 0;
int extend_sys = 0;
dev_t dev = 0;
+ bool has_lxmod = false;
bool vol_err = true;
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
@@ -848,49 +858,64 @@ static int ntfs_read_locked_inode(struct inode *vi)
a->data.resident.value_offset),
le32_to_cpu(
a->data.resident.value_length));
+ /* A resident list is not validated on load; check it now. */
+ if (!ntfs_attr_list_is_valid(ni->attr_list,
+ ni->attr_list_size)) {
+ ntfs_error(vi->i_sb, "Corrupt attribute list.");
+ goto unm_err_out;
+ }
}
}
skip_attr_list_load:
err = ntfs_attr_lookup(AT_EA_INFORMATION, NULL, 0, 0, 0, NULL, 0, ctx);
if (!err) {
NInoSetHasEA(ni);
- ntfs_ea_get_wsl_inode(vi, &dev, flags);
+ ntfs_ea_get_wsl_inode(vi, &dev, flags, &has_lxmod);
}
- if (m->flags & MFT_RECORD_IS_DIRECTORY) {
+ if (ni->flags & FILE_ATTR_REPARSE_POINT) {
+ unsigned int mode;
+
+ err = ntfs_parse_reparse(ni, &mode);
+ if (err)
+ goto unm_err_out;
+ if (mode)
+ vi->i_mode |= mode;
+ else {
+ vi->i_mode &= ~S_IFLNK;
+ if (m->flags & MFT_RECORD_IS_DIRECTORY)
+ vi->i_mode |= S_IFDIR;
+ else
+ vi->i_mode |= S_IFREG;
+ }
+ } else if (m->flags & MFT_RECORD_IS_DIRECTORY) {
vi->i_mode |= S_IFDIR;
+ } else {
+ vi->i_mode |= S_IFREG;
+ }
+
+ if (S_ISDIR(vi->i_mode)) {
/*
- * Apply the directory permissions mask set in the mount
- * options.
+ * Apply the directory permissions mask set in the mount options
+ * when no per-file WSL mode is present.
*/
- vi->i_mode &= ~vol->dmask;
+ if (!has_lxmod)
+ vi->i_mode &= ~vol->dmask;
/* Things break without this kludge! */
if (vi->i_nlink > 1)
set_nlink(vi, 1);
} else {
- if (ni->flags & FILE_ATTR_REPARSE_POINT) {
- unsigned int mode;
-
- mode = ntfs_make_symlink(ni);
- if (mode)
- vi->i_mode |= mode;
- else {
- vi->i_mode &= ~S_IFLNK;
- vi->i_mode |= S_IFREG;
- }
- } else
- vi->i_mode |= S_IFREG;
- /* Apply the file permissions mask set in the mount options. */
- vi->i_mode &= ~vol->fmask;
+ /* Apply the file permissions mask when no WSL mode is present. */
+ if (!has_lxmod)
+ vi->i_mode &= ~vol->fmask;
}
/*
* If an attribute list is present we now have the attribute list value
* in ntfs_ino->attr_list and it is ntfs_ino->attr_list_size bytes.
*/
- if (S_ISDIR(vi->i_mode)) {
+ if (m->flags & MFT_RECORD_IS_DIRECTORY) {
struct index_root *ir;
- u8 *ir_end, *index_end;
view_index_meta:
/* It is a directory, find index root attribute. */
@@ -940,10 +965,9 @@ view_index_meta:
}
ir = (struct index_root *)((u8 *)a +
le16_to_cpu(a->data.resident.value_offset));
- ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length);
- index_end = (u8 *)&ir->index +
- le32_to_cpu(ir->index.index_length);
- if (index_end > ir_end) {
+ if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no) ||
+ ntfs_index_entries_inconsistent(ni->vol, &ir->index,
+ ir->collation_rule, ni->mft_no)) {
ntfs_error(vi->i_sb, "Directory index is corrupt.");
goto unm_err_out;
}
@@ -1014,7 +1038,7 @@ view_index_meta:
m = NULL;
ctx = NULL;
/* Setup the operations for this inode. */
- ntfs_set_vfs_operations(vi, S_IFDIR, 0);
+ ntfs_set_vfs_operations(vi, vi->i_mode, 0);
if (ir->index.flags & LARGE_INDEX)
NInoSetIndexAllocPresent(ni);
} else {
@@ -1074,6 +1098,11 @@ view_index_meta:
/* Setup the state. */
if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) {
if (a->flags & ATTR_COMPRESSION_MASK) {
+ if (NInoWofCompressed(ni)) {
+ ntfs_error(vi->i_sb,
+ "Found native compression on a WOF file.");
+ goto unm_err_out;
+ }
NInoSetCompressed(ni);
ni->flags |= FILE_ATTR_COMPRESSED;
if (vol->cluster_size > 4096) {
@@ -1104,7 +1133,7 @@ view_index_meta:
}
if (a->non_resident) {
NInoSetNonResident(ni);
- if (NInoCompressed(ni) || NInoSparse(ni)) {
+ if (NInoCompressed(ni) || (NInoSparse(ni) && !NInoWofCompressed(ni))) {
if (NInoCompressed(ni) &&
a->data.non_resident.compression_unit != 4) {
ntfs_error(vi->i_sb,
@@ -1180,6 +1209,15 @@ no_data_attr_special_case:
vi->i_flags |= S_IMMUTABLE;
/*
+ * System files such as $Bitmap and $MFT are maintained by the driver
+ * itself, and writing them from userspace corrupts the volume.
+ * Always make them immutable regardless of the sys_immutable option.
+ * Directories are skipped so the root and $Extend stay usable.
+ */
+ if (ni->mft_no < FILE_first_user && S_ISREG(vi->i_mode))
+ vi->i_flags |= S_IMMUTABLE;
+
+ /*
* The number of 512-byte blocks used on disk (for stat). This is in so
* far inaccurate as it doesn't account for any named streams or other
* special non-resident attributes, but that is how Windows works, too,
@@ -1190,11 +1228,15 @@ no_data_attr_special_case:
* sizes of all non-resident attributes present to give us the Linux
* correct size that should go into i_blocks (after division by 512).
*/
- if (S_ISREG(vi->i_mode) && (NInoCompressed(ni) || NInoSparse(ni)))
+ if (S_ISREG(vi->i_mode) &&
+ (NInoCompressed(ni) || (NInoSparse(ni) && !NInoWofCompressed(ni))))
vi->i_blocks = ni->itype.compressed.size >> 9;
else
vi->i_blocks = ni->allocated_size >> 9;
+ if (S_ISLNK(vi->i_mode) && ni->target)
+ vi->i_size = strlen(ni->target);
+
ntfs_debug("Done.");
return 0;
unm_err_out:
@@ -1205,7 +1247,8 @@ unm_err_out:
if (m)
unmap_mft_record(ni);
err_out:
- if (err != -EOPNOTSUPP && err != -ENOMEM && vol_err == true) {
+ if (err != -EOPNOTSUPP && err != -ENOMEM &&
+ err != -EINTR && err != -ERESTARTSYS && vol_err == true) {
ntfs_error(vol->sb,
"Failed with error code %i. Marking corrupt inode 0x%llx as bad. Run chkdsk.",
err, ni->mft_no);
@@ -1370,7 +1413,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
"Attribute name is placed after the mapping pairs array.");
goto unm_err_out;
}
- if (NInoCompressed(ni) || NInoSparse(ni)) {
+ if (NInoCompressed(ni) || (NInoSparse(ni) && !NInoWofCompressed(ni))) {
if (NInoCompressed(ni) && a->data.non_resident.compression_unit != 4) {
ntfs_error(vi->i_sb,
"Found non-standard compression unit (%u instead of 4). Cannot handle this.",
@@ -1431,12 +1474,13 @@ unm_err_out:
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(base_ni);
err_out:
- if (err != -ENOENT)
+ if (err != -ENOENT && err != -EINTR && err != -ERESTARTSYS)
ntfs_error(vol->sb,
"Failed with error code %i while reading attribute inode (mft_no 0x%llx, type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%llx as bad. Run chkdsk.",
err, ni->mft_no, ni->type, ni->name_len,
base_ni->mft_no);
- if (err != -ENOENT && err != -ENOMEM)
+ if (err != -ENOENT && err != -ENOMEM &&
+ err != -EINTR && err != -ERESTARTSYS)
NVolSetErrors(vol);
return err;
}
@@ -1483,7 +1527,6 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
struct attr_record *a;
struct ntfs_attr_search_ctx *ctx;
struct index_root *ir;
- u8 *ir_end, *index_end;
int err = 0;
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
@@ -1534,9 +1577,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
}
ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset));
- ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length);
- index_end = (u8 *)&ir->index + le32_to_cpu(ir->index.index_length);
- if (index_end > ir_end) {
+ if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no) ||
+ ntfs_index_entries_inconsistent(vol, &ir->index,
+ ir->collation_rule, ni->mft_no)) {
ntfs_error(vi->i_sb, "Index is corrupt.");
goto unm_err_out;
}
@@ -1641,8 +1684,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
/* Get the index bitmap attribute inode. */
bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len);
if (IS_ERR(bvi)) {
- ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
err = PTR_ERR(bvi);
+ if (err != -EINTR && err != -ERESTARTSYS)
+ ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
goto unm_err_out;
}
bni = NTFS_I(bvi);
@@ -1686,10 +1730,12 @@ unm_err_out:
if (m)
unmap_mft_record(base_ni);
err_out:
- ntfs_error(vi->i_sb,
- "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.",
- err, ni->mft_no, ni->name_len);
- if (err != -EOPNOTSUPP && err != -ENOMEM)
+ if (err != -EINTR && err != -ERESTARTSYS)
+ ntfs_error(vi->i_sb,
+ "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.",
+ err, ni->mft_no, ni->name_len);
+ if (err != -EOPNOTSUPP && err != -ENOMEM &&
+ err != -EINTR && err != -ERESTARTSYS)
NVolSetErrors(vol);
return err;
}
@@ -1817,7 +1863,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;
@@ -1861,11 +1907,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);
@@ -1994,10 +2035,7 @@ int ntfs_read_inode_mount(struct inode *vi)
/* Catch the end of the attribute list. */
if ((u8 *)al_entry == al_end)
goto em_put_err_out;
- if (!al_entry->length)
- goto em_put_err_out;
- if ((u8 *)al_entry + 6 > al_end ||
- (u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end)
+ if (!ntfs_attr_list_entry_is_valid(al_entry, al_end))
goto em_put_err_out;
next_al_entry = (struct attr_list_entry *)((u8 *)al_entry +
le16_to_cpu(al_entry->length));
@@ -2367,13 +2405,21 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root)
seq_puts(sf, ",discard");
if (NVolDisableSparse(vol))
seq_puts(sf, ",disable_sparse");
+ if (NVolNativeSymlinkRel(vol))
+ seq_puts(sf, ",native_symlink=rel");
+ else
+ seq_puts(sf, ",native_symlink=raw");
+ if (NVolSymlinkNative(vol))
+ seq_puts(sf, ",symlink=native");
+ else
+ seq_puts(sf, ",symlink=wsl");
if (vol->sb->s_flags & SB_POSIXACL)
seq_puts(sf, ",acl");
return 0;
}
int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset,
- const loff_t new_size, bool bsync)
+ const loff_t new_size)
{
struct ntfs_inode *ni = NTFS_I(vi);
loff_t old_init_size;
@@ -2400,10 +2446,6 @@ int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset,
&ntfs_iomap_folio_ops, NULL);
if (err)
return err;
- if (bsync)
- err = filemap_write_and_wait_range(vi->i_mapping,
- old_init_size,
- offset - 1);
}
@@ -2582,8 +2624,8 @@ int ntfs_inode_sync_filename(struct ntfs_inode *ni)
mutex_lock_nested(&index_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
if (NInoBeingDeleted(ni)) {
- iput(index_vi);
mutex_unlock(&index_ni->mrec_lock);
+ iput(index_vi);
continue;
}
@@ -2591,8 +2633,8 @@ int ntfs_inode_sync_filename(struct ntfs_inode *ni)
if (!ictx) {
ntfs_error(sb, "Failed to get index ctx, inode %llu",
index_ni->mft_no);
- iput(index_vi);
mutex_unlock(&index_ni->mrec_lock);
+ iput(index_vi);
continue;
}
@@ -2601,8 +2643,8 @@ int ntfs_inode_sync_filename(struct ntfs_inode *ni)
ntfs_debug("Index lookup failed, inode %llu",
index_ni->mft_no);
ntfs_index_ctx_put(ictx);
- iput(index_vi);
mutex_unlock(&index_ni->mrec_lock);
+ iput(index_vi);
continue;
}
/* Update flags and file size. */
@@ -2741,7 +2783,7 @@ int __ntfs_write_inode(struct inode *vi, int sync)
if (NInoNonResident(ni) && NInoRunlistDirty(ni)) {
down_write(&ni->runlist.lock);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (!err)
NInoClearRunlistDirty(ni);
up_write(&ni->runlist.lock);
@@ -3018,6 +3060,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
struct attr_list_entry *ale = NULL;
struct mft_record *ni_mrec;
u32 attr_al_len;
+ bool free_empty_extents = true;
if (!ni)
return -EINVAL;
@@ -3117,6 +3160,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
ntfs_error(ni->vol->sb, "Couldn't add $ATTRIBUTE_LIST to MFT");
goto rollback;
}
+ free_empty_extents = false;
err = ntfs_attrlist_update(ni);
if (err < 0)
@@ -3136,6 +3180,8 @@ remove_attrlist_record:
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
if (ntfs_attr_record_rm(ctx))
ntfs_error(ni->vol->sb, "Rollback failed to remove attrlist");
+ else
+ free_empty_extents = true;
} else {
ntfs_error(ni->vol->sb, "Rollback failed to find attrlist");
}
@@ -3174,6 +3220,11 @@ rollback:
ni->attr_list_size = 0;
NInoClearAttrList(ni);
NInoClearAttrListDirty(ni);
+ ntfs_attr_put_search_ctx(ctx);
+ ctx = NULL;
+ if (free_empty_extents && ntfs_inode_free_empty_extents(ni))
+ ntfs_error(ni->vol->sb, "Rollback failed to free empty extent");
+ goto err_out;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
err_out:
@@ -3260,6 +3311,55 @@ out:
return err;
}
+/*
+ * ntfs_inode_free_empty_extents - free empty extent MFT records
+ * @ni: base inode whose empty extent records should be freed
+ *
+ * The caller must ensure that no on-disk attribute list references an empty
+ * extent record and must hold @ni->mrec_lock to serialize the extent array.
+ */
+int ntfs_inode_free_empty_extents(struct ntfs_inode *ni)
+{
+ int err = 0, i = 0;
+
+ if (!ni || ni->nr_extents < 0)
+ return -EINVAL;
+
+ mutex_lock(&ni->extent_lock);
+ while (i < ni->nr_extents) {
+ struct ntfs_inode *ext_ni = ni->ext.extent_ntfs_inos[i];
+ struct mft_record *m;
+ int ret;
+
+ m = map_mft_record(ext_ni);
+ if (IS_ERR(m)) {
+ if (!err)
+ err = PTR_ERR(m);
+ i++;
+ continue;
+ }
+ if (le32_to_cpu(m->bytes_in_use) -
+ le16_to_cpu(m->attrs_offset) != 8) {
+ unmap_mft_record(ext_ni);
+ i++;
+ continue;
+ }
+ unmap_mft_record(ext_ni);
+
+ ret = ntfs_mft_record_free(ni->vol, ext_ni);
+ if (ret) {
+ if (!err)
+ err = ret;
+ i++;
+ continue;
+ }
+ ntfs_inode_close(ext_ni);
+ /* ntfs_inode_close() removed this entry from the extent array. */
+ }
+ mutex_unlock(&ni->extent_lock);
+ return err;
+}
+
void ntfs_destroy_ext_inode(struct ntfs_inode *ni)
{
ntfs_debug("Entering.");
@@ -3359,6 +3459,9 @@ int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
* Chkdsk complain if $STANDARD_INFORMATION is not in the base MFT
* record.
*
+ * $INDEX_ROOT must remain resident, but its attribute record may be moved
+ * to an extent MFT record when the base record needs room for the list.
+ *
* Also we can't move $ATTRIBUTE_LIST from base MFT_RECORD, so position
* search context on first attribute after $STANDARD_INFORMATION and
* $ATTRIBUTE_LIST.
@@ -3400,9 +3503,6 @@ retry:
ctx->attr->type == AT_DATA)
goto retry;
- if (ctx->attr->type == AT_INDEX_ROOT)
- goto retry;
-
record_size = le32_to_cpu(ctx->attr->length);
/* Move away attribute. */
@@ -3624,7 +3724,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
FGP_CREAT | FGP_LOCK,
mapping_gfp_mask(mapping));
if (IS_ERR(folio)) {
- ret = -ENOMEM;
+ ret = PTR_ERR(folio);
break;
}
} else {
@@ -3656,6 +3756,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
u64 rl_length = 0;
s64 vcn;
struct runlist_element *rl;
+ int bio_err;
lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len));
vcn = ntfs_pidx_to_cluster(vol, folio->index);
@@ -3686,8 +3787,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,
@@ -3699,8 +3799,15 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
goto err_unlock_folio;
}
- submit_bio_wait(bio);
+ bio_err = submit_bio_wait(bio);
bio_put(bio);
+ if (bio_err) {
+ ntfs_error(vi->i_sb,
+ "Synchronous attribute write failed (%d)",
+ bio_err);
+ ret = bio_err;
+ goto err_unlock_folio;
+ }
vcn += rl_length;
offset += length;
} while (lcn_count != 0);
diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h
index 67942b97fac6..ff61bd402df0 100644
--- a/fs/ntfs/inode.h
+++ b/fs/ntfs/inode.h
@@ -142,6 +142,8 @@ struct ntfs_inode {
struct ntfs_inode *base_ntfs_ino;
} ext;
unsigned int i_dealloc_clusters;
+ __le32 reparse_tag;
+ __le32 reparse_flags;
char *target;
};
@@ -187,6 +189,7 @@ enum {
NI_NonResident,
NI_IndexAllocPresent,
NI_Compressed,
+ NI_WofCompressed,
NI_Encrypted,
NI_Sparse,
NI_SparseDisabled,
@@ -246,6 +249,7 @@ NINO_FNS(MstProtected)
NINO_FNS(NonResident)
NINO_FNS(IndexAllocPresent)
NINO_FNS(Compressed)
+NINO_FNS(WofCompressed)
NINO_FNS(Encrypted)
NINO_FNS(Sparse)
NINO_FNS(SparseDisabled)
@@ -336,6 +340,7 @@ int ntfs_get_block_mft_record(struct ntfs_inode *mft_ni, struct ntfs_inode *ni);
int __ntfs_write_inode(struct inode *vi, int sync);
int ntfs_inode_attach_all_extents(struct ntfs_inode *ni);
int ntfs_inode_add_attrlist(struct ntfs_inode *ni);
+int ntfs_inode_free_empty_extents(struct ntfs_inode *ni);
void ntfs_destroy_ext_inode(struct ntfs_inode *ni);
int ntfs_inode_free_space(struct ntfs_inode *ni, int size);
s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf);
@@ -350,7 +355,7 @@ static inline void ntfs_commit_inode(struct inode *vi)
int ntfs_inode_sync_filename(struct ntfs_inode *ni);
int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset,
- const loff_t new_size, bool bsync);
+ const loff_t new_size);
void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev);
struct folio *ntfs_get_locked_folio(struct address_space *mapping,
pgoff_t index, pgoff_t end_index, struct file_ra_state *ra);
diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c
index dc7d8c893a69..c812d7f19b36 100644
--- a/fs/ntfs/iomap.c
+++ b/fs/ntfs/iomap.c
@@ -81,7 +81,7 @@ const struct iomap_write_ops ntfs_iomap_folio_ops = {
};
static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, loff_t length,
- unsigned int flags, struct iomap *iomap)
+ unsigned int flags, struct iomap *iomap, bool keep_mrec_lock)
{
struct ntfs_inode *base_ni, *ni = NTFS_I(inode);
struct ntfs_attr_search_ctx *ctx;
@@ -89,13 +89,14 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
u32 attr_len;
int err = 0;
char *kattr;
- struct page *ipage;
if (NInoAttr(ni))
base_ni = ni->ext.base_ntfs_ino;
else
base_ni = ni;
+ mutex_lock(&base_ni->mrec_lock);
+
ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
if (!ctx) {
err = -ENOMEM;
@@ -130,23 +131,22 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
- ipage = alloc_page(GFP_NOFS | __GFP_ZERO);
- if (!ipage) {
- err = -ENOMEM;
- goto out;
- }
-
- memcpy(page_address(ipage), kattr, attr_len);
iomap->type = IOMAP_INLINE;
- iomap->inline_data = page_address(ipage);
+ iomap->inline_data = kattr;
iomap->offset = 0;
iomap->length = attr_len;
- iomap->private = ipage;
out:
if (ctx)
ntfs_attr_put_search_ctx(ctx);
+ if (!err && keep_mrec_lock && iomap->type == IOMAP_INLINE) {
+ iomap->private = base_ni;
+ return 0;
+ }
+
+ mutex_unlock(&base_ni->mrec_lock);
+
return err;
}
@@ -270,37 +270,37 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
static int __ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap, struct iomap *srcmap,
- bool need_unwritten)
+ bool need_unwritten, bool keep_mrec_lock)
{
if (NInoNonResident(NTFS_I(inode)))
return ntfs_read_iomap_begin_non_resident(inode, offset, length,
flags, iomap, need_unwritten);
return ntfs_read_iomap_begin_resident(inode, offset, length,
- flags, iomap);
+ flags, iomap, keep_mrec_lock);
}
static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
{
return __ntfs_read_iomap_begin(inode, offset, length, flags, iomap,
- srcmap, true);
+ srcmap, true, true);
}
static int ntfs_read_iomap_end(struct inode *inode, loff_t pos, loff_t length,
ssize_t written, unsigned int flags, struct iomap *iomap)
{
- if (iomap->type == IOMAP_INLINE) {
- struct page *ipage = iomap->private;
-
- put_page(ipage);
- }
+ struct ntfs_inode *base_ni = iomap->private;
+ if (base_ni)
+ mutex_unlock(&base_ni->mrec_lock);
return written;
}
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_read_iomap_next,
+ ntfs_read_iomap_begin, ntfs_read_iomap_end);
+
const struct iomap_ops ntfs_read_iomap_ops = {
- .iomap_begin = ntfs_read_iomap_begin,
- .iomap_end = ntfs_read_iomap_end,
+ .iomap_next = ntfs_read_iomap_next,
};
/*
@@ -340,7 +340,7 @@ static int ntfs_seek_iomap_begin(struct inode *inode, loff_t offset, loff_t leng
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
{
return __ntfs_read_iomap_begin(inode, offset, length, flags, iomap,
- srcmap, false);
+ srcmap, false, false);
}
static int ntfs_zero_read_iomap_end(struct inode *inode, loff_t pos, loff_t length,
@@ -351,14 +351,17 @@ static int ntfs_zero_read_iomap_end(struct inode *inode, loff_t pos, loff_t leng
return written;
}
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_zero_read_iomap_next,
+ ntfs_seek_iomap_begin, ntfs_zero_read_iomap_end);
+
static const struct iomap_ops ntfs_zero_read_iomap_ops = {
- .iomap_begin = ntfs_seek_iomap_begin,
- .iomap_end = ntfs_zero_read_iomap_end,
+ .iomap_next = ntfs_zero_read_iomap_next,
};
+static DEFINE_IOMAP_ITER_NEXT(ntfs_seek_iomap_next, ntfs_seek_iomap_begin);
+
const struct iomap_ops ntfs_seek_iomap_ops = {
- .iomap_begin = ntfs_seek_iomap_begin,
- .iomap_end = ntfs_read_iomap_end,
+ .iomap_next = ntfs_seek_iomap_next,
};
int ntfs_dio_zero_range(struct inode *inode, loff_t offset, loff_t length)
@@ -659,7 +662,6 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
u32 attr_len;
int err = 0;
char *kattr;
- struct page *ipage;
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx) {
@@ -680,24 +682,18 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
attr_len = le32_to_cpu(a->data.resident.value_length);
kattr = (u8 *)a + le16_to_cpu(a->data.resident.value_offset);
- ipage = alloc_page(GFP_NOFS | __GFP_ZERO);
- if (!ipage) {
- err = -ENOMEM;
- goto out;
- }
-
- memcpy(page_address(ipage), kattr, attr_len);
iomap->type = IOMAP_INLINE;
- iomap->inline_data = page_address(ipage);
+ iomap->inline_data = kattr;
iomap->offset = 0;
- /* iomap requires there is only one INLINE_DATA extent */
iomap->length = attr_len;
- iomap->private = ipage;
out:
if (ctx)
ntfs_attr_put_search_ctx(ctx);
- mutex_unlock(&ni->mrec_lock);
+
+ if (err)
+ mutex_unlock(&ni->mrec_lock);
+
return err;
}
@@ -705,21 +701,7 @@ static int ntfs_write_iomap_begin_non_resident(struct inode *inode, loff_t offse
loff_t length, unsigned int flags,
struct iomap *iomap, int ntfs_iomap_flags)
{
- struct ntfs_inode *ni = NTFS_I(inode);
-
- if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) &&
- offset + length > ni->initialized_size) {
- int ret;
-
- ret = ntfs_extend_initialized_size(inode, offset,
- offset + length,
- ntfs_iomap_flags &
- NTFS_IOMAP_FLAGS_DIO);
- if (ret < 0)
- return ret;
- }
-
- mutex_lock(&ni->mrec_lock);
+ mutex_lock(&NTFS_I(inode)->mrec_lock);
if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_BEGIN)
return ntfs_write_simple_iomap_begin_non_resident(inode, offset,
length, iomap);
@@ -735,28 +717,10 @@ static int __ntfs_write_iomap_begin(struct inode *inode, loff_t offset,
struct iomap *iomap, int ntfs_iomap_flags)
{
struct ntfs_inode *ni = NTFS_I(inode);
- loff_t end = offset + length;
if (NVolShutdown(ni->vol))
return -EIO;
- if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) &&
- end > ni->data_size) {
- struct ntfs_volume *vol = ni->vol;
- int ret;
-
- mutex_lock(&ni->mrec_lock);
- if (end > ni->allocated_size &&
- end < ni->allocated_size + vol->preallocated_size)
- ret = ntfs_attr_expand(ni, end,
- ni->allocated_size + vol->preallocated_size);
- else
- ret = ntfs_attr_expand(ni, end, 0);
- mutex_unlock(&ni->mrec_lock);
- if (ret)
- return ret;
- }
-
if (!NInoNonResident(ni)) {
mutex_lock(&ni->mrec_lock);
return ntfs_write_iomap_begin_resident(inode, offset, iomap);
@@ -778,43 +742,10 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
unsigned int flags, struct iomap *iomap)
{
struct ntfs_inode *ni = NTFS_I(inode);
- struct ntfs_attr_search_ctx *ctx;
- u32 attr_len;
- int err;
- char *kattr;
- struct page *ipage = iomap->private;
-
- mutex_lock(&ni->mrec_lock);
- ctx = ntfs_attr_get_search_ctx(ni, NULL);
- if (!ctx) {
- written = -ENOMEM;
- goto err_out;
- }
-
- err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
- CASE_SENSITIVE, 0, NULL, 0, ctx);
- if (err) {
- if (err == -ENOENT)
- err = -EIO;
- written = err;
- goto err_out;
- }
-
- /* The total length of the attribute value. */
- attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
- if (pos >= attr_len || pos + written > attr_len)
- goto err_out;
- kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
- memcpy(kattr + pos, iomap_inline_data(iomap, pos), written);
- mark_mft_record_dirty(ctx->ntfs_ino);
-err_out:
- if (ctx)
- ntfs_attr_put_search_ctx(ctx);
- put_page(ipage);
+ mark_mft_record_dirty(ni);
mutex_unlock(&ni->mrec_lock);
return written;
-
}
static int ntfs_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
@@ -827,9 +758,11 @@ static int ntfs_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
return written;
}
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_write_iomap_next,
+ ntfs_write_iomap_begin, ntfs_write_iomap_end);
+
const struct iomap_ops ntfs_write_iomap_ops = {
- .iomap_begin = ntfs_write_iomap_begin,
- .iomap_end = ntfs_write_iomap_end,
+ .iomap_next = ntfs_write_iomap_next,
};
static int ntfs_page_mkwrite_iomap_begin(struct inode *inode, loff_t offset,
@@ -840,9 +773,11 @@ static int ntfs_page_mkwrite_iomap_begin(struct inode *inode, loff_t offset,
NTFS_IOMAP_FLAGS_MKWRITE);
}
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_page_mkwrite_iomap_next,
+ ntfs_page_mkwrite_iomap_begin, ntfs_write_iomap_end);
+
const struct iomap_ops ntfs_page_mkwrite_iomap_ops = {
- .iomap_begin = ntfs_page_mkwrite_iomap_begin,
- .iomap_end = ntfs_write_iomap_end,
+ .iomap_next = ntfs_page_mkwrite_iomap_next,
};
static int ntfs_dio_iomap_begin(struct inode *inode, loff_t offset,
@@ -853,9 +788,11 @@ static int ntfs_dio_iomap_begin(struct inode *inode, loff_t offset,
NTFS_IOMAP_FLAGS_DIO);
}
+static DEFINE_IOMAP_ITER_NEXT_END(ntfs_dio_iomap_next,
+ ntfs_dio_iomap_begin, ntfs_write_iomap_end);
+
const struct iomap_ops ntfs_dio_iomap_ops = {
- .iomap_begin = ntfs_dio_iomap_begin,
- .iomap_end = ntfs_write_iomap_end,
+ .iomap_next = ntfs_dio_iomap_next,
};
static ssize_t ntfs_writeback_range(struct iomap_writepage_ctx *wpc,
diff --git a/fs/ntfs/layout.h b/fs/ntfs/layout.h
index d94f914e830f..9438fd9b668e 100644
--- a/fs/ntfs/layout.h
+++ b/fs/ntfs/layout.h
@@ -2267,6 +2267,8 @@ enum {
IO_REPARSE_PLUGIN_SELECT = cpu_to_le32(0xffff0fff),
};
+#define SYMLINK_FLAG_RELATIVE 1
+
/*
* struct reparse_point - $REPARSE_POINT attribute content (0xc0)\
*
@@ -2287,6 +2289,23 @@ struct reparse_point {
u8 reparse_data[];
} __packed;
+struct mount_point_reparse_data {
+ __le16 substitute_name_offset;
+ __le16 substitute_name_length;
+ __le16 print_name_offset;
+ __le16 print_name_length;
+ __le16 path_buffer[];
+} __packed;
+
+struct symlink_reparse_data {
+ __le16 substitute_name_offset;
+ __le16 substitute_name_length;
+ __le16 print_name_offset;
+ __le16 print_name_length;
+ __le32 flags;
+ __le16 path_buffer[];
+} __packed;
+
/*
* struct ea_information - $EA_INFORMATION attribute content (0xd0)
*
diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c
index 835a041023a2..0d6cd08ee2e7 100644
--- a/fs/ntfs/lcnalloc.c
+++ b/fs/ntfs/lcnalloc.c
@@ -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.");
@@ -298,7 +298,12 @@ struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol, const s64 st
clusters = count;
rlpos = rlsize = 0;
mapping = lcnbmp_vi->i_mapping;
- i_size = i_size_read(lcnbmp_vi);
+ /*
+ * lcn_empty_bits_per_page is sized from nr_clusters, but $Bitmap can
+ * cover more clusters than that; bound the scan by the array.
+ */
+ i_size = min_t(s64, i_size_read(lcnbmp_vi),
+ ((s64)vol->nr_clusters + 7) >> 3);
while (1) {
ntfs_debug("Start of outer while loop: done_zones 0x%x, search_zone %i, pass %i, zone_start 0x%llx, zone_end 0x%llx, bmp_initial_pos 0x%llx, bmp_pos 0x%llx, rlpos %i, rlsize %i.",
done_zones, search_zone, pass,
@@ -1040,8 +1045,9 @@ err_out:
"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);
diff --git a/fs/ntfs/lib/decompress_common.c b/fs/ntfs/lib/decompress_common.c
new file mode 100644
index 000000000000..1705face42a3
--- /dev/null
+++ b/fs/ntfs/lib/decompress_common.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: MIT
+/*
+ * decompress_common.c - Code shared by the XPRESS and LZX decompressors
+ *
+ * This is a port of the upstream wimlib "decompress_common.c" which builds
+ * subtable-based Huffman decode tables, as opposed to the older
+ * binary-tree-based format previously used in this library. The vectorized
+ * (SSE2/AVX2) fill paths are omitted for portability in the kernel.
+ *
+ * Copyright (C) 2022 Eric Biggers
+ */
+
+#include "decompress_common.h"
+
+/* Compute the number of bits with which a subtable must be indexed for a
+ * codeword of length @codeword_len, given that the root table is indexed with
+ * @table_bits bits.
+ */
+static u32 compute_subtable_bits(u32 table_bits,
+ u32 codeword_len, u16 len_counts[])
+{
+ u32 subtable_bits = codeword_len - table_bits;
+ s32 remainder = (s32)1 << subtable_bits;
+
+ for (;;) {
+ remainder -= len_counts[table_bits + subtable_bits];
+ if (remainder <= 0)
+ break;
+ subtable_bits++;
+ remainder <<= 1;
+ }
+ return subtable_bits;
+}
+
+/* Build the subtables for codewords longer than table_bits. */
+static int build_subtables(u16 decode_table[], u32 num_syms, u32 table_bits,
+ u16 len_counts[], const u16 sorted_syms[], u32 sym_idx,
+ u32 decode_table_pos, u32 decode_table_size)
+{
+ u32 subtable_pos = 1U << table_bits;
+ u32 subtable_bits = table_bits;
+ u32 subtable_prefix = (u32)-1;
+ u32 codeword_len = table_bits + 1;
+ u32 codeword = decode_table_pos << 1;
+ u32 prefix;
+ u16 entry;
+ u32 n;
+
+ for (; sym_idx < num_syms; sym_idx++) {
+ while (len_counts[codeword_len] == 0) {
+ codeword_len++;
+ codeword <<= 1;
+ }
+
+ prefix = codeword >> (codeword_len - table_bits);
+
+ if (prefix != subtable_prefix) {
+ subtable_prefix = prefix;
+ subtable_bits = compute_subtable_bits(table_bits, codeword_len,
+ len_counts);
+ decode_table[subtable_prefix] =
+ MAKE_DECODE_TABLE_ENTRY(subtable_pos, subtable_bits);
+ }
+
+ entry = MAKE_DECODE_TABLE_ENTRY(sorted_syms[sym_idx],
+ codeword_len - table_bits);
+ n = 1U << (subtable_bits - (codeword_len - table_bits));
+
+ /* Defensive bound check: 'lens' is derived from untrusted
+ * on-disk compressed data, and subtable growth depends on
+ * its content. This should never trigger for a correctly
+ * sized DECODE_TABLE_ENOUGH() value, but turns a wrong value
+ * into a clean decode failure instead of writing past the
+ * caller's decode_table[].
+ */
+ if (unlikely(subtable_pos + n > decode_table_size))
+ return -1;
+
+ do {
+ decode_table[subtable_pos++] = entry;
+ } while (--n);
+
+ len_counts[codeword_len]--;
+ codeword++;
+ }
+
+ return 0;
+}
+
+/*
+ * Given an alphabet of symbols and the length of each symbol's codeword in a
+ * canonical prefix code, build a table for quickly decoding symbols that were
+ * encoded with that code.
+ *
+ * The root table is indexed with 'table_bits' bits. Codewords not longer than
+ * 'table_bits' are decoded directly from the root table. Longer codewords are
+ * decoded via subtables: the corresponding root entry is a pointer (the index
+ * of the subtable plus the number of bits with which the subtable is indexed),
+ * and the subtable is indexed with the remaining bits of the codeword.
+ *
+ * Each entry stores both the symbol (high 12 bits) and the codeword length (low
+ * 4 bits), so a single lookup yields the symbol and lets the bitstream be
+ * advanced by the correct number of bits.
+ *
+ * @decode_table: array in which to build the table (declared with
+ * DECODE_TABLE()). May alias @lens.
+ * @num_syms: number of symbols in the alphabet.
+ * @table_bits: log2 of the number of root table entries.
+ * @lens: array of @num_syms codeword lengths, indexed by symbol.
+ * @max_codeword_len: longest codeword length allowed for this code.
+ * @working_space: temporary array declared with DECODE_TABLE_WORKING_SPACE().
+ * @decode_table_size: number of u16 entries in @decode_table (i.e.
+ * ARRAY_SIZE(decode_table) at the call site). Used only as a
+ * defensive bound check against @lens-dependent subtable growth.
+ *
+ * Returns 0 on success, or -1 if the lengths do not form a valid prefix code,
+ * or if building the subtables would overflow @decode_table_size entries.
+ */
+int make_huffman_decode_table(u16 decode_table[], u32 num_syms, u32 table_bits,
+ const u8 lens[], u32 max_codeword_len,
+ u16 working_space[], u32 decode_table_size)
+{
+ u16 *const len_counts = &working_space[0];
+ u16 *const offsets = &working_space[1 * (max_codeword_len + 1)];
+ u16 *const sorted_syms = &working_space[2 * (max_codeword_len + 1)];
+ u32 decode_table_pos = 0;
+ u32 sym_idx;
+ u32 codeword_len;
+ s32 remainder = 1;
+ void *entry_ptr = decode_table;
+ u32 len;
+ u32 sym;
+
+ /* Count how many codewords have each length, including 0. */
+ for (len = 0; len <= max_codeword_len; len++)
+ len_counts[len] = 0;
+ for (sym = 0; sym < num_syms; sym++)
+ len_counts[lens[sym]]++;
+
+ /* A codeword of length n should require a proportion of the codespace
+ * equaling (1/2)^n. The code is complete iff the codespace is exactly
+ * filled by the lengths.
+ */
+ for (len = 1; len <= max_codeword_len; len++) {
+ remainder = (remainder << 1) - len_counts[len];
+ if (unlikely(remainder < 0))
+ return -1; /* over-subscribed */
+ }
+
+ if (remainder != 0) {
+ /* Incomplete code. Permitted only if the code is empty. */
+ if (unlikely(remainder != (s32)(1U << max_codeword_len)))
+ return -1;
+
+ /* Empty code: zero the root table so lookups yield symbol 0
+ * without consuming any bits.
+ */
+ memset(decode_table, 0, sizeof(decode_table[0]) << table_bits);
+ return 0;
+ }
+
+ /* Sort the symbols primarily by increasing codeword length and
+ * secondarily by increasing symbol value.
+ */
+ offsets[0] = 0;
+ for (len = 0; len < max_codeword_len; len++)
+ offsets[len + 1] = offsets[len] + len_counts[len];
+ for (sym = 0; sym < num_syms; sym++)
+ sorted_syms[offsets[lens[sym]]++] = sym;
+
+ /* Fill the root table entries for codewords no longer than table_bits. */
+ sym_idx = offsets[0];
+ codeword_len = 1;
+ for (; codeword_len <= table_bits; codeword_len++) {
+ u32 stores_per_loop = 1U << (table_bits - codeword_len);
+ u32 end_sym_idx = sym_idx + len_counts[codeword_len];
+
+ for (; sym_idx < end_sym_idx; sym_idx++) {
+ u16 v = MAKE_DECODE_TABLE_ENTRY(sorted_syms[sym_idx],
+ codeword_len);
+ u32 n = stores_per_loop;
+ u16 *p = entry_ptr;
+
+ do {
+ *p++ = v;
+ } while (--n);
+ entry_ptr = p;
+ }
+ }
+ decode_table_pos = (u16 *)entry_ptr - decode_table;
+
+ /* If all symbols were processed, no subtables are required. */
+ if (sym_idx == num_syms)
+ return 0;
+
+ /* At least one subtable is required. Process the remaining symbols. */
+ return build_subtables(decode_table, num_syms, table_bits, len_counts,
+ sorted_syms, sym_idx, decode_table_pos,
+ decode_table_size);
+}
diff --git a/fs/ntfs/lib/decompress_common.h b/fs/ntfs/lib/decompress_common.h
new file mode 100644
index 000000000000..9bf85cd52e6d
--- /dev/null
+++ b/fs/ntfs/lib/decompress_common.h
@@ -0,0 +1,444 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * decompress_common.h - Code shared by the XPRESS and LZX decompressors
+ *
+ * This is a port of the upstream wimlib "decompress_common.h" which uses a
+ * subtable-based Huffman decode table format, as opposed to the older
+ * binary-tree-based format previously used in this library.
+ *
+ * Copyright (C) 2022 Eric Biggers
+ */
+
+#ifndef _LINUX_NTFS_LIB_DECOMPRESS_COMMON_H
+#define _LINUX_NTFS_LIB_DECOMPRESS_COMMON_H
+
+#include <linux/compiler.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+/* "Force inline" macro (not required, but helpful for performance). */
+#define forceinline __always_inline
+
+/* Size of a machine word. */
+#define WORDBYTES sizeof(size_t)
+#define WORDBITS (8 * WORDBYTES)
+
+/* UNALIGNED_ACCESS_IS_FAST should be 1 if unaligned memory accesses can be
+ * performed efficiently on the target platform.
+ */
+#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+# define UNALIGNED_ACCESS_IS_FAST 1
+#else
+# define UNALIGNED_ACCESS_IS_FAST 0
+#endif
+
+/* Deprecated name kept for compatibility with the upstream source. */
+#define FAST_UNALIGNED_ACCESS UNALIGNED_ACCESS_IS_FAST
+
+/* likely()/unlikely() are provided by <linux/compiler.h>. */
+
+/* STATIC_ASSERT() - verify the truth of an expression at compile time. */
+#define STATIC_ASSERT(expr) ((void)sizeof(char[1 - 2 * !(expr)]))
+
+/* STATIC_ASSERT_ZERO() - like STATIC_ASSERT() but evaluates to 0 so it can be
+ * used in constant expressions.
+ */
+#define STATIC_ASSERT_ZERO(expr) ((int)sizeof(char[-!(expr)]))
+
+/* Unaligned word load/store helpers. */
+static forceinline size_t load_word_unaligned(const void *p)
+{
+ size_t v;
+
+ memcpy(&v, p, sizeof(v));
+ return v;
+}
+
+static forceinline void store_word_unaligned(size_t v, void *p)
+{
+ memcpy(p, &v, sizeof(v));
+}
+
+static forceinline void copy_word_unaligned(const void *src, void *dst)
+{
+ store_word_unaligned(load_word_unaligned(src), dst);
+}
+
+static forceinline size_t repeat_u16(u16 b)
+{
+ size_t v = b;
+
+ STATIC_ASSERT(WORDBITS == 32 || WORDBITS == 64);
+ v |= v << 16;
+ v |= v << ((WORDBITS == 64) ? 32 : 0);
+ return v;
+}
+
+static forceinline size_t repeat_byte(u8 b)
+{
+ return repeat_u16(((u16)b << 8) | b);
+}
+
+/******************************************************************************/
+/* Input bitstream for XPRESS and LZX */
+/*----------------------------------------------------------------------------*/
+
+/* Structure that encapsulates a block of in-memory data being interpreted as a
+ * stream of bits, optionally with interwoven literal bytes. Bits are assumed
+ * to be stored in little endian 16-bit coding units, with the bits ordered high
+ * to low.
+ */
+struct input_bitstream {
+ /* Bits that have been read from the input buffer. The bits are
+ * left-justified; the next bit is always bit 31.
+ */
+ u32 bitbuf;
+
+ /* Number of bits currently held in @bitbuf. */
+ u32 bitsleft;
+
+ /* Pointer to the next byte to be retrieved from the input buffer. */
+ const u8 *next;
+
+ /* Pointer past the end of the input buffer. */
+ const u8 *end;
+};
+
+/* Initialize a bitstream to read from the specified input buffer. */
+static forceinline void init_input_bitstream(struct input_bitstream *is,
+ const void *buffer, u32 size)
+{
+ is->bitbuf = 0;
+ is->bitsleft = 0;
+ is->next = buffer;
+ is->end = is->next + size;
+}
+
+/* Note: for performance reasons, the following methods don't return error
+ * codes to the caller if the input buffer is overrun. Instead, they just
+ * assume that all overrun data is zeroes.
+ */
+
+/* Ensure the bit buffer variable for the bitstream contains at least @num_bits
+ * bits. Following this, bitstream_peek_bits() and/or bitstream_remove_bits()
+ * may be called on the bitstream to peek or remove up to @num_bits bits. This
+ * works for at most 16 bits, which is sufficient for LZX (max codeword length
+ * 16) and XPRESS (max codeword length 15).
+ */
+static forceinline void bitstream_ensure_bits(struct input_bitstream *is,
+ unsigned int num_bits)
+{
+ if (is->bitsleft >= num_bits)
+ return;
+
+ if (unlikely(is->end - is->next < 2))
+ goto overflow;
+
+ is->bitbuf |= (u32)get_unaligned_le16(is->next) << (16 - is->bitsleft);
+ is->next += 2;
+ is->bitsleft += 16;
+ return;
+
+overflow:
+ is->bitsleft = 32;
+}
+
+/* Return the next @num_bits bits from the bitstream, without removing them.
+ * There must be at least @num_bits remaining in the buffer variable.
+ */
+static forceinline u32 bitstream_peek_bits(const struct input_bitstream *is,
+ unsigned int num_bits)
+{
+ return (is->bitbuf >> 1) >> (sizeof(is->bitbuf) * 8 - num_bits - 1);
+}
+
+/* Remove @num_bits from the bitstream. */
+static forceinline void bitstream_remove_bits(struct input_bitstream *is,
+ unsigned int num_bits)
+{
+ is->bitbuf <<= num_bits;
+ is->bitsleft -= num_bits;
+}
+
+/* Remove and return @num_bits bits from the bitstream. */
+static forceinline u32 bitstream_pop_bits(struct input_bitstream *is,
+ unsigned int num_bits)
+{
+ u32 bits = bitstream_peek_bits(is, num_bits);
+
+ bitstream_remove_bits(is, num_bits);
+ return bits;
+}
+
+/* Read and return the next @num_bits bits from the bitstream. */
+static forceinline u32 bitstream_read_bits(struct input_bitstream *is,
+ unsigned int num_bits)
+{
+ bitstream_ensure_bits(is, num_bits);
+ return bitstream_pop_bits(is, num_bits);
+}
+
+/* Read and return the next literal byte embedded in the bitstream. */
+static forceinline u8 bitstream_read_byte(struct input_bitstream *is)
+{
+ if (unlikely(is->end == is->next))
+ return 0;
+ return *is->next++;
+}
+
+/* Read and return the next 16-bit integer embedded in the bitstream. */
+static forceinline u16 bitstream_read_u16(struct input_bitstream *is)
+{
+ u16 v;
+
+ if (unlikely(is->end - is->next < 2))
+ return 0;
+ v = get_unaligned_le16(is->next);
+ is->next += 2;
+ return v;
+}
+
+/* Read and return the next 32-bit integer embedded in the bitstream. */
+static forceinline u32 bitstream_read_u32(struct input_bitstream *is)
+{
+ u32 v;
+
+ if (unlikely(is->end - is->next < 4))
+ return 0;
+ v = get_unaligned_le32(is->next);
+ is->next += 4;
+ return v;
+}
+
+/* Read into @dst_buffer an array of literal bytes embedded in the bitstream.
+ * Return 0 if there were enough bytes remaining in the input, otherwise -1.
+ */
+static forceinline int bitstream_read_bytes(struct input_bitstream *is,
+ void *dst_buffer, size_t count)
+{
+ if (unlikely((size_t)(is->end - is->next) < count))
+ return -1;
+ memcpy(dst_buffer, is->next, count);
+ is->next += count;
+ return 0;
+}
+
+/* Align the input bitstream on a coding-unit boundary. */
+static forceinline void bitstream_align(struct input_bitstream *is)
+{
+ is->bitsleft = 0;
+ is->bitbuf = 0;
+}
+
+/******************************************************************************/
+/* Huffman decoding */
+/*----------------------------------------------------------------------------*/
+
+/*
+ * Required alignment for the Huffman decode tables. We require this alignment
+ * so that we can fill the entries with word instructions without having to deal
+ * with misaligned buffers.
+ */
+#define DECODE_TABLE_ALIGNMENT 16
+
+/*
+ * Each decode table entry is 16 bits divided into two fields: 'symbol' (high 12
+ * bits) and 'length' (low 4 bits). See the comments in decompress_common.c for
+ * the precise meaning of these fields depending on the entry type.
+ */
+#define DECODE_TABLE_SYMBOL_SHIFT 4
+#define DECODE_TABLE_MAX_SYMBOL ((1 << (16 - DECODE_TABLE_SYMBOL_SHIFT)) - 1)
+#define DECODE_TABLE_MAX_LENGTH ((1 << DECODE_TABLE_SYMBOL_SHIFT) - 1)
+#define DECODE_TABLE_LENGTH_MASK DECODE_TABLE_MAX_LENGTH
+#define MAKE_DECODE_TABLE_ENTRY(symbol, length) \
+ (((symbol) << DECODE_TABLE_SYMBOL_SHIFT) | (length))
+
+/*
+ * Read and return the next Huffman-encoded symbol from the given bitstream
+ * using the given decode table. If the input data is exhausted, then the
+ * Huffman symbol will be decoded as if the missing bits were all zeroes.
+ */
+static forceinline unsigned int read_huffsym(struct input_bitstream *is,
+ const u16 decode_table[],
+ unsigned int table_bits,
+ unsigned int max_codeword_len)
+{
+ unsigned int entry;
+ unsigned int symbol;
+ unsigned int length;
+
+ /* Preload the bitbuffer with 'max_codeword_len' bits. */
+ bitstream_ensure_bits(is, max_codeword_len);
+
+ /* Index the root table by the next 'table_bits' bits of input. */
+ entry = decode_table[bitstream_peek_bits(is, table_bits)];
+
+ /* Extract the "symbol" and "length" from the entry. */
+ symbol = entry >> DECODE_TABLE_SYMBOL_SHIFT;
+ length = entry & DECODE_TABLE_LENGTH_MASK;
+
+ /* If the codeword is longer than 'table_bits', the root entry is a
+ * subtable pointer. Discard the bits used to index the root table and
+ * index the subtable by the next 'length' bits.
+ */
+ if (max_codeword_len > table_bits &&
+ entry >= (1U << (table_bits + DECODE_TABLE_SYMBOL_SHIFT))) {
+ bitstream_remove_bits(is, table_bits);
+ entry = decode_table[symbol + bitstream_peek_bits(is, length)];
+ symbol = entry >> DECODE_TABLE_SYMBOL_SHIFT;
+ length = entry & DECODE_TABLE_LENGTH_MASK;
+ }
+
+ /* Discard the (remaining) bits of the codeword. */
+ bitstream_remove_bits(is, length);
+
+ return symbol;
+}
+
+/*
+ * DECODE_TABLE_ENOUGH() evaluates to the maximum number of decode table
+ * entries, including all subtable entries, that may be required for decoding a
+ * given Huffman code. It is a compile-time mapping computed by the zlib
+ * 'enough' utility. An unknown combination produces a build error.
+ */
+#define DECODE_TABLE_ENOUGH(num_syms, table_bits, max_codeword_len) ( \
+ ((num_syms) == 8 && (table_bits) == 5 && (max_codeword_len) == 7) ? 36 : \
+ ((num_syms) == 8 && (table_bits) == 6 && (max_codeword_len) == 7) ? 66 : \
+ ((num_syms) == 8 && (table_bits) == 7 && (max_codeword_len) == 7) ? 128 : \
+ ((num_syms) == 20 && (table_bits) == 5 && (max_codeword_len) == 15) ? 1062 : \
+ ((num_syms) == 20 && (table_bits) == 6 && (max_codeword_len) == 15) ? 582 : \
+ ((num_syms) == 20 && (table_bits) == 7 && (max_codeword_len) == 15) ? 390 : \
+ ((num_syms) == 54 && (table_bits) == 9 && (max_codeword_len) == 15) ? 618 : \
+ ((num_syms) == 54 && (table_bits) == 10 && (max_codeword_len) == 15) ? 1098 : \
+ ((num_syms) == 249 && (table_bits) == 9 && (max_codeword_len) == 16) ? 878 : \
+ ((num_syms) == 249 && (table_bits) == 10 && (max_codeword_len) == 16) ? 1326 : \
+ ((num_syms) == 249 && (table_bits) == 11 && (max_codeword_len) == 16) ? 2318 : \
+ ((num_syms) == 496 && (table_bits) == 11 && (max_codeword_len) == 16) ? 2566 : \
+ ((num_syms) == 256 && (table_bits) == 9 && (max_codeword_len) == 15) ? 822 : \
+ ((num_syms) == 256 && (table_bits) == 10 && (max_codeword_len) == 15) ? 1302 : \
+ ((num_syms) == 256 && (table_bits) == 11 && (max_codeword_len) == 15) ? 2310 : \
+ ((num_syms) == 512 && (table_bits) == 10 && (max_codeword_len) == 15) ? 1558 : \
+ ((num_syms) == 512 && (table_bits) == 11 && (max_codeword_len) == 15) ? 2566 : \
+ ((num_syms) == 512 && (table_bits) == 12 && (max_codeword_len) == 15) ? 4606 : \
+ ((num_syms) == 656 && (table_bits) == 10 && (max_codeword_len) == 16) ? 1734 : \
+ ((num_syms) == 656 && (table_bits) == 11 && (max_codeword_len) == 16) ? 2726 : \
+ ((num_syms) == 656 && (table_bits) == 12 && (max_codeword_len) == 16) ? 4758 : \
+ ((num_syms) == 799 && (table_bits) == 9 && (max_codeword_len) == 15) ? 1366 : \
+ ((num_syms) == 799 && (table_bits) == 10 && (max_codeword_len) == 15) ? 1846 : \
+ ((num_syms) == 799 && (table_bits) == 11 && (max_codeword_len) == 15) ? 2854 : \
+ -1)
+
+/* Wrapper around DECODE_TABLE_ENOUGH() that does additional compile-time
+ * validation.
+ */
+#define DECODE_TABLE_SIZE(num_syms, table_bits, max_codeword_len) ( \
+ STATIC_ASSERT_ZERO((num_syms) > 0) + \
+ STATIC_ASSERT_ZERO((table_bits) > 0) + \
+ STATIC_ASSERT_ZERO((max_codeword_len) > 0) + \
+ STATIC_ASSERT_ZERO((num_syms) <= 1U << (max_codeword_len)) + \
+ STATIC_ASSERT_ZERO((table_bits) <= (max_codeword_len)) + \
+ STATIC_ASSERT_ZERO((num_syms) - 1 <= DECODE_TABLE_MAX_SYMBOL) + \
+ STATIC_ASSERT_ZERO((table_bits) <= DECODE_TABLE_MAX_LENGTH) + \
+ STATIC_ASSERT_ZERO((max_codeword_len) - (table_bits) <= \
+ DECODE_TABLE_MAX_LENGTH) + \
+ STATIC_ASSERT_ZERO((1U << table_bits) > (num_syms) - 1) + \
+ STATIC_ASSERT_ZERO(DECODE_TABLE_ENOUGH( \
+ (num_syms), (table_bits), \
+ (max_codeword_len)) > 0) + \
+ STATIC_ASSERT_ZERO(DECODE_TABLE_ENOUGH( \
+ (num_syms), (table_bits), \
+ (max_codeword_len)) - 1 <= \
+ DECODE_TABLE_MAX_SYMBOL) + \
+ DECODE_TABLE_ENOUGH((num_syms), (table_bits), \
+ (max_codeword_len)) \
+)
+
+/* Declare the decode table for a Huffman code. */
+#define DECODE_TABLE(name, num_syms, table_bits, max_codeword_len) \
+ u16 name[DECODE_TABLE_SIZE((num_syms), (table_bits), \
+ (max_codeword_len))] \
+ __aligned(DECODE_TABLE_ALIGNMENT)
+
+/* Declare the temporary "working_space" array needed for building the decode
+ * table for a Huffman code.
+ */
+#define DECODE_TABLE_WORKING_SPACE(name, num_syms, max_codeword_len) \
+ u16 name[2 * ((max_codeword_len) + 1) + (num_syms)]
+
+int make_huffman_decode_table(u16 decode_table[], u32 num_syms,
+ u32 table_bits, const u8 lens[],
+ u32 max_codeword_len, u16 working_space[],
+ u32 decode_table_size);
+
+/******************************************************************************/
+/* LZ match copying */
+/*----------------------------------------------------------------------------*/
+
+/*
+ * Copy an LZ77 match of 'length' bytes from the match source at 'out_next -
+ * offset' to the match destination at 'out_next'. The source and destination
+ * may overlap. This handles validating the length and offset; it returns 0 if
+ * the match was valid (and was copied), otherwise -1.
+ */
+static forceinline int lz_copy(u32 length, u32 offset, u8 *out_begin,
+ u8 *out_next, u8 *out_end, u32 min_length)
+{
+ const u8 *src;
+ u8 *end;
+
+ /* Validate the offset. */
+ if (unlikely(offset > (u32)(out_next - out_begin)))
+ return -1;
+
+ src = out_next - offset;
+
+ /* Fast path: copy a short, non-overlapping match whose end is not too
+ * close to the end of the buffer.
+ */
+ if (UNALIGNED_ACCESS_IS_FAST && length <= 3 * WORDBYTES &&
+ offset >= WORDBYTES && out_end - out_next >= 3 * WORDBYTES) {
+ copy_word_unaligned(src + WORDBYTES * 0, out_next + WORDBYTES * 0);
+ copy_word_unaligned(src + WORDBYTES * 1, out_next + WORDBYTES * 1);
+ copy_word_unaligned(src + WORDBYTES * 2, out_next + WORDBYTES * 2);
+ return 0;
+ }
+
+ /* Validate the length. */
+ if (unlikely(length > (u32)(out_end - out_next)))
+ return -1;
+ end = out_next + length;
+
+ if (UNALIGNED_ACCESS_IS_FAST && likely(out_end - end >= WORDBYTES - 1)) {
+ if (offset >= WORDBYTES) {
+ do {
+ copy_word_unaligned(src, out_next);
+ src += WORDBYTES;
+ out_next += WORDBYTES;
+ } while (out_next < end);
+ return 0;
+ } else if (offset == 1) {
+ size_t v = repeat_byte(*(out_next - 1));
+
+ do {
+ store_word_unaligned(v, out_next);
+ src += WORDBYTES;
+ out_next += WORDBYTES;
+ } while (out_next < end);
+ return 0;
+ }
+ }
+
+ /* Fall back to a bytewise copy. */
+ if (min_length >= 2)
+ *out_next++ = *src++;
+ if (min_length >= 3)
+ *out_next++ = *src++;
+ do {
+ *out_next++ = *src++;
+ } while (out_next != end);
+ return 0;
+}
+
+#endif /* _LINUX_NTFS_LIB_DECOMPRESS_COMMON_H */
diff --git a/fs/ntfs/lib/lib.h b/fs/ntfs/lib/lib.h
new file mode 100644
index 000000000000..a684d600fd3c
--- /dev/null
+++ b/fs/ntfs/lib/lib.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * lib.h - Public declarations for the LZX and XPRESS decompressors.
+ *
+ * Adapted for the linux kernel. These are the low-level decompressor
+ * allocations; WOF (system-compressed) access goes through the
+ * ntfs_codec_ops interface declared in "../ntfs_codec.h".
+ */
+
+#ifndef _LINUX_NTFS_LIB_LIB_H
+#define _LINUX_NTFS_LIB_LIB_H
+
+#include <linux/types.h>
+
+/* globals from xpress_decompress.c */
+struct xpress_decompressor *xpress_allocate_decompressor(void);
+void xpress_free_decompressor(struct xpress_decompressor *d);
+int xpress_decompress(struct xpress_decompressor *d,
+ const void *compressed_data, size_t compressed_size,
+ void *uncompressed_data, size_t uncompressed_size);
+
+/* globals from lzx_decompress.c */
+struct lzx_decompressor *lzx_allocate_decompressor(void);
+void lzx_free_decompressor(struct lzx_decompressor *d);
+int lzx_decompress(struct lzx_decompressor *d, const void *compressed_data,
+ size_t compressed_size, void *uncompressed_data,
+ size_t uncompressed_size);
+
+#endif /* _LINUX_NTFS_LIB_LIB_H */
diff --git a/fs/ntfs/lib/lzx_decompress.c b/fs/ntfs/lib/lzx_decompress.c
new file mode 100644
index 000000000000..be040dce6e23
--- /dev/null
+++ b/fs/ntfs/lib/lzx_decompress.c
@@ -0,0 +1,631 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * lzx_decompress.c - A decompressor for the LZX compression format
+ *
+ * This is a port of the upstream wimlib "lzx_decompress.c" which uses a
+ * subtable-based Huffman decode table format. The window size is fixed at
+ * 32768 bytes, which is the only size used in System-compressed (WOF) files.
+ *
+ * Copyright (C) 2012-2016 Eric Biggers
+ */
+
+#include <linux/array_size.h>
+#include <linux/bits.h>
+
+#include "decompress_common.h"
+#include "lib.h"
+#include "../ntfs_codec.h"
+
+/* Number of literal byte values. */
+#define LZX_NUM_CHARS 256
+
+/* The smallest and largest allowed match lengths. */
+#define LZX_MIN_MATCH_LEN 2
+#define LZX_MAX_MATCH_LEN 257
+
+/* Number of distinct match lengths that can be represented. */
+#define LZX_NUM_LENS (LZX_MAX_MATCH_LEN - LZX_MIN_MATCH_LEN + 1)
+
+/* Number of match lengths for which no length symbol is required. */
+#define LZX_NUM_PRIMARY_LENS 7
+#define LZX_NUM_LEN_HEADERS (LZX_NUM_PRIMARY_LENS + 1)
+
+/* Valid values of the 3-bit block type field. */
+#define LZX_BLOCKTYPE_VERBATIM 1
+#define LZX_BLOCKTYPE_ALIGNED 2
+#define LZX_BLOCKTYPE_UNCOMPRESSED 3
+
+/* LZX window size is fixed at 32768 bytes for System-compressed files. */
+
+/* Number of offset slots for a 32768-byte window. */
+#define LZX_NUM_OFFSET_SLOTS 30
+
+/* Number of symbols in the main code. */
+#define LZX_MAINCODE_NUM_SYMBOLS \
+ (LZX_NUM_CHARS + (LZX_NUM_OFFSET_SLOTS * LZX_NUM_LEN_HEADERS))
+
+/* Number of symbols in the length code. */
+#define LZX_LENCODE_NUM_SYMBOLS (LZX_NUM_LENS - LZX_NUM_PRIMARY_LENS)
+
+/* Number of symbols in the precode. */
+#define LZX_PRECODE_NUM_SYMBOLS 20
+
+/* Number of bits in which each precode codeword length is represented. */
+#define LZX_PRECODE_ELEMENT_SIZE 4
+
+/* Number of low-order bits of each match offset that are entropy-encoded in
+ * aligned offset blocks.
+ */
+#define LZX_NUM_ALIGNED_OFFSET_BITS 3
+
+/* Number of symbols in the aligned offset code. */
+#define LZX_ALIGNEDCODE_NUM_SYMBOLS BIT(LZX_NUM_ALIGNED_OFFSET_BITS)
+
+/* Mask for the match offset bits that are entropy-encoded in aligned offset
+ * blocks.
+ */
+#define LZX_ALIGNED_OFFSET_BITMASK (BIT(LZX_NUM_ALIGNED_OFFSET_BITS) - 1)
+
+/* Number of bits in which each aligned offset codeword length is represented. */
+#define LZX_ALIGNEDCODE_ELEMENT_SIZE 3
+
+/* The first offset slot which requires an aligned offset symbol in aligned
+ * offset blocks.
+ */
+#define LZX_MIN_ALIGNED_OFFSET_SLOT 8
+
+/* Maximum lengths (in bits) of the codewords in each Huffman code. */
+#define LZX_MAX_MAIN_CODEWORD_LEN 16
+#define LZX_MAX_LEN_CODEWORD_LEN 16
+#define LZX_MAX_PRE_CODEWORD_LEN ((1 << LZX_PRECODE_ELEMENT_SIZE) - 1)
+#define LZX_MAX_ALIGNED_CODEWORD_LEN ((1 << LZX_ALIGNEDCODE_ELEMENT_SIZE) - 1)
+
+/* For LZX-compressed blocks in WIM/system-compressed files this value is
+ * always used as the filesize parameter for the E8 call preprocessing.
+ */
+#define LZX_WIM_MAGIC_FILESIZE 12000000
+
+/* Assumed LZX block size when the encoded block size begins with a 0 bit. */
+#define LZX_DEFAULT_BLOCK_SIZE 32768
+
+/* Number of offsets in the recent (or "repeat") offsets queue. */
+#define LZX_NUM_RECENT_OFFSETS 3
+
+/* An offset of n bytes is actually encoded as (n + LZX_OFFSET_ADJUSTMENT). */
+#define LZX_OFFSET_ADJUSTMENT (LZX_NUM_RECENT_OFFSETS - 1)
+
+/* These values are chosen for fast decompression. */
+#define LZX_MAINCODE_TABLEBITS 11
+#define LZX_LENCODE_TABLEBITS 9
+#define LZX_PRECODE_TABLEBITS 6
+#define LZX_ALIGNEDCODE_TABLEBITS 7
+
+#define LZX_READ_LENS_MAX_OVERRUN 50
+
+/* Mapping: offset slot => first match offset that uses that offset slot.
+ * The offset slots for repeat offsets map to "fake" offsets < 1.
+ */
+static const s32 lzx_offset_slot_base[LZX_NUM_OFFSET_SLOTS + 1] = {
+ -2, -1, 0, 1, 2, /* 0 --- 4 */
+ 4, 6, 10, 14, 22, /* 5 --- 9 */
+ 30, 46, 62, 94, 126, /* 10 --- 14 */
+ 190, 254, 382, 510, 766, /* 15 --- 19 */
+ 1022, 1534, 2046, 3070, 4094, /* 20 --- 24 */
+ 6142, 8190, 12286, 16382, 24574, /* 25 --- 29 */
+ 32766, /* extra */
+};
+
+/* Mapping: offset slot => how many extra bits must be read and added to the
+ * corresponding offset slot base to decode the match offset.
+ */
+static const u8 lzx_extra_offset_bits[LZX_NUM_OFFSET_SLOTS] = {
+ 0, 0, 0, 0, 1,
+ 1, 2, 2, 3, 3,
+ 4, 4, 5, 5, 6,
+ 6, 7, 7, 8, 8,
+ 9, 9, 10, 10, 11,
+ 11, 12, 12, 13, 13,
+};
+
+/* Like lzx_extra_offset_bits[], but with the entropy-coded aligned offset
+ * bits already subtracted. Valid only for offset slots that may appear in
+ * aligned offset blocks.
+ */
+static const u8 lzx_extra_offset_bits_minus_aligned[LZX_NUM_OFFSET_SLOTS] = {
+ 0, 0, 0, 0, 1,
+ 1, 2, 2, 0, 0,
+ 1, 1, 2, 2, 3,
+ 3, 4, 4, 5, 5,
+ 6, 6, 7, 7, 8,
+ 8, 9, 9, 10, 10,
+};
+
+/* Reusable heap-allocated memory for LZX decompression. The decode tables and
+ * their corresponding codeword length arrays are grouped in unions so the
+ * memory can be reused across phases, and the per-code working spaces share a
+ * single union since only one is needed at a time.
+ */
+struct lzx_decompressor {
+ DECODE_TABLE(maincode_decode_table, LZX_MAINCODE_NUM_SYMBOLS,
+ LZX_MAINCODE_TABLEBITS, LZX_MAX_MAIN_CODEWORD_LEN);
+ u8 maincode_lens[LZX_MAINCODE_NUM_SYMBOLS + LZX_READ_LENS_MAX_OVERRUN];
+
+ DECODE_TABLE(lencode_decode_table, LZX_LENCODE_NUM_SYMBOLS,
+ LZX_LENCODE_TABLEBITS, LZX_MAX_LEN_CODEWORD_LEN);
+ u8 lencode_lens[LZX_LENCODE_NUM_SYMBOLS + LZX_READ_LENS_MAX_OVERRUN];
+
+ union {
+ DECODE_TABLE(alignedcode_decode_table,
+ LZX_ALIGNEDCODE_NUM_SYMBOLS,
+ LZX_ALIGNEDCODE_TABLEBITS,
+ LZX_MAX_ALIGNED_CODEWORD_LEN);
+ u8 alignedcode_lens[LZX_ALIGNEDCODE_NUM_SYMBOLS];
+ };
+
+ union {
+ DECODE_TABLE(precode_decode_table, LZX_PRECODE_NUM_SYMBOLS,
+ LZX_PRECODE_TABLEBITS, LZX_MAX_PRE_CODEWORD_LEN);
+ u8 precode_lens[LZX_PRECODE_NUM_SYMBOLS];
+ /* extra_offset_bits[] is used as scratch in aligned blocks. */
+ u8 extra_offset_bits[LZX_NUM_OFFSET_SLOTS];
+ };
+
+ union {
+ DECODE_TABLE_WORKING_SPACE(maincode_working_space,
+ LZX_MAINCODE_NUM_SYMBOLS,
+ LZX_MAX_MAIN_CODEWORD_LEN);
+ DECODE_TABLE_WORKING_SPACE(lencode_working_space,
+ LZX_LENCODE_NUM_SYMBOLS,
+ LZX_MAX_LEN_CODEWORD_LEN);
+ DECODE_TABLE_WORKING_SPACE(alignedcode_working_space,
+ LZX_ALIGNEDCODE_NUM_SYMBOLS,
+ LZX_MAX_ALIGNED_CODEWORD_LEN);
+ DECODE_TABLE_WORKING_SPACE(precode_working_space,
+ LZX_PRECODE_NUM_SYMBOLS,
+ LZX_MAX_PRE_CODEWORD_LEN);
+ };
+} __aligned(DECODE_TABLE_ALIGNMENT);
+
+static forceinline unsigned int read_presym(const struct lzx_decompressor *d,
+ struct input_bitstream *is)
+{
+ return read_huffsym(is, d->precode_decode_table, LZX_PRECODE_TABLEBITS,
+ LZX_MAX_PRE_CODEWORD_LEN);
+}
+
+static forceinline unsigned int read_mainsym(const struct lzx_decompressor *d,
+ struct input_bitstream *is)
+{
+ return read_huffsym(is, d->maincode_decode_table,
+ LZX_MAINCODE_TABLEBITS, LZX_MAX_MAIN_CODEWORD_LEN);
+}
+
+static forceinline unsigned int read_lensym(const struct lzx_decompressor *d,
+ struct input_bitstream *is)
+{
+ return read_huffsym(is, d->lencode_decode_table, LZX_LENCODE_TABLEBITS,
+ LZX_MAX_LEN_CODEWORD_LEN);
+}
+
+static forceinline unsigned int
+read_alignedsym(const struct lzx_decompressor *d, struct input_bitstream *is)
+{
+ return read_huffsym(is, d->alignedcode_decode_table,
+ LZX_ALIGNEDCODE_TABLEBITS,
+ LZX_MAX_ALIGNED_CODEWORD_LEN);
+}
+
+/*
+ * Read a precode from the compressed bitstream, then use it to decode
+ * @num_lens codeword length values and write them to @lens.
+ */
+static int lzx_read_codeword_lens(struct lzx_decompressor *d,
+ struct input_bitstream *is, u8 *lens,
+ u32 num_lens)
+{
+ u8 *len_ptr = lens;
+ u8 *lens_end = lens + num_lens;
+ u32 i;
+
+ /* Read the lengths of the precode codewords. These are stored
+ * explicitly.
+ */
+ for (i = 0; i < LZX_PRECODE_NUM_SYMBOLS; i++) {
+ d->precode_lens[i] =
+ bitstream_read_bits(is, LZX_PRECODE_ELEMENT_SIZE);
+ }
+
+ /* Build the decoding table for the precode. */
+ if (make_huffman_decode_table(d->precode_decode_table,
+ LZX_PRECODE_NUM_SYMBOLS,
+ LZX_PRECODE_TABLEBITS,
+ d->precode_lens,
+ LZX_MAX_PRE_CODEWORD_LEN,
+ d->precode_working_space,
+ ARRAY_SIZE(d->precode_decode_table)))
+ return -1;
+
+ /* Decode the codeword lengths. */
+ do {
+ u32 presym;
+ u8 len;
+
+ presym = read_presym(d, is);
+ if (presym < 17) {
+ /* Difference from old length. */
+ len = *len_ptr - presym;
+ if ((s8)len < 0)
+ len += 17;
+ *len_ptr++ = len;
+ } else {
+ /* Special RLE values. */
+ u32 run_len;
+
+ if (presym == 17) {
+ run_len = 4 + bitstream_read_bits(is, 4);
+ len = 0;
+ } else if (presym == 18) {
+ run_len = 20 + bitstream_read_bits(is, 5);
+ len = 0;
+ } else {
+ run_len = 4 + bitstream_read_bits(is, 1);
+ presym = read_presym(d, is);
+ if (unlikely(presym > 17))
+ return -1;
+ len = *len_ptr - presym;
+ if ((s8)len < 0)
+ len += 17;
+ }
+
+ do {
+ *len_ptr++ = len;
+ } while (--run_len);
+ /* The worst case overrun is when presym == 18,
+ * run_len == 20 + 31, and only 1 length was
+ * remaining, so LZX_READ_LENS_MAX_OVERRUN == 50.
+ * Overrun while reading the first half of
+ * maincode_lens can corrupt the previous values in
+ * the second half, but the resulting lengths will
+ * still be in range, and data that generates overruns
+ * is invalid anyway.
+ */
+ }
+ } while (len_ptr < lens_end);
+
+ return 0;
+}
+
+static void undo_translate_target(void *target, s32 input_pos)
+{
+ s32 abs_offset, rel_offset;
+
+ abs_offset = get_unaligned_le32(target);
+ if (abs_offset >= 0) {
+ if (abs_offset < LZX_WIM_MAGIC_FILESIZE) {
+ /* "good translation" */
+ rel_offset = abs_offset - input_pos;
+ put_unaligned_le32(rel_offset, target);
+ }
+ } else {
+ if (abs_offset >= -input_pos) {
+ /* "compensating translation" */
+ rel_offset = abs_offset + LZX_WIM_MAGIC_FILESIZE;
+ put_unaligned_le32(rel_offset, target);
+ }
+ }
+}
+
+/*
+ * Undo the 'E8' preprocessing used in LZX. Before compression, the
+ * uncompressed data was preprocessed by changing the targets of suspected x86
+ * CALL instructions from relative offsets to absolute offsets. After
+ * match/literal decoding, the decompressor must undo the translation.
+ *
+ * E8 preprocessing is disabled in the last 6 bytes of the data, which means
+ * the 5-byte call instruction cannot start in the last 10 bytes. The scalar
+ * implementation below exploits this by replacing the last 6 bytes with 0xE8
+ * trap bytes, eliminating end-of-buffer checks from the inner loop.
+ */
+static void lzx_postprocess(u8 *data, u32 size)
+{
+ u8 *tail;
+ u8 saved_bytes[6];
+ u8 *p;
+
+ if (size <= 10)
+ return;
+
+ tail = &data[size - 6];
+ memcpy(saved_bytes, tail, 6);
+ memset(tail, 0xE8, 6);
+ p = data;
+ for (;;) {
+ while (*p != 0xE8)
+ p++;
+ if (p >= tail)
+ break;
+ undo_translate_target(p + 1, (s32)(p - data));
+ p += 5;
+ }
+ memcpy(tail, saved_bytes, 6);
+}
+
+static int lzx_read_block_header(struct lzx_decompressor *d,
+ struct input_bitstream *is,
+ u32 recent_offsets[], int *block_type_ret,
+ u32 *block_size_ret)
+{
+ int block_type;
+ u32 block_size;
+ u32 i;
+
+ bitstream_ensure_bits(is, 4);
+
+ /* Read the block type. */
+ block_type = bitstream_pop_bits(is, 3);
+
+ /* Read the block size. With the 32768-byte window used in system
+ * compression, block sizes are always encoded in 16 bits.
+ */
+ if (bitstream_pop_bits(is, 1))
+ block_size = LZX_DEFAULT_BLOCK_SIZE;
+ else
+ block_size = bitstream_read_bits(is, 16);
+
+ switch (block_type) {
+ case LZX_BLOCKTYPE_ALIGNED:
+ /* Read the aligned offset codeword lengths. */
+ for (i = 0; i < LZX_ALIGNEDCODE_NUM_SYMBOLS; i++) {
+ d->alignedcode_lens[i] =
+ bitstream_read_bits(is,
+ LZX_ALIGNEDCODE_ELEMENT_SIZE);
+ }
+ /* Fall though, since the rest of the header for aligned offset
+ * blocks is the same as that for verbatim blocks.
+ */
+ fallthrough;
+
+ case LZX_BLOCKTYPE_VERBATIM:
+ /* Read the main codeword lengths, which are divided into two
+ * parts: literal symbols and match headers.
+ */
+ if (lzx_read_codeword_lens(d, is, d->maincode_lens,
+ LZX_NUM_CHARS))
+ return -1;
+ if (lzx_read_codeword_lens(d, is,
+ d->maincode_lens + LZX_NUM_CHARS,
+ LZX_MAINCODE_NUM_SYMBOLS - LZX_NUM_CHARS))
+ return -1;
+
+ /* Read the length codeword lengths. */
+ if (lzx_read_codeword_lens(d, is, d->lencode_lens,
+ LZX_LENCODE_NUM_SYMBOLS))
+ return -1;
+ break;
+
+ case LZX_BLOCKTYPE_UNCOMPRESSED:
+ /* The header of an uncompressed block contains new values for
+ * the recent offsets queue, starting on the next 16-bit
+ * boundary in the bitstream. If the stream is *already*
+ * aligned, the next 16 bits must be discarded.
+ */
+ bitstream_ensure_bits(is, 1);
+ bitstream_align(is);
+ recent_offsets[0] = bitstream_read_u32(is);
+ recent_offsets[1] = bitstream_read_u32(is);
+ recent_offsets[2] = bitstream_read_u32(is);
+
+ /* Offsets of 0 are invalid. */
+ if (recent_offsets[0] == 0 || recent_offsets[1] == 0 ||
+ recent_offsets[2] == 0)
+ return -1;
+ break;
+
+ default:
+ /* Unrecognized block type. */
+ return -1;
+ }
+
+ *block_type_ret = block_type;
+ *block_size_ret = block_size;
+ return 0;
+}
+
+static int lzx_decompress_block(struct lzx_decompressor *d,
+ struct input_bitstream *is, int block_type,
+ u32 block_size, u8 *const out_begin,
+ u8 *out_next, u32 recent_offsets[])
+{
+ u8 *const block_end = out_next + block_size;
+ unsigned int min_aligned_offset_slot;
+ const u8 *extra_offset_bits;
+
+ /* Build the Huffman decode tables. The main and length tables are
+ * always needed; for aligned blocks the aligned offset table is also
+ * needed.
+ */
+ if (make_huffman_decode_table(d->maincode_decode_table,
+ LZX_MAINCODE_NUM_SYMBOLS,
+ LZX_MAINCODE_TABLEBITS, d->maincode_lens,
+ LZX_MAX_MAIN_CODEWORD_LEN,
+ d->maincode_working_space,
+ ARRAY_SIZE(d->maincode_decode_table)))
+ return -1;
+
+ if (make_huffman_decode_table(d->lencode_decode_table,
+ LZX_LENCODE_NUM_SYMBOLS,
+ LZX_LENCODE_TABLEBITS, d->lencode_lens,
+ LZX_MAX_LEN_CODEWORD_LEN,
+ d->lencode_working_space,
+ ARRAY_SIZE(d->lencode_decode_table)))
+ return -1;
+
+ if (block_type == LZX_BLOCKTYPE_ALIGNED) {
+ if (make_huffman_decode_table(d->alignedcode_decode_table,
+ LZX_ALIGNEDCODE_NUM_SYMBOLS,
+ LZX_ALIGNEDCODE_TABLEBITS,
+ d->alignedcode_lens,
+ LZX_MAX_ALIGNED_CODEWORD_LEN,
+ d->alignedcode_working_space,
+ ARRAY_SIZE(d->alignedcode_decode_table)))
+ return -1;
+ min_aligned_offset_slot = LZX_MIN_ALIGNED_OFFSET_SLOT;
+ extra_offset_bits = lzx_extra_offset_bits_minus_aligned;
+ } else {
+ min_aligned_offset_slot = LZX_NUM_OFFSET_SLOTS;
+ extra_offset_bits = lzx_extra_offset_bits;
+ }
+
+ /* Decode the literals and matches. */
+ do {
+ unsigned int mainsym;
+ unsigned int length;
+ u32 offset;
+ unsigned int offset_slot;
+
+ mainsym = read_mainsym(d, is);
+ if (mainsym < LZX_NUM_CHARS) {
+ /* Literal */
+ *out_next++ = mainsym;
+ continue;
+ }
+
+ /* Match */
+
+ /* Decode the length header and offset slot.
+ */
+ STATIC_ASSERT(LZX_NUM_CHARS % LZX_NUM_LEN_HEADERS == 0);
+ length = mainsym % LZX_NUM_LEN_HEADERS;
+ offset_slot = (mainsym - LZX_NUM_CHARS) / LZX_NUM_LEN_HEADERS;
+
+ /* If needed, read a length symbol to decode the full length. */
+ if (length == LZX_NUM_PRIMARY_LENS)
+ length += read_lensym(d, is);
+ length += LZX_MIN_MATCH_LEN;
+
+ if (offset_slot < LZX_NUM_RECENT_OFFSETS) {
+ /* Repeat offset. This isn't a real LRU queue, since
+ * using the R2 offset doesn't bump the R1 offset down
+ * to R2.
+ */
+ offset = recent_offsets[offset_slot];
+ recent_offsets[offset_slot] = recent_offsets[0];
+ } else {
+ /* Explicit offset. */
+ offset = bitstream_read_bits(is,
+ extra_offset_bits[offset_slot]);
+ if (offset_slot >= min_aligned_offset_slot) {
+ offset = (offset << LZX_NUM_ALIGNED_OFFSET_BITS) |
+ read_alignedsym(d, is);
+ }
+ offset += lzx_offset_slot_base[offset_slot];
+
+ /* Update the match offset LRU queue. */
+ STATIC_ASSERT(LZX_NUM_RECENT_OFFSETS == 3);
+ recent_offsets[2] = recent_offsets[1];
+ recent_offsets[1] = recent_offsets[0];
+ }
+ recent_offsets[0] = offset;
+
+ /* Validate the match and copy it to the current position. */
+ if (unlikely(lz_copy(length, offset, out_begin, out_next,
+ block_end, LZX_MIN_MATCH_LEN)))
+ return -1;
+ out_next += length;
+ } while (out_next != block_end);
+
+ return 0;
+}
+
+int lzx_decompress(struct lzx_decompressor *d, const void *compressed_data,
+ size_t compressed_size, void *uncompressed_data,
+ size_t uncompressed_size)
+{
+ u8 *const out_begin = uncompressed_data;
+ u8 *out_next = out_begin;
+ u8 *const out_end = out_begin + uncompressed_size;
+ struct input_bitstream is;
+
+ STATIC_ASSERT(LZX_NUM_RECENT_OFFSETS == 3);
+ u32 recent_offsets[LZX_NUM_RECENT_OFFSETS] = {1, 1, 1};
+ bool may_have_e8_byte = false;
+
+ init_input_bitstream(&is, compressed_data, compressed_size);
+
+ /* Codeword lengths begin as all 0's for delta encoding purposes. */
+ memset(d->maincode_lens, 0, LZX_MAINCODE_NUM_SYMBOLS);
+ memset(d->lencode_lens, 0, LZX_LENCODE_NUM_SYMBOLS);
+
+ /* Decompress blocks until we have all the uncompressed data.
+ */
+ while (out_next != out_end) {
+ int block_type;
+ u32 block_size;
+
+ if (lzx_read_block_header(d, &is, recent_offsets, &block_type,
+ &block_size))
+ return -1;
+
+ if (block_size < 1 || block_size > (u32)(out_end - out_next))
+ return -1;
+
+ if (likely(block_type != LZX_BLOCKTYPE_UNCOMPRESSED)) {
+ /* Compressed block. */
+ if (lzx_decompress_block(d, &is, block_type, block_size,
+ out_begin, out_next,
+ recent_offsets))
+ return -1;
+
+ /* If the first E8 byte was in this block, then it
+ * must have been encoded as a literal (mainsym E8).
+ */
+ if (d->maincode_lens[0xE8])
+ may_have_e8_byte = true;
+ } else {
+ /* Uncompressed block. */
+ if (bitstream_read_bytes(&is, out_next, block_size))
+ return -1;
+ if (block_size & 1)
+ bitstream_read_byte(&is);
+ /* There may have been an E8 byte in the block. */
+ may_have_e8_byte = true;
+ }
+ out_next += block_size;
+ }
+
+ /* Postprocess the data unless it cannot possibly contain E8 bytes. */
+ if (may_have_e8_byte)
+ lzx_postprocess(uncompressed_data, uncompressed_size);
+
+ return 0;
+}
+
+struct lzx_decompressor *lzx_allocate_decompressor(void)
+{
+ return kmalloc_obj(struct lzx_decompressor, GFP_NOFS);
+}
+
+void lzx_free_decompressor(struct lzx_decompressor *d)
+{
+ kfree(d);
+}
+
+static size_t lzx_scratch_size(u32 chunk_size)
+{
+ return sizeof(struct lzx_decompressor);
+}
+
+static int lzx_decompress_chunk(void *scratch, const void *src, size_t src_len,
+ void *dst, size_t dst_len, u32 chunk_size)
+{
+ struct lzx_decompressor *d = scratch;
+
+ return lzx_decompress(d, src, src_len, dst, dst_len);
+}
+
+const struct ntfs_codec_ops ntfs_lzx32k_codec_ops = {
+ .id = NTFS_CODEC_LZX32K,
+ .name = "lzx32k",
+ .scratch_size = lzx_scratch_size,
+ .decompress_chunk = lzx_decompress_chunk,
+};
diff --git a/fs/ntfs/lib/xpress_decompress.c b/fs/ntfs/lib/xpress_decompress.c
new file mode 100644
index 000000000000..cb212543268d
--- /dev/null
+++ b/fs/ntfs/lib/xpress_decompress.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * xpress_decompress.c - A decompressor for the XPRESS compression format
+ * (Huffman variant), which can be used in "System Compressed" (WOF) files.
+ *
+ * This is a port of the upstream wimlib "xpress_decompress.c" which uses a
+ * subtable-based Huffman decode table format. The decode table and the
+ * codeword-length array share a union since the lengths are fully consumed
+ * before the table is written.
+ *
+ * Copyright (C) 2012-2016 Eric Biggers
+ */
+
+#include <linux/array_size.h>
+
+#include "decompress_common.h"
+#include "lib.h"
+#include "../ntfs_codec.h"
+
+#define XPRESS_NUM_CHARS 256
+#define XPRESS_NUM_SYMBOLS 512
+#define XPRESS_MAX_CODEWORD_LEN 15
+#define XPRESS_MIN_MATCH_LEN 3
+
+/* This value is chosen for fast decompression. */
+#define XPRESS_TABLEBITS 11
+
+/* Reusable heap-allocated memory for XPRESS decompression. The decode table
+ * and the codeword-length array alias each other in a union: all lengths are
+ * consumed into the working space before any decode-table entry is written.
+ */
+struct xpress_decompressor {
+ union {
+ DECODE_TABLE(decode_table, XPRESS_NUM_SYMBOLS, XPRESS_TABLEBITS,
+ XPRESS_MAX_CODEWORD_LEN);
+ u8 lens[XPRESS_NUM_SYMBOLS];
+ };
+ DECODE_TABLE_WORKING_SPACE(working_space, XPRESS_NUM_SYMBOLS,
+ XPRESS_MAX_CODEWORD_LEN);
+} __aligned(DECODE_TABLE_ALIGNMENT);
+
+int xpress_decompress(struct xpress_decompressor *d,
+ const void *compressed_data, size_t compressed_size,
+ void *uncompressed_data, size_t uncompressed_size)
+{
+ const u8 *const in_begin = compressed_data;
+ u8 *const out_begin = uncompressed_data;
+ u8 *out_next = out_begin;
+ u8 *const out_end = out_begin + uncompressed_size;
+ struct input_bitstream is;
+ u32 i;
+
+ /* Read the Huffman codeword lengths (512 4-bit values packed into 256
+ * bytes).
+ */
+ if (compressed_size < XPRESS_NUM_SYMBOLS / 2)
+ return -1;
+ for (i = 0; i < XPRESS_NUM_SYMBOLS / 2; i++) {
+ d->lens[2 * i + 0] = in_begin[i] & 0xf;
+ d->lens[2 * i + 1] = in_begin[i] >> 4;
+ }
+
+ /* Build a decoding table for the Huffman code. */
+ if (make_huffman_decode_table(d->decode_table, XPRESS_NUM_SYMBOLS,
+ XPRESS_TABLEBITS, d->lens,
+ XPRESS_MAX_CODEWORD_LEN,
+ d->working_space,
+ ARRAY_SIZE(d->decode_table)))
+ return -1;
+
+ /* Decode the matches and literals. */
+ init_input_bitstream(&is, in_begin + XPRESS_NUM_SYMBOLS / 2,
+ compressed_size - XPRESS_NUM_SYMBOLS / 2);
+
+ while (out_next != out_end) {
+ u32 sym;
+ u32 log2_offset;
+ u32 length;
+ u32 offset;
+
+ sym = read_huffsym(&is, d->decode_table, XPRESS_TABLEBITS,
+ XPRESS_MAX_CODEWORD_LEN);
+ if (sym < XPRESS_NUM_CHARS) {
+ /* Literal */
+ *out_next++ = sym;
+ } else {
+ /* Match */
+ length = sym & 0xf;
+ log2_offset = (sym >> 4) & 0xf;
+
+ bitstream_ensure_bits(&is, 16);
+
+ offset = ((u32)1 << log2_offset) |
+ bitstream_pop_bits(&is, log2_offset);
+
+ if (length == 0xf) {
+ length += bitstream_read_byte(&is);
+ if (length == 0xf + 0xff)
+ length = bitstream_read_u16(&is);
+ }
+ length += XPRESS_MIN_MATCH_LEN;
+
+ if (unlikely(lz_copy(length, offset, out_begin, out_next,
+ out_end, XPRESS_MIN_MATCH_LEN)))
+ return -1;
+
+ out_next += length;
+ }
+ }
+ return 0;
+}
+
+struct xpress_decompressor *xpress_allocate_decompressor(void)
+{
+ return kmalloc_obj(struct xpress_decompressor, GFP_NOFS);
+}
+
+void xpress_free_decompressor(struct xpress_decompressor *d)
+{
+ kfree(d);
+}
+
+static size_t xpress_scratch_size(u32 chunk_size)
+{
+ return sizeof(struct xpress_decompressor);
+}
+
+static int xpress_decompress_chunk(void *scratch, const void *src,
+ size_t src_len, void *dst, size_t dst_len,
+ u32 chunk_size)
+{
+ return xpress_decompress(scratch, src, src_len, dst, dst_len);
+}
+
+const struct ntfs_codec_ops ntfs_xpress4k_codec_ops = {
+ .id = NTFS_CODEC_XPRESS4K,
+ .name = "xpress4k",
+ .scratch_size = xpress_scratch_size,
+ .decompress_chunk = xpress_decompress_chunk,
+};
+
+const struct ntfs_codec_ops ntfs_xpress8k_codec_ops = {
+ .id = NTFS_CODEC_XPRESS8K,
+ .name = "xpress8k",
+ .scratch_size = xpress_scratch_size,
+ .decompress_chunk = xpress_decompress_chunk,
+};
+
+const struct ntfs_codec_ops ntfs_xpress16k_codec_ops = {
+ .id = NTFS_CODEC_XPRESS16K,
+ .name = "xpress16k",
+ .scratch_size = xpress_scratch_size,
+ .decompress_chunk = xpress_decompress_chunk,
+};
diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c
index 3f8d1640f1d5..1404664dacc0 100644
--- a/fs/ntfs/logfile.c
+++ b/fs/ntfs/logfile.c
@@ -132,7 +132,7 @@ static bool ntfs_check_restart_area(struct inode *vi, struct restart_page_header
{
u64 file_size;
struct restart_area *ra;
- u16 ra_ofs, ra_len, ca_ofs;
+ u32 ra_ofs, ra_len, ca_ofs;
u8 fs_bits;
ntfs_debug("Entering.");
@@ -622,8 +622,7 @@ is_empty:
ntfs_debug("Done.");
return true;
err_out:
- if (rstr1_ph)
- kvfree(rstr1_ph);
+ kvfree(rstr1_ph);
return false;
}
@@ -692,7 +691,7 @@ map_vcn:
memset(empty_buf, 0xff, vol->cluster_size);
- ra = kzalloc(sizeof(*ra), GFP_NOFS);
+ ra = kzalloc_obj(*ra, GFP_NOFS);
if (!ra)
goto err;
@@ -710,6 +709,9 @@ map_vcn:
if (unlikely(lcn == LCN_RL_NOT_MAPPED)) {
vcn = rl->vcn;
kvfree(empty_buf);
+ empty_buf = NULL;
+ kfree(ra);
+ ra = NULL;
goto map_vcn;
}
/* If this run is not valid abort with an error. */
@@ -753,7 +755,7 @@ map_vcn:
} while (start < end);
} while ((++rl)->vcn < end_vcn);
up_write(&log_ni->runlist.lock);
- kfree(empty_buf);
+ kvfree(empty_buf);
kfree(ra);
truncate_inode_pages(log_vi->i_mapping, 0);
/* Set the flag so we do not have to do it again on remount. */
diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
index 7d989267a82b..4b7449495375 100644
--- a/fs/ntfs/mft.c
+++ b/fs/ntfs/mft.c
@@ -30,6 +30,8 @@ int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m,
{
struct attr_record *a;
struct super_block *sb = vol->sb;
+ u16 attrs_offset;
+ u32 bytes_in_use;
if (!ntfs_is_file_record(m->magic)) {
ntfs_error(sb, "Record %llu has no FILE magic (0x%x)\n",
@@ -65,7 +67,16 @@ int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m,
goto err_out;
}
- a = (struct attr_record *)((char *)m + le16_to_cpu(m->attrs_offset));
+ attrs_offset = le16_to_cpu(m->attrs_offset);
+ bytes_in_use = le32_to_cpu(m->bytes_in_use);
+
+ if (attrs_offset > bytes_in_use ||
+ bytes_in_use - attrs_offset < sizeof_field(struct attr_record, type)) {
+ ntfs_error(sb, "Record %llu has corrupt attribute offset\n", mft_no);
+ goto err_out;
+ }
+
+ a = (struct attr_record *)((char *)m + attrs_offset);
if ((char *)a < (char *)m || (char *)a > (char *)m + vol->mft_record_size) {
ntfs_error(sb, "Record %llu is corrupt\n", mft_no);
goto err_out;
@@ -202,7 +213,8 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni)
return m;
atomic_dec(&ni->count);
- ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m));
+ if (PTR_ERR(m) != -EINTR && PTR_ERR(m) != -ERESTARTSYS)
+ ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m));
return m;
}
@@ -449,9 +461,9 @@ static void ntfs_bio_end_io(struct bio *bio)
int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
struct mft_record *m)
{
- u8 *kmirr = NULL;
+ u8 *kmirr;
struct folio *folio;
- unsigned int folio_ofs, lcn_folio_off = 0;
+ unsigned int folio_ofs;
int err = 0;
struct bio *bio;
@@ -479,31 +491,30 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
kmirr = kmap_local_folio(folio, 0) + folio_ofs;
/* Copy the mst protected mft record to the mirror. */
memcpy(kmirr, m, vol->mft_record_size);
-
- if (vol->cluster_size_bits > PAGE_SHIFT) {
- lcn_folio_off = folio->index << PAGE_SHIFT;
- lcn_folio_off &= vol->cluster_size_mask;
- }
+ kunmap_local(kmirr);
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) +
+ ((u64)folio->index << PAGE_SHIFT) +
+ folio_ofs);
- if (!bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs)) {
+ if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs))
+ err = submit_bio_wait(bio);
+ else
err = -EIO;
- bio_put(bio);
- goto unlock_folio;
- }
+ bio_put(bio);
- bio->bi_end_io = ntfs_bio_end_io;
- submit_bio(bio);
- /* Current state: all buffers are clean, unlocked, and uptodate. */
+ /*
+ * The in-memory mirror is now valid because we just memcpy()'d the
+ * mst-protected mft record into it. Mark the folio uptodate even on
+ * write error so a subsequent read_mapping_folio() does not refetch
+ * the stale on-disk mirror and overwrite this copy. The error is
+ * propagated to the caller via @err.
+ */
folio_mark_uptodate(folio);
-unlock_folio:
folio_unlock(folio);
- kunmap_local(kmirr);
folio_put(folio);
if (likely(!err)) {
ntfs_debug("Done.");
@@ -566,7 +577,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;
@@ -578,8 +589,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)) {
@@ -588,20 +599,36 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
}
/* Synchronize the mft mirror now if not @sync. */
- if (!sync && ni->mft_no < vol->mftmirr_size)
- ntfs_sync_mft_mirror(vol, ni->mft_no, fixup_m);
+ if (!sync && ni->mft_no < vol->mftmirr_size) {
+ int sub_err = ntfs_sync_mft_mirror(vol, ni->mft_no,
+ fixup_m);
+ if (unlikely(sub_err) && !err)
+ err = sub_err;
+ }
- folio_get(folio);
- bio->bi_private = folio;
- bio->bi_end_io = ntfs_bio_end_io;
- submit_bio(bio);
+ if (sync) {
+ int sub_err = submit_bio_wait(bio);
+
+ bio_put(bio);
+ if (unlikely(sub_err) && !err)
+ err = sub_err;
+ } else {
+ folio_get(folio);
+ bio->bi_private = folio;
+ bio->bi_end_io = ntfs_bio_end_io;
+ submit_bio(bio);
+ }
offset += vol->cluster_size;
i++;
}
/* If @sync, now synchronize the mft mirror. */
- if (sync && ni->mft_no < vol->mftmirr_size)
- ntfs_sync_mft_mirror(vol, ni->mft_no, fixup_m);
+ if (sync && ni->mft_no < vol->mftmirr_size) {
+ int sub_err = ntfs_sync_mft_mirror(vol, ni->mft_no, fixup_m);
+
+ if (unlikely(sub_err) && !err)
+ err = sub_err;
+ }
kunmap_local(kaddr);
if (unlikely(err)) {
/* I/O error during writing. This is really bad! */
@@ -615,12 +642,14 @@ done:
return 0;
put_bio_out:
bio_put(bio);
+unmap_err_out:
+ kunmap_local(kaddr);
err_out:
/*
- * Current state: all buffers are clean, unlocked, and uptodate.
- * The caller should mark the base inode as bad so that no more i/o
- * happens. ->drop_inode() will still be invoked so all extent inodes
- * and other allocated memory will be freed.
+ * The caller should mark the base inode as bad so no more I/O
+ * happens. ->drop_inode() will still be invoked so all extent inodes
+ * and other allocated memory will be freed. ENOMEM is retried by
+ * redirtying the mft record below.
*/
if (err == -ENOMEM) {
ntfs_error(vol->sb,
@@ -713,23 +742,6 @@ static int ntfs_test_inode_wb(struct inode *vi, u64 ino, void *data)
*
* If the mft record is not a FILE record or it is a base mft record, we can
* safely write it and return 'true'.
- *
- * We now know the mft record is an extent mft record. We check if the inode
- * corresponding to its base mft record is in icache. If it is not, we cannot
- * safely determine the state of the extent inode, so we return 'false'.
- *
- * We now have the base inode for the extent mft record. We check if it has an
- * ntfs inode for the extent mft record attached. If not, it is safe to write
- * the extent mft record and we return 'true'.
- *
- * If the extent inode is attached, we check if it is dirty. If so, we return
- * 'false' (letting the standard write_inode path handle it).
- *
- * If it is not dirty, we attempt to lock the extent mft record. If the lock
- * was already taken, it is not safe to write and we return 'false'.
- *
- * If we manage to obtain the lock we have exclusive access to the extent mft
- * record. We set @locked_ni to the now locked ntfs inode and return 'true'.
*/
static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no,
const struct mft_record *m, struct ntfs_inode **locked_ni,
@@ -738,8 +750,7 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no,
struct super_block *sb = vol->sb;
struct inode *mft_vi = vol->mft_ino;
struct inode *vi;
- struct ntfs_inode *ni, *eni, **extent_nis;
- int i;
+ struct ntfs_inode *ni;
struct ntfs_attr na = {0};
ntfs_debug("Entering for inode 0x%llx.", mft_no);
@@ -819,110 +830,134 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no,
mft_no);
return true;
}
- /*
- * This is an extent mft record. Check if the inode corresponding to
- * its base mft record is in icache and obtain a reference to it if it
- * is.
- */
- na.mft_no = MREF_LE(m->base_mft_record);
- na.state = 0;
- ntfs_debug("Mft record 0x%llx is an extent record. Looking for base inode 0x%llx in icache.",
- mft_no, na.mft_no);
- if (!na.mft_no) {
- /* Balance the below iput(). */
- vi = igrab(mft_vi);
- WARN_ON(vi != mft_vi);
- } else {
- vi = find_inode_nowait(sb, mft_no, ntfs_test_inode_wb, &na);
- if (na.state == NI_BeingDeleted || na.state == NI_BeingCreated)
+
+ ntfs_debug("Mft record 0x%llx is an extent record, skip it.",
+ mft_no);
+ return false;
+}
+
+static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk.";
+
+#define FIRST_NORMAL_MFT_RECORD 24
+#define MFT_RECORD_RESERVE 4
+
+/*
+ * Records 12-15 are marked in use by Windows but normally have no name
+ * and no links. Keep them as the last bootstrap option when a volume
+ * mounted without an in-memory tail reserve needs its first $MFT metadata
+ * extent.
+ */
+static bool mft_reserved_is_free(struct ntfs_volume *vol,
+ struct ntfs_inode *mft_ni, s64 mft_no)
+{
+ struct attr_record *a;
+ struct mft_record *m;
+ struct folio *folio;
+ void *mapped;
+ pgoff_t index = NTFS_MFT_NR_TO_PIDX(vol, mft_no);
+ unsigned int ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no);
+ u32 attrs_offset, bytes_in_use;
+ bool available = false, have_std = false;
+ int i;
+
+ for (i = 0; i < mft_ni->nr_extents; i++) {
+ if (mft_ni->ext.extent_ntfs_inos[i] &&
+ mft_ni->ext.extent_ntfs_inos[i]->mft_no == mft_no)
return false;
}
-
- if (!vi)
+ m = kmalloc(vol->mft_record_size, GFP_NOFS);
+ if (!m)
return false;
- ntfs_debug("Base inode 0x%llx is in icache.", na.mft_no);
- /*
- * The base inode is in icache. Check if it has the extent inode
- * corresponding to this extent mft record attached.
- */
- ni = NTFS_I(vi);
- mutex_lock(&ni->extent_lock);
- if (ni->nr_extents <= 0) {
- /*
- * The base inode has no attached extent inodes, write this
- * extent mft record.
- */
- mutex_unlock(&ni->extent_lock);
- *ref_vi = vi;
- ntfs_debug("Base inode 0x%llx has no attached extent inodes, write the extent record.",
- na.mft_no);
- return true;
- }
- /* Iterate over the attached extent inodes. */
- extent_nis = ni->ext.extent_ntfs_inos;
- for (eni = NULL, i = 0; i < ni->nr_extents; ++i) {
- if (mft_no == extent_nis[i]->mft_no) {
- /*
- * Found the extent inode corresponding to this extent
- * mft record.
- */
- eni = extent_nis[i];
+
+ folio = read_mapping_folio(vol->mft_ino->i_mapping, index, NULL);
+ if (IS_ERR(folio))
+ goto free_m;
+
+ folio_lock(folio);
+ mapped = kmap_local_folio(folio, 0);
+ memcpy(m, (u8 *)mapped + ofs, vol->mft_record_size);
+ kunmap_local(mapped);
+ folio_unlock(folio);
+ folio_put(folio);
+ if (post_read_mst_fixup((struct ntfs_record *)m, vol->mft_record_size))
+ goto free_m;
+
+ if (!ntfs_is_mft_record(m->magic) ||
+ !(m->flags & MFT_RECORD_IN_USE) || m->base_mft_record ||
+ m->link_count)
+ goto out;
+
+ attrs_offset = le16_to_cpu(m->attrs_offset);
+ bytes_in_use = le32_to_cpu(m->bytes_in_use);
+ if (attrs_offset > bytes_in_use || bytes_in_use > vol->mft_record_size ||
+ bytes_in_use - attrs_offset < sizeof(a->type))
+ goto out;
+
+ for (a = (struct attr_record *)((u8 *)m + attrs_offset);
+ (u8 *)a + sizeof(a->type) <= (u8 *)m + bytes_in_use;) {
+ u32 len;
+
+ if (a->type == AT_END) {
+ if ((u8 *)a + sizeof(a->type) + sizeof(a->length) >
+ (u8 *)m + bytes_in_use)
+ break;
+ /* Also accept a record emptied by an earlier bootstrap. */
+ available = have_std ||
+ (u8 *)a == (u8 *)m + attrs_offset;
break;
}
- }
- /*
- * If the extent inode was not attached to the base inode, write this
- * extent mft record.
- */
- if (!eni) {
- mutex_unlock(&ni->extent_lock);
- *ref_vi = vi;
- ntfs_debug("Extent inode 0x%llx is not attached to its base inode 0x%llx, write the extent record.",
- mft_no, na.mft_no);
- return true;
- }
- ntfs_debug("Extent inode 0x%llx is attached to its base inode 0x%llx.",
- mft_no, na.mft_no);
- /* Take a reference to the extent ntfs inode. */
- atomic_inc(&eni->count);
- mutex_unlock(&ni->extent_lock);
-
- /* if extent inode is dirty, write_inode will write it */
- if (NInoDirty(eni)) {
- atomic_dec(&eni->count);
- *ref_vi = vi;
- return false;
- }
+ if (a->type == AT_FILE_NAME)
+ break;
+ len = le32_to_cpu(a->length);
+ if (len < offsetof(struct attr_record, data) ||
+ (u8 *)a + len > (u8 *)m + bytes_in_use)
+ break;
+ if (a->type == AT_STANDARD_INFORMATION) {
+ u32 value_len, value_ofs;
- /*
- * Found the extent inode coresponding to this extent mft record.
- * Try to take the mft record lock.
- */
- if (unlikely(!mutex_trylock(&eni->mrec_lock))) {
- atomic_dec(&eni->count);
- *ref_vi = vi;
- ntfs_debug("Extent mft record 0x%llx is already locked, do not write it.",
- mft_no);
- return false;
+ if (have_std || a->non_resident ||
+ len < offsetof(struct attr_record,
+ data.resident.reserved) + 1)
+ break;
+ value_len = le32_to_cpu(a->data.resident.value_length);
+ value_ofs = le16_to_cpu(a->data.resident.value_offset);
+ if (value_ofs > len || value_len > len - value_ofs)
+ break;
+ have_std = true;
+ }
+ a = (struct attr_record *)((u8 *)a + len);
}
- ntfs_debug("Managed to lock extent mft record 0x%llx, write it.",
- mft_no);
- /*
- * The write has to occur while we hold the mft record lock so return
- * the locked extent ntfs inode.
- */
- *locked_ni = eni;
- return true;
+out:
+ kfree(m);
+ return available;
+free_m:
+ kfree(m);
+ return false;
}
-static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk.";
-
-#define RESERVED_MFT_RECORDS 64
+static s64 mft_reserve_end(const u8 *buf, s64 buf_start, s64 buf_end,
+ s64 start, s64 pass_end, s64 initialized_mft_records)
+{
+ s64 end = start + 1;
+ s64 limit = min_t(s64, start + MFT_RECORD_RESERVE, pass_end);
+
+ if (limit > initialized_mft_records)
+ limit = initialized_mft_records;
+ if (limit > buf_end)
+ limit = buf_end;
+ while (end < limit &&
+ !(buf[(end - buf_start) >> 3] &
+ (1 << ((end - buf_start) & 7))))
+ end++;
+ return end;
+}
/*
- * ntfs_mft_bitmap_find_and_alloc_free_rec_nolock - see name
+ * mft_bitmap_alloc_free_rec - find and allocate a free MFT record
* @vol: volume on which to search for a free mft record
* @base_ni: open base inode if allocating an extent mft record or NULL
+ * @max_mft_no: first record which must not be allocated, or -1
+ * @new_reserve_end: if not NULL, end of a free run starting after the result
*
* Search for a free mft record in the mft bitmap attribute on the ntfs volume
* @vol.
@@ -938,10 +973,12 @@ static const char *es = " Leaving inconsistent metadata. Unmount and run chkds
*
* Locking: Caller must hold vol->mftbmp_lock for writing.
*/
-static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vol,
- struct ntfs_inode *base_ni)
+static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol,
+ struct ntfs_inode *base_ni,
+ s64 max_mft_no, s64 *new_reserve_end)
{
s64 pass_end, ll, data_pos, pass_start, ofs, bit;
+ s64 initialized_mft_records;
unsigned long flags;
struct address_space *mftbmp_mapping;
u8 *buf = NULL, *byte;
@@ -958,30 +995,36 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
read_lock_irqsave(&NTFS_I(vol->mft_ino)->size_lock, flags);
pass_end = NTFS_I(vol->mft_ino)->allocated_size >>
vol->mft_record_size_bits;
+ initialized_mft_records = NTFS_I(vol->mft_ino)->initialized_size >>
+ vol->mft_record_size_bits;
read_unlock_irqrestore(&NTFS_I(vol->mft_ino)->size_lock, flags);
read_lock_irqsave(&NTFS_I(vol->mftbmp_ino)->size_lock, flags);
ll = NTFS_I(vol->mftbmp_ino)->initialized_size << 3;
read_unlock_irqrestore(&NTFS_I(vol->mftbmp_ino)->size_lock, flags);
if (pass_end > ll)
pass_end = ll;
- pass = 1;
- if (!base_ni)
- data_pos = vol->mft_data_pos;
- else
- data_pos = base_ni->mft_no + 1;
- if (data_pos < RESERVED_MFT_RECORDS)
- data_pos = RESERVED_MFT_RECORDS;
- if (data_pos >= pass_end) {
- data_pos = RESERVED_MFT_RECORDS;
+ if (max_mft_no >= 0 && pass_end > max_mft_no)
+ pass_end = max_mft_no;
+ if (base_ni && base_ni->mft_no == FILE_MFT) {
+ data_pos = FILE_first_user;
pass = 2;
- /* This happens on a freshly formatted volume. */
if (data_pos >= pass_end)
return -ENOSPC;
- }
-
- if (base_ni && base_ni->mft_no == FILE_MFT) {
- data_pos = 0;
- pass = 2;
+ } else {
+ pass = 1;
+ if (!base_ni)
+ data_pos = vol->mft_data_pos;
+ else
+ data_pos = base_ni->mft_no + 1;
+ if (data_pos < FIRST_NORMAL_MFT_RECORD)
+ data_pos = FIRST_NORMAL_MFT_RECORD;
+ if (data_pos >= pass_end) {
+ data_pos = FIRST_NORMAL_MFT_RECORD;
+ pass = 2;
+ /* This happens on a freshly formatted volume. */
+ if (data_pos >= pass_end)
+ return -ENOSPC;
+ }
}
pass_start = data_pos;
@@ -1016,38 +1059,28 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
size, data_pos, bit);
for (; bit < size && data_pos + bit < pass_end;
bit &= ~7ull, bit += 8) {
- /*
- * If we're extending $MFT and running out of the first
- * mft record (base record) then give up searching since
- * no guarantee that the found record will be accessible.
- */
- if (base_ni && base_ni->mft_no == FILE_MFT && bit > 400) {
- folio_unlock(folio);
- kunmap_local(buf);
- folio_put(folio);
- return -ENOSPC;
- }
-
byte = buf + (bit >> 3);
if (*byte == 0xff)
continue;
- b = ffz((unsigned long)*byte);
- if (b < 8 && b >= (bit & 7)) {
+ b = bit & 7;
+ for (; b < 8; b++) {
+ if (*byte & (1 << b))
+ continue;
ll = data_pos + (bit & ~7ull) + b;
- if (unlikely(ll > (1ll << 32))) {
+ if (ll >= pass_end)
+ break;
+ /* Keep the dynamic tail reserve for $MFT metadata. */
+ if ((!base_ni || base_ni->mft_no != FILE_MFT) &&
+ ll >= vol->mft_record_reserve_pos &&
+ ll < vol->mft_record_reserve_end)
+ continue;
+ if (unlikely(ll >= (1ll << 32))) {
folio_unlock(folio);
kunmap_local(buf);
folio_put(folio);
return -ENOSPC;
}
- *byte |= 1 << b;
- folio_mark_dirty(folio);
- folio_unlock(folio);
- kunmap_local(buf);
- folio_put(folio);
- ntfs_debug("Done. (Found and allocated mft record 0x%llx.)",
- ll);
- return ll;
+ goto found;
}
}
ntfs_debug("After inner for loop: size 0x%x, data_pos 0x%llx, bit 0x%llx",
@@ -1070,7 +1103,8 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
* part of the zone which we omitted earlier.
*/
pass_end = pass_start;
- data_pos = pass_start = RESERVED_MFT_RECORDS;
+ data_pos = FIRST_NORMAL_MFT_RECORD;
+ pass_start = FIRST_NORMAL_MFT_RECORD;
ntfs_debug("pass %i, pass_start 0x%llx, pass_end 0x%llx.",
pass, pass_start, pass_end);
if (data_pos >= pass_end)
@@ -1080,9 +1114,22 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
/* No free mft records in currently initialized mft bitmap. */
ntfs_debug("Done. (No free mft records left in currently initialized mft bitmap.)");
return -ENOSPC;
+found:
+ if (new_reserve_end)
+ *new_reserve_end = mft_reserve_end(buf, data_pos,
+ data_pos + size, ll, pass_end,
+ initialized_mft_records);
+ *byte |= 1 << b;
+ folio_mark_dirty(folio);
+ folio_unlock(folio);
+ kunmap_local(buf);
+ folio_put(folio);
+ ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", ll);
+ return ll;
}
-static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
+static int ntfs_mft_attr_extend(struct ntfs_inode *ni,
+ struct ntfs_inode *locked_ni)
{
int ret = 0;
struct ntfs_inode *base_ni;
@@ -1103,7 +1150,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
}
}
- ret = ntfs_attr_update_mapping_pairs(ni, 0);
+ ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, locked_ni);
if (ret)
pr_err("MP update failed\n");
@@ -1291,7 +1338,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size +
le16_to_cpu(a->data.non_resident.mapping_pairs_offset));
if (unlikely(ret)) {
- ret = ntfs_mft_attr_extend(mftbmp_ni);
+ ret = ntfs_mft_attr_extend(mftbmp_ni, mftbmp_ni);
if (!ret)
goto extended_ok;
if (ret != -EAGAIN)
@@ -1402,7 +1449,9 @@ undo_alloc:
NVolSetErrors(vol);
}
mark_mft_record_dirty(ctx->ntfs_ino);
- } else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) {
+ } else if (status.mp_extended &&
+ ntfs_attr_update_mapping_pairs_locked(mftbmp_ni, 0,
+ mftbmp_ni)) {
ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
NVolSetErrors(vol);
}
@@ -1490,7 +1539,6 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0);
if (likely(!ret)) {
ntfs_debug("Done. (Wrote eight initialized bytes to mft bitmap.");
- ntfs_inc_free_mft_records(vol, 8 * 8);
return 0;
}
ntfs_error(vol->sb, "Failed to write to mft bitmap.");
@@ -1547,8 +1595,9 @@ err_out:
* @vol: volume on which to extend the mft data attribute
*
* Extend the mft data attribute on the ntfs volume @vol by 16 mft records
- * worth of clusters or if not enough space for this by one mft record worth
- * of clusters.
+ * worth of clusters or if not enough space for this by two mft records worth
+ * of clusters. Keeping at least two new records breaks the recursion between
+ * extending $MFT and allocating a record for a new $MFT attribute extent.
*
* Note: Only changes allocated_size, i.e. does not touch initialized_size or
* data_size.
@@ -1602,10 +1651,8 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
}
lcn = rl->lcn + rl->length;
ntfs_debug("Last lcn of mft data attribute is 0x%llx.", lcn);
- /* Minimum allocation is one mft record worth of clusters. */
- min_nr = NTFS_B_TO_CLU(vol, vol->mft_record_size);
- if (!min_nr)
- min_nr = 1;
+ /* Keep room for the allocating record and at least one MFT reserve. */
+ min_nr = DIV_ROUND_UP_ULL((u64)vol->mft_record_size * 2, vol->cluster_size);
/* Want to allocate 16 mft records worth of clusters. */
nr = vol->mft_record_size << 4 >> vol->cluster_size_bits;
if (!nr)
@@ -1729,7 +1776,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size +
le16_to_cpu(a->data.non_resident.mapping_pairs_offset));
if (unlikely(ret)) {
- ret = ntfs_mft_attr_extend(mft_ni);
+ ret = ntfs_mft_attr_extend(mft_ni, NULL);
if (!ret)
goto extended_ok;
if (ret != -EAGAIN)
@@ -2004,6 +2051,7 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
* @ni: [OUT] on success, set to the allocated ntfs inode
* @base_ni: [IN] open base inode if allocating an extent mft record or NULL
* @ni_mrec: [OUT] on successful return this is the mapped mft record
+ * @mft_data_vcn: [IN] lowest VCN of a new $MFT/$DATA extent, or -1
*
* Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol.
*
@@ -2031,30 +2079,23 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
* optimize this we start scanning at the place specified by @base_ni or if
* @base_ni is NULL we start where we last stopped and we perform wrap around
* when we reach the end. Note, we do not try to allocate mft records below
- * number 64 because numbers 0 to 15 are the defined system files anyway and 16
- * to 64 are special in that they are used for storing extension mft records
- * for the $DATA attribute of $MFT. This is required to avoid the possibility
- * of creating a runlist with a circular dependency which once written to disk
- * can never be read in again. Windows will only use records 16 to 24 for
- * normal files if the volume is completely out of space. We never use them
- * which means that when the volume is really out of space we cannot create any
- * more files while Windows can still create up to 8 small files. We can start
- * doing this at some later time, it does not matter much for now.
+ * number 24 because numbers 0 to 15 are the defined system files and records
+ * 16 to 23 are kept for metadata compatibility. Records reserved dynamically
+ * at the initialized MFT tail are skipped by normal allocation and consumed by
+ * $MFT metadata extent allocation.
*
* When scanning the mft bitmap, we only search up to the last allocated mft
- * record. If there are no free records left in the range 64 to number of
+ * record. If there are no free records left in the range 24 to number of
* allocated mft records, then we extend the $MFT/$DATA attribute in order to
* create free mft records. We extend the allocated size of $MFT/$DATA by 16
* records at a time or one cluster, if cluster size is above 16kiB. If there
- * is not sufficient space to do this, we try to extend by a single mft record
- * or one cluster, if cluster size is above the mft record size.
+ * is not sufficient space to do this, we try to extend by two mft records or
+ * one cluster, if a cluster already contains at least two mft records.
*
- * No matter how many mft records we allocate, we initialize only the first
- * allocated mft record, incrementing mft data size and initialized size
- * accordingly, open an struct ntfs_inode for it and return it to the caller, unless
- * there are less than 64 mft records, in which case we allocate and initialize
- * mft records until we reach record 64 which we consider as the first free mft
- * record for use by normal files.
+ * When extending the initialized MFT tail, we also initialize up to four
+ * additional records and reserve them in memory for future $MFT metadata
+ * extents. If there are less than 24 mft records, records are initialized
+ * until record 24, which is the first record used for normal files.
*
* If during any stage we overflow the initialized data in the mft bitmap, we
* extend the initialized size (and data size) by 8 bytes, allocating another
@@ -2090,9 +2131,13 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
*/
int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
struct ntfs_inode **ni, struct ntfs_inode *base_ni,
- struct mft_record **ni_mrec)
+ struct mft_record **ni_mrec, const s64 mft_data_vcn)
{
s64 ll, bit, old_data_initialized, old_data_size;
+ s64 nr_new_mft_records = 0;
+ s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1;
+ s64 candidate_reserve_end = -1;
+ s64 *reserve_endp;
unsigned long flags;
struct folio *folio;
struct ntfs_inode *mft_ni, *mftbmp_ni;
@@ -2103,7 +2148,9 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
unsigned int ofs;
int err;
__le16 seq_no, usn;
- bool record_formatted = false;
+ bool record_formatted = false, from_reserve = false, tail_alloc = false;
+ bool reserve_created = false;
+ bool forced_reserved_record = false;
unsigned int memalloc_flags;
if (base_ni && *ni)
@@ -2112,6 +2159,21 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
/* @mode and @base_ni are mutually exclusive. */
if (mode && base_ni)
return -EINVAL;
+ if (mft_data_vcn >= 0 &&
+ (!base_ni || base_ni->mft_no != FILE_MFT))
+ return -EINVAL;
+ if (mft_data_vcn >= 0) {
+ u64 vbo;
+
+ if ((u64)mft_data_vcn > (U64_MAX >> vol->cluster_size_bits))
+ return -EOVERFLOW;
+ vbo = (u64)mft_data_vcn << vol->cluster_size_bits;
+ /*
+ * The whole extent record must be reachable without this
+ * extent, including when an MFT record spans multiple clusters.
+ */
+ max_mft_no = vbo >> vol->mft_record_size_bits;
+ }
if (base_ni)
ntfs_debug("Entering (allocating an extent mft record for base mft record 0x%llx).",
@@ -2126,10 +2188,39 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
mutex_lock(&mft_ni->mrec_lock);
mftbmp_ni = NTFS_I(vol->mftbmp_ino);
search_free_rec:
+ from_reserve = false;
+ reserve_created = false;
+ candidate_reserve_end = -1;
if (!base_ni || base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
- bit = ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(vol, base_ni);
+ if (base_ni && base_ni->mft_no == FILE_MFT &&
+ vol->mft_record_reserve_pos < vol->mft_record_reserve_end &&
+ (max_mft_no < 0 || vol->mft_record_reserve_pos < max_mft_no)) {
+ bit = vol->mft_record_reserve_pos;
+ err = ntfs_bitmap_set_bit(vol->mftbmp_ino, bit);
+ if (unlikely(err)) {
+ ntfs_error(vol->sb,
+ "Failed to allocate reserved MFT record 0x%llx.",
+ bit);
+ goto err_out;
+ }
+ vol->mft_record_reserve_pos++;
+ from_reserve = true;
+ ntfs_debug("Allocated MFT metadata record 0x%llx from tail reserve.",
+ bit);
+ goto have_alloc_rec;
+ }
+ reserve_endp = vol->mft_record_reserve_pos >=
+ vol->mft_record_reserve_end ? &candidate_reserve_end : NULL;
+ bit = mft_bitmap_alloc_free_rec(vol, base_ni, max_mft_no, reserve_endp);
if (bit >= 0) {
+ if (candidate_reserve_end > bit + 1) {
+ vol->mft_record_reserve_pos = bit + 1;
+ vol->mft_record_reserve_end = candidate_reserve_end;
+ reserve_created = true;
+ ntfs_debug("Reserved free MFT records [0x%llx, 0x%llx) for metadata.",
+ bit + 1, candidate_reserve_end);
+ }
ntfs_debug("Found and allocated free record (#1), bit 0x%llx.",
(long long)bit);
goto have_alloc_rec;
@@ -2144,6 +2235,24 @@ search_free_rec:
}
if (base_ni && base_ni->mft_no == FILE_MFT) {
+ static const u8 bootstrap_records[] = {
+ FILE_reserved15, FILE_reserved12, FILE_reserved13,
+ FILE_reserved14,
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bootstrap_records); i++) {
+ if (max_mft_no >= 0 && bootstrap_records[i] >= max_mft_no)
+ continue;
+ if (!mft_reserved_is_free(vol, mft_ni,
+ bootstrap_records[i]))
+ continue;
+ bit = bootstrap_records[i];
+ forced_reserved_record = true;
+ ntfs_debug("Using reserved MFT record %lld to bootstrap metadata extension.",
+ bit);
+ goto have_alloc_rec;
+ }
memalloc_nofs_restore(memalloc_flags);
return bit;
}
@@ -2163,10 +2272,10 @@ search_free_rec:
old_data_initialized = mftbmp_ni->initialized_size;
read_unlock_irqrestore(&mftbmp_ni->size_lock, flags);
if (old_data_initialized << 3 > ll &&
- old_data_initialized > RESERVED_MFT_RECORDS / 8) {
+ old_data_initialized << 3 > FIRST_NORMAL_MFT_RECORD) {
bit = ll;
- if (bit < RESERVED_MFT_RECORDS)
- bit = RESERVED_MFT_RECORDS;
+ if (bit < FIRST_NORMAL_MFT_RECORD)
+ bit = FIRST_NORMAL_MFT_RECORD;
if (unlikely(bit >= (1ll << 32)))
goto max_err_out;
ntfs_debug("Found free record (#2), bit 0x%llx.",
@@ -2252,6 +2361,11 @@ have_alloc_rec:
read_lock_irqsave(&mft_ni->size_lock, flags);
old_data_initialized = mft_ni->initialized_size;
read_unlock_irqrestore(&mft_ni->size_lock, flags);
+ tail_alloc = (!base_ni || base_ni->mft_no != FILE_MFT) &&
+ bit >= (old_data_initialized >> vol->mft_record_size_bits) &&
+ vol->mft_record_reserve_pos >= vol->mft_record_reserve_end;
+ if (tail_alloc)
+ ll = (bit + 2) << vol->mft_record_size_bits;
if (ll <= old_data_initialized) {
ntfs_debug("Allocated mft record already initialized.");
goto mft_rec_already_initialized;
@@ -2284,6 +2398,29 @@ have_alloc_rec:
mft_ni->initialized_size);
}
read_unlock_irqrestore(&mft_ni->size_lock, flags);
+ if (tail_alloc) {
+ s64 bitmap_records;
+
+ read_lock_irqsave(&mft_ni->size_lock, flags);
+ reserve_end = mft_ni->allocated_size >>
+ vol->mft_record_size_bits;
+ read_unlock_irqrestore(&mft_ni->size_lock, flags);
+ read_lock_irqsave(&mftbmp_ni->size_lock, flags);
+ bitmap_records = mftbmp_ni->initialized_size << 3;
+ read_unlock_irqrestore(&mftbmp_ni->size_lock, flags);
+ if (reserve_end > bitmap_records)
+ reserve_end = bitmap_records;
+ if (reserve_end > bit + 1 + MFT_RECORD_RESERVE)
+ reserve_end = bit + 1 + MFT_RECORD_RESERVE;
+ reserve_start = bit + 1;
+ if (reserve_end > reserve_start) {
+ ll = reserve_end << vol->mft_record_size_bits;
+ } else {
+ reserve_start = -1;
+ reserve_end = -1;
+ ll = (bit + 1) << vol->mft_record_size_bits;
+ }
+ }
} else if (ll > mft_ni->allocated_size) {
err = -ENOSPC;
goto undo_mftbmp_alloc_nolock;
@@ -2352,14 +2489,25 @@ have_alloc_rec:
mark_mft_record_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(mft_ni);
+ if (reserve_start >= 0 && reserve_end > reserve_start) {
+ vol->mft_record_reserve_pos = reserve_start;
+ vol->mft_record_reserve_end = reserve_end;
+ ntfs_debug("Reserved MFT records [0x%llx, 0x%llx) for metadata.",
+ reserve_start, reserve_end);
+ }
read_lock_irqsave(&mft_ni->size_lock, flags);
ntfs_debug("Status of mft data after mft record initialization: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.",
mft_ni->allocated_size, i_size_read(vol->mft_ino),
mft_ni->initialized_size);
WARN_ON(i_size_read(vol->mft_ino) > mft_ni->allocated_size);
WARN_ON(mft_ni->initialized_size > i_size_read(vol->mft_ino));
+ nr_new_mft_records = (i_size_read(vol->mft_ino) - old_data_size) >>
+ vol->mft_record_size_bits;
read_unlock_irqrestore(&mft_ni->size_lock, flags);
mft_rec_already_initialized:
+ /* Account for newly visible MFT records before dropping the lock. */
+ if (nr_new_mft_records > 0)
+ ntfs_inc_free_mft_records(vol, nr_new_mft_records);
/*
* We can finally drop the mft bitmap lock as the mft data attribute
* has been fully updated. The only disparity left is that the
@@ -2391,8 +2539,8 @@ mft_rec_already_initialized:
/* If we just formatted the mft record no need to do it again. */
if (!record_formatted) {
/* Sanity check that the mft record is really not in use. */
- if (ntfs_is_file_record(m->magic) &&
- (m->flags & MFT_RECORD_IN_USE)) {
+ if (!forced_reserved_record && ntfs_is_file_record(m->magic) &&
+ (m->flags & MFT_RECORD_IN_USE)) {
ntfs_warning(vol->sb,
"Mft record 0x%llx was marked free in mft bitmap but is marked used itself. Unmount and run chkdsk.",
bit);
@@ -2411,7 +2559,17 @@ mft_rec_already_initialized:
* wrong with the previous mft record.
*/
seq_no = m->sequence_number;
- usn = *(__le16 *)((u8 *)m + le16_to_cpu(m->usa_ofs));
+ /*
+ * The mft record still holds unvalidated, MST-protected on-disk
+ * bytes, so m->usa_ofs is untrusted here. Only preserve the old
+ * update sequence number if that offset is in bounds; otherwise
+ * leave usn zero so it is not restored below.
+ */
+ if (!(le16_to_cpu(m->usa_ofs) & 1) &&
+ le16_to_cpu(m->usa_ofs) + sizeof(usn) <= vol->mft_record_size)
+ usn = *(__le16 *)((u8 *)m + le16_to_cpu(m->usa_ofs));
+ else
+ usn = 0;
err = ntfs_mft_record_layout(vol, bit, m);
if (unlikely(err)) {
ntfs_error(vol->sb, "Failed to layout allocated mft record 0x%llx.",
@@ -2455,9 +2613,13 @@ mft_rec_already_initialized:
ntfs_error(vol->sb, "Failed to map allocated extent mft record 0x%llx.",
bit);
err = PTR_ERR(m_tmp);
- /* Set the mft record itself not in use. */
- m->flags &= cpu_to_le16(
- ~le16_to_cpu(MFT_RECORD_IN_USE));
+ if (forced_reserved_record) {
+ m->base_mft_record = 0;
+ m->flags |= MFT_RECORD_IN_USE;
+ } else {
+ /* Set the mft record itself not in use. */
+ m->flags &= cpu_to_le16(~le16_to_cpu(MFT_RECORD_IN_USE));
+ }
/* Make sure the mft record is written out to disk. */
ntfs_mft_mark_dirty(folio);
folio_unlock(folio);
@@ -2498,7 +2660,7 @@ mft_rec_already_initialized:
* record.
*/
- (*ni)->mrec = kmalloc(vol->mft_record_size, GFP_NOFS);
+ (*ni)->mrec = kmemdup(m, vol->mft_record_size, GFP_NOFS);
if (!(*ni)->mrec) {
folio_unlock(folio);
kunmap_local(m);
@@ -2507,7 +2669,6 @@ mft_rec_already_initialized:
goto undo_mftbmp_alloc;
}
- memcpy((*ni)->mrec, m, vol->mft_record_size);
post_read_mst_fixup((struct ntfs_record *)(*ni)->mrec, vol->mft_record_size);
ntfs_mft_mark_dirty(folio);
folio_unlock(folio);
@@ -2530,7 +2691,8 @@ mft_rec_already_initialized:
(*ni)->mft_no = bit;
if (ni_mrec)
*ni_mrec = (*ni)->mrec;
- ntfs_dec_free_mft_records(vol, 1);
+ if (!forced_reserved_record)
+ ntfs_dec_free_mft_records(vol, 1);
return 0;
undo_data_init:
write_lock_irqsave(&mft_ni->size_lock, flags);
@@ -2542,10 +2704,13 @@ undo_mftbmp_alloc:
if (!base_ni || base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
undo_mftbmp_alloc_nolock:
- if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
+ if (!forced_reserved_record && ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es);
NVolSetErrors(vol);
}
+ if ((from_reserve || reserve_created) &&
+ vol->mft_record_reserve_pos == bit + 1)
+ vol->mft_record_reserve_pos = bit;
if (!base_ni || base_ni->mft_no != FILE_MFT)
up_write(&vol->mftbmp_lock);
err_out:
@@ -2581,9 +2746,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
int err;
u16 seq_no;
__le16 old_seq_no;
+ __le64 old_base_mft_record;
struct mft_record *ni_mrec;
unsigned int memalloc_flags;
struct ntfs_inode *base_ni;
+ bool keep_reserved;
if (!vol || !ni)
return -EINVAL;
@@ -2596,9 +2763,23 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
/* Cache the mft reference for later. */
mft_no = ni->mft_no;
-
- /* Mark the mft record as not in use. */
- ni_mrec->flags &= ~MFT_RECORD_IN_USE;
+ if (likely(ni->nr_extents >= 0))
+ base_ni = ni;
+ else
+ base_ni = ni->ext.base_ntfs_ino;
+ keep_reserved = mft_no >= FILE_reserved12 &&
+ mft_no <= FILE_reserved15 &&
+ base_ni->mft_no == FILE_MFT;
+
+ old_base_mft_record = ni_mrec->base_mft_record;
+ if (keep_reserved) {
+ /* Restore the special, unnamed form used by reserved records. */
+ ni_mrec->base_mft_record = 0;
+ ni_mrec->flags |= MFT_RECORD_IN_USE;
+ } else {
+ /* Mark the mft record as not in use. */
+ ni_mrec->flags &= ~MFT_RECORD_IN_USE;
+ }
/* Increment the sequence number, skipping zero, if it is not zero. */
old_seq_no = ni_mrec->sequence_number;
@@ -2627,24 +2808,28 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
if (err)
goto sync_rollback;
- if (likely(ni->nr_extents >= 0))
- base_ni = ni;
- else
- base_ni = ni->ext.base_ntfs_ino;
+ if (keep_reserved) {
+ unmap_mft_record(ni);
+ return 0;
+ }
/* Clear the bit in the $MFT/$BITMAP corresponding to this record. */
memalloc_flags = memalloc_nofs_save();
if (base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no);
+ if (!err)
+ ntfs_inc_free_mft_records(vol, 1);
+ if (!err && base_ni->mft_no == FILE_MFT &&
+ mft_no + 1 == vol->mft_record_reserve_pos &&
+ mft_no < vol->mft_record_reserve_end)
+ vol->mft_record_reserve_pos = mft_no;
if (base_ni->mft_no != FILE_MFT)
up_write(&vol->mftbmp_lock);
memalloc_nofs_restore(memalloc_flags);
if (err)
goto bitmap_rollback;
-
unmap_mft_record(ni);
- ntfs_inc_free_mft_records(vol, 1);
return 0;
/* Rollback what we did... */
@@ -2662,6 +2847,7 @@ sync_rollback:
"Eeek! Rollback failed in %s. Leaving inconsistent metadata!\n", __func__);
ni_mrec->flags |= MFT_RECORD_IN_USE;
ni_mrec->sequence_number = old_seq_no;
+ ni_mrec->base_mft_record = old_base_mft_record;
NInoSetDirty(ni);
write_mft_record(ni, ni_mrec, 0);
unmap_mft_record(ni);
@@ -2702,11 +2888,13 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
struct ntfs_inode *ni = NTFS_I(vi);
struct ntfs_volume *vol = ni->vol;
u8 *kaddr;
- struct ntfs_inode **locked_nis __free(kfree) = kmalloc_array(PAGE_SIZE / NTFS_BLOCK_SIZE,
- sizeof(struct ntfs_inode *), GFP_NOFS);
+ struct ntfs_inode **locked_nis __free(kfree) = kmalloc_objs(struct ntfs_inode *,
+ PAGE_SIZE / NTFS_BLOCK_SIZE,
+ GFP_NOFS);
int nr_locked_nis = 0, err = 0, mft_ofs, prev_mft_ofs;
- struct inode **ref_inos __free(kfree) = kmalloc_array(PAGE_SIZE / NTFS_BLOCK_SIZE,
- sizeof(struct inode *), GFP_NOFS);
+ struct inode **ref_inos __free(kfree) = kmalloc_objs(struct inode *,
+ PAGE_SIZE / NTFS_BLOCK_SIZE,
+ GFP_NOFS);
int nr_ref_inos = 0;
struct bio *bio = NULL;
u64 mft_no;
@@ -2715,14 +2903,16 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
s64 vcn = ntfs_pidx_to_cluster(vol, folio->index);
s64 end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size);
unsigned int folio_sz;
- struct runlist_element *rl = NULL;
loff_t i_size = i_size_read(vi);
ntfs_debug("Entering for inode 0x%llx, attribute type 0x%x, folio index 0x%lx.",
ni->mft_no, ni->type, folio->index);
- if (!locked_nis || !ref_inos)
+ if (!locked_nis || !ref_inos) {
+ folio_redirty_for_writepage(wbc, folio);
+ folio_unlock(folio);
return -ENOMEM;
+ }
/* We have to zero every time due to mmap-at-end-of-file. */
if (folio->index >= (i_size >> folio_shift(folio)))
@@ -2757,19 +2947,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
&tni, &ref_inos[nr_ref_inos])) {
unsigned int mft_record_off = 0;
s64 vcn_off = vcn;
-
- /*
- * Skip $MFT extent mft records and let them being written
- * by writeback to avioid deadlocks. the $MFT runlist
- * lock must be taken before $MFT extent mrec_lock is taken.
- */
- if (tni && tni->nr_extents < 0 &&
- tni->ext.base_ntfs_ino == NTFS_I(vol->mft_ino)) {
- mutex_unlock(&tni->mrec_lock);
- atomic_dec(&tni->count);
- iput(vol->mft_ino);
- continue;
- }
+ s64 rl_len = 0;
/*
* The record should be written. If a locked ntfs
@@ -2789,8 +2967,12 @@ flush_bio:
}
if (vol->cluster_size < folio_size(folio)) {
+ struct runlist_element *rl;
+
down_write(&ni->runlist.lock);
rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn);
+ if (!IS_ERR(rl))
+ rl_len = rl->length - (vcn_off - rl->vcn);
up_write(&ni->runlist.lock);
if (IS_ERR(rl) || lcn < 0) {
err = -EIO;
@@ -2815,13 +2997,13 @@ flush_bio:
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 &&
(mft_record_off ||
- (rl && rl->length - (vcn_off - rl->vcn) == 1) ||
+ rl_len == 1 ||
mft_ofs + NTFS_BLOCK_SIZE >= PAGE_SIZE))
folio_sz = NTFS_BLOCK_SIZE;
else
@@ -2840,9 +3022,13 @@ flush_bio:
}
prev_mft_ofs = mft_ofs;
- if (mft_no < vol->mftmirr_size)
- ntfs_sync_mft_mirror(vol, mft_no,
+ if (mft_no < vol->mftmirr_size) {
+ int sub_err = ntfs_sync_mft_mirror(vol, mft_no,
(struct mft_record *)(kaddr + mft_ofs));
+
+ if (unlikely(sub_err) && !err)
+ err = sub_err;
+ }
} else if (ref_inos[nr_ref_inos])
nr_ref_inos++;
}
diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h
index 75a51a98d0f6..ed5c1d595c0d 100644
--- a/fs/ntfs/mft.h
+++ b/fs/ntfs/mft.h
@@ -78,7 +78,7 @@ static inline int write_mft_record(struct ntfs_inode *ni, struct mft_record *m,
int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
struct ntfs_inode **ni, struct ntfs_inode *base_ni,
- struct mft_record **ni_mrec);
+ struct mft_record **ni_mrec, const s64 mft_data_vcn);
int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni);
int ntfs_mft_records_write(const struct ntfs_volume *vol, const u64 mref,
const s64 count, struct mft_record *b);
diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index 96c450e62efc..fdf52fac4329 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -61,12 +61,12 @@ static int ntfs_check_bad_windows_name(struct ntfs_volume *vol,
const __le16 *wc,
unsigned int wc_len)
{
- if (ntfs_check_bad_char(wc, wc_len))
- return -EINVAL;
-
if (!NVolCheckWindowsNames(vol))
return 0;
+ if (ntfs_check_bad_char(wc, wc_len))
+ return -EINVAL;
+
/* Check for trailing space or dot. */
if (wc_len > 0 &&
(wc[wc_len - 1] == cpu_to_le16(' ') ||
@@ -230,9 +230,8 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent,
if (MREF_ERR(mref) == -ENOENT) {
ntfs_debug("Entry was not found, adding negative dentry.");
/* The dcache will handle negative entries. */
- d_add(dent, NULL);
ntfs_debug("Done.");
- return NULL;
+ return d_splice_alias(NULL, dent);
}
ntfs_error(vol->sb, "ntfs_lookup_ino_by_name() failed with error code %i.",
-MREF_ERR(mref));
@@ -344,9 +343,9 @@ static int ntfs_sd_add_everyone(struct ntfs_inode *ni)
sd_len = sizeof(struct security_descriptor_relative) + 2 *
(sizeof(struct ntfs_sid) + 8) + sizeof(struct ntfs_acl) +
sizeof(struct ntfs_ace) + 4;
- sd = kmalloc(sd_len, GFP_NOFS);
+ sd = kzalloc(sd_len, GFP_NOFS);
if (!sd)
- return -1;
+ return -ENOMEM;
sd->revision = 1;
sd->control = SE_DACL_PRESENT | SE_SELF_RELATIVE;
@@ -394,7 +393,7 @@ static int ntfs_sd_add_everyone(struct ntfs_inode *ni)
static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *dir,
__le16 *name, u8 name_len, mode_t mode, dev_t dev,
- __le16 *target, int target_len)
+ const char *target, int target_len)
{
struct ntfs_inode *dir_ni = NTFS_I(dir);
struct ntfs_volume *vol = dir_ni->vol;
@@ -425,8 +424,6 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
* directories, also setup the index values to the defaults.
*/
if (S_ISDIR(mode)) {
- mode &= ~vol->dmask;
-
NInoSetMstProtected(ni);
ni->itype.index.block_size = 4096;
ni->itype.index.block_size_bits = ntfs_ffs(4096) - 1;
@@ -440,8 +437,6 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
ni->itype.index.vcn_size_bits =
vol->sector_size_bits;
}
- } else {
- mode &= ~vol->fmask;
}
if (IS_RDONLY(vi))
@@ -485,7 +480,7 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
mark_inode_dirty(dir);
err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL,
- &ni_mrec);
+ &ni_mrec, -1);
if (err) {
iput(vi);
return ERR_PTR(err);
@@ -608,7 +603,10 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
goto err_out;
if (S_ISLNK(mode)) {
- err = ntfs_reparse_set_wsl_symlink(ni, target, target_len);
+ if (NVolSymlinkNative(vol))
+ err = ntfs_reparse_set_native_symlink(ni, target, target_len);
+ else
+ err = ntfs_reparse_set_wsl_symlink(ni, target, target_len);
if (!err)
rollback_reparse = true;
} else if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISSOCK(mode) ||
@@ -683,7 +681,8 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
mutex_unlock(&dir_ni->mrec_lock);
mutex_unlock(&ni->mrec_lock);
- ni->flags = fn->file_attributes;
+ ni->flags = fn->file_attributes |
+ (ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN);
/* Set the sequence number. */
vi->i_generation = ni->seq_no;
set_nlink(vi, 1);
@@ -734,7 +733,7 @@ err_out:
}
static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir,
- struct dentry *dentry, umode_t mode, bool excl)
+ struct dentry *dentry, umode_t mode)
{
struct ntfs_volume *vol = NTFS_SB(dir->i_sb);
struct ntfs_inode *ni;
@@ -1080,7 +1079,7 @@ static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
- ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFDIR | mode, 0, NULL, 0);
+ ni = __ntfs_create(idmap, dir, uname, uname_len, mode, 0, NULL, 0);
kmem_cache_free(ntfs_name_cache, uname);
if (IS_ERR(ni)) {
err = PTR_ERR(ni);
@@ -1264,6 +1263,7 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct ntfs_volume *vol = NTFS_SB(sb);
struct ntfs_inode *old_ni, *new_ni = NULL;
struct ntfs_inode *old_dir_ni = NTFS_I(old_dir), *new_dir_ni = NTFS_I(new_dir);
+ bool new_dir_first = false;
if (NVolShutdown(old_dir_ni->vol))
return -EIO;
@@ -1299,36 +1299,39 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
old_inode = old_dentry->d_inode;
new_inode = new_dentry->d_inode;
old_ni = NTFS_I(old_inode);
+ if (new_inode)
+ new_ni = NTFS_I(new_inode);
+ if (old_dir != new_dir)
+ new_dir_first = is_subdir(new_dentry->d_parent,
+ old_dentry->d_parent);
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
- mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+ if (new_ni)
+ mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);
+
+ if (old_dir == new_dir) {
+ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+ } else if (new_dir_first) {
+ mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
+ } else {
+ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+ mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
+ }
- if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni)) {
+ if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni) ||
+ (new_ni && NInoBeingDeleted(new_ni)) ||
+ (old_dir != new_dir && NInoBeingDeleted(new_dir_ni))) {
err = -ENOENT;
- goto unlock_old;
+ goto err_out;
}
is_dir = S_ISDIR(old_inode->i_mode);
if (new_inode) {
- new_ni = NTFS_I(new_inode);
- mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);
- if (old_dir != new_dir) {
- mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
- if (NInoBeingDeleted(new_dir_ni)) {
- err = -ENOENT;
- goto err_out;
- }
- }
-
- if (NInoBeingDeleted(new_ni)) {
- err = -ENOENT;
- goto err_out;
- }
-
if (is_dir) {
struct mft_record *ni_mrec;
@@ -1346,14 +1349,6 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
err = ntfs_delete(new_ni, new_dir_ni, uname_new, new_name_len, false);
if (err)
goto err_out;
- } else {
- if (old_dir != new_dir) {
- mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
- if (NInoBeingDeleted(new_dir_ni)) {
- err = -ENOENT;
- goto err_out;
- }
- }
}
err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len);
@@ -1384,13 +1379,17 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
inode_inc_iversion(new_dir);
err_out:
- if (old_dir != new_dir)
+ if (old_dir == new_dir) {
+ mutex_unlock(&old_dir_ni->mrec_lock);
+ } else if (new_dir_first) {
+ mutex_unlock(&old_dir_ni->mrec_lock);
mutex_unlock(&new_dir_ni->mrec_lock);
- if (new_inode)
+ } else {
+ mutex_unlock(&new_dir_ni->mrec_lock);
+ mutex_unlock(&old_dir_ni->mrec_lock);
+ }
+ if (new_ni)
mutex_unlock(&new_ni->mrec_lock);
-
-unlock_old:
- mutex_unlock(&old_dir_ni->mrec_lock);
mutex_unlock(&old_ni->mrec_lock);
if (uname_new)
kmem_cache_free(ntfs_name_cache, uname_new);
@@ -1409,9 +1408,7 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
int err = 0;
struct ntfs_inode *ni;
__le16 *usrc;
- __le16 *utarget;
int usrc_len;
- int utarget_len;
int symlen = strlen(symname);
if (NVolShutdown(vol))
@@ -1432,23 +1429,12 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
goto out;
}
- utarget_len = ntfs_nlstoucs(vol, symname, symlen, &utarget,
- PATH_MAX);
- if (utarget_len < 0) {
- if (utarget_len != -ENAMETOOLONG)
- ntfs_error(sb, "Failed to convert target name to Unicode.");
- err = -ENOMEM;
- kmem_cache_free(ntfs_name_cache, usrc);
- goto out;
- }
-
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
ni = __ntfs_create(idmap, dir, usrc, usrc_len, S_IFLNK | 0777, 0,
- utarget, utarget_len);
+ symname, symlen);
kmem_cache_free(ntfs_name_cache, usrc);
- kvfree(utarget);
if (IS_ERR(ni)) {
err = PTR_ERR(ni);
goto out;
@@ -1532,8 +1518,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
if (uname_len < 0) {
if (uname_len != -ENAMETOOLONG)
ntfs_error(sb, "Failed to convert name to unicode.");
- err = -ENOMEM;
- goto out;
+ return -ENOMEM;
}
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
@@ -1563,7 +1548,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
mutex_unlock(&ni->mrec_lock);
out:
- kfree(uname);
+ kmem_cache_free(ntfs_name_cache, uname);
return err;
}
diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h
index 45064dbcc2e4..45f77848a9cf 100644
--- a/fs/ntfs/ntfs.h
+++ b/fs/ntfs/ntfs.h
@@ -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. */
@@ -202,6 +200,10 @@ static inline struct ntfs_volume *NTFS_SB(struct super_block *sb)
/* From fs/ntfs/compress.c */
int ntfs_read_compressed_block(struct folio *folio);
+#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
+int ntfs_read_wof_compressed_block(struct folio *folio);
+void ntfs_wof_free_workspaces(void);
+#endif
int allocate_compression_buffers(void);
void free_compression_buffers(void);
int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
diff --git a/fs/ntfs/ntfs_codec.h b/fs/ntfs/ntfs_codec.h
new file mode 100644
index 000000000000..6030055fb0c1
--- /dev/null
+++ b/fs/ntfs/ntfs_codec.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Transparent compression codec interface.
+ *
+ * Copyright (c) 2026 LG Electronics Co., Ltd.
+ */
+
+#ifndef _NTFS_CODEC_H
+#define _NTFS_CODEC_H
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+
+struct compress_context;
+
+enum ntfs_codec_id {
+ NTFS_CODEC_LZNT1,
+#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
+ NTFS_CODEC_XPRESS4K,
+ NTFS_CODEC_XPRESS8K,
+ NTFS_CODEC_XPRESS16K,
+ NTFS_CODEC_LZX32K,
+#endif
+};
+
+struct ntfs_codec_ops {
+ enum ntfs_codec_id id;
+ const char *name;
+ size_t (*scratch_size)(u32 chunk_size);
+ int (*decompress_chunk)(void *scratch,
+ const void *src, size_t src_len,
+ void *dst, size_t dst_len,
+ u32 chunk_size);
+ int (*decompress_pages)(struct page *dest_pages[],
+ int completed_pages[],
+ int *dest_index, int *dest_ofs,
+ int dest_max_index, int dest_max_ofs,
+ int xpage, char *xpage_done,
+ u8 *cb_start, u32 cb_size,
+ loff_t i_size, s64 initialized_size);
+ int (*compress_subblock)(struct compress_context *pctx,
+ const char *inbuf, int bufsize, char *outbuf);
+};
+
+extern const struct ntfs_codec_ops ntfs_lznt1_codec_ops;
+#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
+extern const struct ntfs_codec_ops ntfs_xpress4k_codec_ops;
+extern const struct ntfs_codec_ops ntfs_xpress8k_codec_ops;
+extern const struct ntfs_codec_ops ntfs_xpress16k_codec_ops;
+extern const struct ntfs_codec_ops ntfs_lzx32k_codec_ops;
+#endif
+
+#endif /* _NTFS_CODEC_H */
diff --git a/fs/ntfs/quota.c b/fs/ntfs/quota.c
deleted file mode 100644
index b443243b58fb..000000000000
--- a/fs/ntfs/quota.c
+++ /dev/null
@@ -1,95 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * NTFS kernel quota ($Quota) handling.
- *
- * Copyright (c) 2004 Anton Altaparmakov
- */
-
-#include "index.h"
-#include "quota.h"
-#include "debug.h"
-#include "ntfs.h"
-
-/*
- * ntfs_mark_quotas_out_of_date - mark the quotas out of date on an ntfs volume
- * @vol: ntfs volume on which to mark the quotas out of date
- *
- * Mark the quotas out of date on the ntfs volume @vol and return 'true' on
- * success and 'false' on error.
- */
-bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol)
-{
- struct ntfs_index_context *ictx;
- struct quota_control_entry *qce;
- const __le32 qid = QUOTA_DEFAULTS_ID;
- int err;
-
- ntfs_debug("Entering.");
- if (NVolQuotaOutOfDate(vol))
- goto done;
- if (!vol->quota_ino || !vol->quota_q_ino) {
- ntfs_error(vol->sb, "Quota inodes are not open.");
- return false;
- }
- inode_lock(vol->quota_q_ino);
- ictx = ntfs_index_ctx_get(NTFS_I(vol->quota_q_ino), I30, 4);
- if (!ictx) {
- ntfs_error(vol->sb, "Failed to get index context.");
- goto err_out;
- }
- err = ntfs_index_lookup(&qid, sizeof(qid), ictx);
- if (err) {
- if (err == -ENOENT)
- ntfs_error(vol->sb, "Quota defaults entry is not present.");
- else
- ntfs_error(vol->sb, "Lookup of quota defaults entry failed.");
- goto err_out;
- }
- if (ictx->data_len < offsetof(struct quota_control_entry, sid)) {
- ntfs_error(vol->sb, "Quota defaults entry size is invalid. Run chkdsk.");
- goto err_out;
- }
- qce = (struct quota_control_entry *)ictx->data;
- if (le32_to_cpu(qce->version) != QUOTA_VERSION) {
- ntfs_error(vol->sb,
- "Quota defaults entry version 0x%x is not supported.",
- le32_to_cpu(qce->version));
- goto err_out;
- }
- ntfs_debug("Quota defaults flags = 0x%x.", le32_to_cpu(qce->flags));
- /* If quotas are already marked out of date, no need to do anything. */
- if (qce->flags & QUOTA_FLAG_OUT_OF_DATE)
- goto set_done;
- /*
- * If quota tracking is neither requested, nor enabled and there are no
- * pending deletes, no need to mark the quotas out of date.
- */
- if (!(qce->flags & (QUOTA_FLAG_TRACKING_ENABLED |
- QUOTA_FLAG_TRACKING_REQUESTED |
- QUOTA_FLAG_PENDING_DELETES)))
- goto set_done;
- /*
- * Set the QUOTA_FLAG_OUT_OF_DATE bit thus marking quotas out of date.
- * This is verified on WinXP to be sufficient to cause windows to
- * rescan the volume on boot and update all quota entries.
- */
- qce->flags |= QUOTA_FLAG_OUT_OF_DATE;
- /* Ensure the modified flags are written to disk. */
- ntfs_index_entry_mark_dirty(ictx);
-set_done:
- ntfs_index_ctx_put(ictx);
- inode_unlock(vol->quota_q_ino);
- /*
- * We set the flag so we do not try to mark the quotas out of date
- * again on remount.
- */
- NVolSetQuotaOutOfDate(vol);
-done:
- ntfs_debug("Done.");
- return true;
-err_out:
- if (ictx)
- ntfs_index_ctx_put(ictx);
- inode_unlock(vol->quota_q_ino);
- return false;
-}
diff --git a/fs/ntfs/quota.h b/fs/ntfs/quota.h
deleted file mode 100644
index 4b7322661a32..000000000000
--- a/fs/ntfs/quota.h
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Defines for NTFS kernel quota ($Quota) handling.
- *
- * Copyright (c) 2004 Anton Altaparmakov
- */
-
-#ifndef _LINUX_NTFS_QUOTA_H
-#define _LINUX_NTFS_QUOTA_H
-
-#include "volume.h"
-
-bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol);
-
-#endif /* _LINUX_NTFS_QUOTA_H */
diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c
index 74713716813f..1a6073e22677 100644
--- a/fs/ntfs/reparse.c
+++ b/fs/ntfs/reparse.c
@@ -24,6 +24,66 @@ struct wsl_link_reparse_data {
char link[];
};
+struct wof_reparse_data {
+ __le32 version;
+ __le32 provider;
+ __le32 provider_version;
+ __le32 compression_format;
+} __packed;
+
+#define WOF_CURRENT_VERSION cpu_to_le32(1)
+
+#define WOF_PROVIDER_WIM cpu_to_le32(1)
+#define WOF_PROVIDER_FILE cpu_to_le32(2)
+
+#define WOF_PROVIDER_CURRENT_VERSION cpu_to_le32(1)
+
+#define WOF_COMPRESSION_XPRESS4K cpu_to_le32(0)
+#define WOF_COMPRESSION_LZX cpu_to_le32(1)
+#define WOF_COMPRESSION_XPRESS8K cpu_to_le32(2)
+#define WOF_COMPRESSION_XPRESS16K cpu_to_le32(3)
+
+static bool reparse_name_is_valid(size_t size, size_t name_off, u16 len)
+{
+ if ((name_off | len) & 1)
+ return false;
+
+ return name_off + len <= size;
+}
+
+/*
+ * Windows-native reparse payloads store pathnames as UTF-16 strings with '\\'
+ * separators. Convert the on-disk UTF-16 target into the mount's NLS and
+ * normalize path separators.
+ */
+static int ntfs_reparse_target_to_nls(struct ntfs_volume *vol,
+ const __le16 *uname, u16 ulen,
+ char **target)
+{
+ int err, i;
+
+ *target = NULL;
+ ulen >>= 1;
+ if (!ulen)
+ return -EINVAL;
+
+ if (!uname[ulen - 1])
+ ulen--;
+
+ err = ntfs_ucstonls(vol, uname, ulen, (unsigned char **)target, 0);
+ if (err < 0) {
+ ntfs_attr_name_free((unsigned char **)target);
+ return err;
+ }
+
+ for (i = 0; i < err; i++) {
+ if ((*target)[i] == '\\')
+ (*target)[i] = '/';
+ }
+
+ return 0;
+}
+
/* Index entry in $Extend/$Reparse */
struct reparse_index {
struct index_entry_header header;
@@ -38,8 +98,10 @@ __le16 reparse_index_name[] = {cpu_to_le16('$'), cpu_to_le16('R'), 0};
* Check if the reparse point attribute buffer is valid.
* Returns true if valid, false otherwise.
*/
-static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni,
- const struct reparse_point *reparse_attr, size_t size)
+static bool valid_reparse_buffer(struct ntfs_inode *ni,
+ const struct reparse_point *reparse_attr,
+ size_t size,
+ size_t payload_min_len)
{
size_t expected;
@@ -50,6 +112,11 @@ static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni,
if (size < sizeof(struct reparse_point))
return false;
+ /* The payload must contain the fixed fields for the current tag. */
+ if (payload_min_len &&
+ le16_to_cpu(reparse_attr->reparse_data_length) < payload_min_len)
+ return false;
+
/* Reserved zero tag is invalid */
if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO)
return false;
@@ -79,35 +146,103 @@ static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni,
static bool valid_reparse_data(struct ntfs_inode *ni,
const struct reparse_point *reparse_attr, size_t size)
{
- const struct wsl_link_reparse_data *wsl_reparse_data =
- (const struct wsl_link_reparse_data *)reparse_attr->reparse_data;
- unsigned int data_len = le16_to_cpu(reparse_attr->reparse_data_length);
-
- if (ntfs_is_valid_reparse_buffer(ni, reparse_attr, size) == false)
+ if (size < sizeof(*reparse_attr))
return false;
switch (reparse_attr->reparse_tag) {
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ {
+ struct mount_point_reparse_data *data;
+ size_t data_offs;
+
+ if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data)))
+ return false;
+
+ data = (struct mount_point_reparse_data *)reparse_attr->reparse_data;
+ data_offs = offsetof(struct reparse_point, reparse_data) +
+ offsetof(struct mount_point_reparse_data, path_buffer);
+
+ if (!reparse_name_is_valid(size,
+ data_offs +
+ le16_to_cpu(data->substitute_name_offset),
+ le16_to_cpu(data->substitute_name_length)) ||
+ !reparse_name_is_valid(size,
+ data_offs +
+ le16_to_cpu(data->print_name_offset),
+ le16_to_cpu(data->print_name_length)))
+ return false;
+ break;
+ }
+ case IO_REPARSE_TAG_SYMLINK:
+ {
+ struct symlink_reparse_data *data;
+ size_t data_offs;
+
+ if (!valid_reparse_buffer(ni, reparse_attr, size,
+ sizeof(*data)))
+ return false;
+
+ data = (struct symlink_reparse_data *)reparse_attr->reparse_data;
+ data_offs = offsetof(struct reparse_point, reparse_data) +
+ offsetof(struct symlink_reparse_data, path_buffer);
+
+ if (!reparse_name_is_valid(size,
+ data_offs +
+ le16_to_cpu(data->substitute_name_offset),
+ le16_to_cpu(data->substitute_name_length)) ||
+ !reparse_name_is_valid(size,
+ data_offs +
+ le16_to_cpu(data->print_name_offset),
+ le16_to_cpu(data->print_name_length)))
+ return false;
+ break;
+ }
case IO_REPARSE_TAG_LX_SYMLINK:
- if (data_len <= sizeof(wsl_reparse_data->type) ||
- wsl_reparse_data->type != cpu_to_le32(2))
+ {
+ struct wsl_link_reparse_data *data;
+
+ if (!valid_reparse_buffer(ni, reparse_attr, size,
+ sizeof(*data)))
+ return false;
+
+ data = (struct wsl_link_reparse_data *)reparse_attr->reparse_data;
+
+ if (le16_to_cpu(reparse_attr->reparse_data_length) <= sizeof(data->type) ||
+ data->type != cpu_to_le32(2))
return false;
break;
+ }
case IO_REPARSE_TAG_AF_UNIX:
case IO_REPARSE_TAG_LX_FIFO:
case IO_REPARSE_TAG_LX_CHR:
case IO_REPARSE_TAG_LX_BLK:
- if (data_len || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
+ if (!valid_reparse_buffer(ni, reparse_attr, size, 0))
+ return false;
+ if (le16_to_cpu(reparse_attr->reparse_data_length) ||
+ !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
+ return false;
+ break;
+ case IO_REPARSE_TAG_WOF: {
+ if (!valid_reparse_buffer(ni, reparse_attr, size,
+ sizeof(struct wof_reparse_data)))
+ return false;
+ break;
+ }
+ default:
+ if (!valid_reparse_buffer(ni, reparse_attr, size, 0))
return false;
+ break;
}
return true;
}
-static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr)
+static unsigned int ntfs_reparse_tag_mode(__le32 reparse_tag)
{
unsigned int mode = 0;
- switch (reparse_attr->reparse_tag) {
+ switch (reparse_tag) {
+ case IO_REPARSE_TAG_MOUNT_POINT:
case IO_REPARSE_TAG_SYMLINK:
case IO_REPARSE_TAG_LX_SYMLINK:
mode = S_IFLNK;
@@ -129,45 +264,136 @@ static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr)
}
/*
- * Get the target for symbolic link
+ * Parse reparse point data and initialize its in-memory representation.
*/
-unsigned int ntfs_make_symlink(struct ntfs_inode *ni)
+int ntfs_parse_reparse(struct ntfs_inode *ni, unsigned int *mode)
{
s64 attr_size = 0;
+ int err = -EINVAL;
unsigned int lth;
struct reparse_point *reparse_attr;
- struct wsl_link_reparse_data *wsl_link_data;
- unsigned int mode = 0;
+
+ kvfree(ni->target);
+ ni->target = NULL;
+ ni->reparse_tag = 0;
+ ni->reparse_flags = 0;
+ *mode = 0;
reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0,
&attr_size);
- if (reparse_attr && attr_size &&
- valid_reparse_data(ni, reparse_attr, attr_size)) {
- switch (reparse_attr->reparse_tag) {
- case IO_REPARSE_TAG_LX_SYMLINK:
- wsl_link_data =
- (struct wsl_link_reparse_data *)reparse_attr->reparse_data;
- if (wsl_link_data->type == cpu_to_le32(2)) {
- lth = le16_to_cpu(reparse_attr->reparse_data_length) -
- sizeof(wsl_link_data->type);
- ni->target = kvzalloc(lth + 1, GFP_NOFS);
- if (ni->target) {
- memcpy(ni->target, wsl_link_data->link, lth);
- ni->target[lth] = 0;
- mode = ntfs_reparse_tag_mode(reparse_attr);
- }
+ if (IS_ERR(reparse_attr)) {
+ err = PTR_ERR(reparse_attr);
+ ntfs_error(ni->vol->sb,
+ "Failed to read reparse point: %d.", err);
+ return err;
+ }
+ if (!valid_reparse_data(ni, reparse_attr, attr_size)) {
+ ntfs_error(ni->vol->sb, "Invalid reparse point.");
+ err = -EFSCORRUPTED;
+ goto out;
+ }
+
+ switch (reparse_attr->reparse_tag) {
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ {
+ struct mount_point_reparse_data *data =
+ (struct mount_point_reparse_data *)reparse_attr->reparse_data;
+ const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
+ le16_to_cpu(data->substitute_name_offset));
+
+ err = ntfs_reparse_target_to_nls(ni->vol,
+ name,
+ le16_to_cpu(data->substitute_name_length),
+ &ni->target);
+ break;
+ }
+ case IO_REPARSE_TAG_SYMLINK:
+ {
+ struct symlink_reparse_data *data =
+ (struct symlink_reparse_data *)reparse_attr->reparse_data;
+ const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
+ le16_to_cpu(data->substitute_name_offset));
+
+ err = ntfs_reparse_target_to_nls(ni->vol,
+ name,
+ le16_to_cpu(data->substitute_name_length),
+ &ni->target);
+ if (!err)
+ ni->reparse_flags = data->flags;
+ break;
+ }
+ case IO_REPARSE_TAG_LX_SYMLINK:
+ {
+ struct wsl_link_reparse_data *wsl_link_data =
+ (struct wsl_link_reparse_data *)reparse_attr->reparse_data;
+
+ if (wsl_link_data->type == cpu_to_le32(2)) {
+ lth = le16_to_cpu(reparse_attr->reparse_data_length) -
+ sizeof(wsl_link_data->type);
+ ni->target = kvzalloc(lth + 1, GFP_NOFS);
+ if (ni->target) {
+ memcpy(ni->target, wsl_link_data->link, lth);
+ ni->target[lth] = 0;
+ err = 0;
}
- break;
- default:
- mode = ntfs_reparse_tag_mode(reparse_attr);
}
- } else
- ni->flags &= ~FILE_ATTR_REPARSE_POINT;
+ break;
+ }
+ case IO_REPARSE_TAG_WOF:
+ {
+#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
+ const struct wof_reparse_data *wof_data =
+ (const struct wof_reparse_data *)reparse_attr->reparse_data;
+
+ ni->itype.compressed.block_size_bits = 0;
+ ni->itype.compressed.block_size = 0;
+ if (wof_data->version == WOF_CURRENT_VERSION &&
+ wof_data->provider == WOF_PROVIDER_FILE &&
+ wof_data->provider_version ==
+ WOF_PROVIDER_CURRENT_VERSION) {
+ switch (wof_data->compression_format) {
+ case WOF_COMPRESSION_XPRESS4K:
+ ni->itype.compressed.block_size_bits =
+ 12;
+ break;
+ case WOF_COMPRESSION_XPRESS8K:
+ ni->itype.compressed.block_size_bits =
+ 13;
+ break;
+ case WOF_COMPRESSION_XPRESS16K:
+ ni->itype.compressed.block_size_bits =
+ 14;
+ break;
+ case WOF_COMPRESSION_LZX:
+ ni->itype.compressed.block_size_bits =
+ 15;
+ break;
+ }
+ }
+ if (ni->itype.compressed.block_size_bits)
+ ni->itype.compressed.block_size =
+ 1
+ << ni->itype.compressed.block_size_bits;
+#endif
+ NInoSetWofCompressed(ni);
+ VFS_I(ni)->i_mode &= ~0222;
+ err = 0;
+ break;
+ }
+ default:
+ err = 0;
+ }
+
+ if (!err) {
+ *mode = ntfs_reparse_tag_mode(
+ reparse_attr->reparse_tag);
+ ni->reparse_tag = reparse_attr->reparse_tag;
+ }
- if (reparse_attr)
- kvfree(reparse_attr);
+out:
+ kvfree(reparse_attr);
- return mode;
+ return err;
}
unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref)
@@ -179,13 +405,16 @@ 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);
+ if (IS_ERR(reparse_attr))
+ reparse_attr = NULL;
- if (reparse_attr && attr_size) {
+ if (reparse_attr && attr_size >= sizeof(*reparse_attr)) {
switch (reparse_attr->reparse_tag) {
+ case IO_REPARSE_TAG_MOUNT_POINT:
case IO_REPARSE_TAG_SYMLINK:
case IO_REPARSE_TAG_LX_SYMLINK:
dt_type = DT_LNK;
@@ -204,13 +433,121 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr
}
}
- if (reparse_attr)
- kvfree(reparse_attr);
+ kvfree(reparse_attr);
iput(vi);
return dt_type;
}
+static bool ntfs_is_drive_letter(const char *target)
+{
+ return ((target[0] >= 'A' && target[0] <= 'Z') ||
+ (target[0] >= 'a' && target[0] <= 'z')) &&
+ target[1] == ':';
+}
+
+/*
+ * ntfs_translate_symlink_path
+ *
+ * @dentry: dentry of the symlink/junction being resolved
+ * @target: NUL-terminated NLS target string with '\\' already normalized to '/'
+ * @translated: out parameter, set to a newly kmalloc'd relative path on success
+ *
+ * Windows junctions (IO_REPARSE_TAG_MOUNT_POINT) and non-relative symlinks
+ * (IO_REPARSE_TAG_SYMLINK without SYMLINK_FLAG_RELATIVE) store substitute
+ * names such as "/??/C:/foo", "//?/C:/foo", "/foo", or "C:/foo". Linux
+ * cannot continue pathname lookup from those syntaxes, so rewrite them as a
+ * path relative to the symlink's containing directory on this NTFS volume,
+ * anchored at the volume root via "../".
+ *
+ * Note: bind-mounted subtrees of the volume may resolve to unexpected
+ * locations because the computed "../" depth is relative to the NTFS volume
+ * root, not the bind-mounted subtree root.
+ *
+ * Return: 0 on success with *translated set to a newly allocated string the
+ * caller must kfree(); negative errno on failure.
+ */
+int ntfs_translate_symlink_path(struct dentry *dentry, const char *target,
+ char **translated)
+{
+ char *buf, *link_path, *out, *p;
+ const char *path, *tail;
+ unsigned int up_levels = 0;
+ size_t tail_len, out_len;
+ int err;
+
+ if (!dentry || !target || !translated)
+ return -EINVAL;
+
+ path = target;
+ /* reject UNC path. */
+ if (path[0] == '/' && path[1] == '/' &&
+ !(path[2] == '?' && path[3] == '/'))
+ return -EOPNOTSUPP;
+
+ /* target starts with "/??/" or "//?/"? */
+ if ((path[0] == '/' && path[1] == '?' && path[2] == '?' && path[3] == '/') ||
+ (path[0] == '/' && path[1] == '/' && path[2] == '?' && path[3] == '/'))
+ path += 4;
+
+ /* target must start with a drive character or '/'. */
+ if (ntfs_is_drive_letter(path)) {
+ if (path[2] && path[2] != '/')
+ return -EOPNOTSUPP;
+ tail = path + 2;
+ if (*tail == '/')
+ tail++;
+ } else if (*path == '/') {
+ tail = path + 1;
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ tail_len = strlen(tail);
+
+ buf = kmalloc(PATH_MAX, GFP_NOFS);
+ if (!buf)
+ return -ENOMEM;
+
+ link_path = dentry_path_raw(dentry, buf, PATH_MAX);
+ if (IS_ERR(link_path)) {
+ err = PTR_ERR(link_path);
+ goto out;
+ }
+
+ /* count '/' after the leading slash. */
+ for (p = link_path + 1; *p; p++)
+ if (*p == '/')
+ up_levels++;
+
+ /* build "./" + ("../" * up_levels) + tail. */
+ out_len = 2 + up_levels * 3 + tail_len;
+ if (out_len >= PATH_MAX) {
+ err = -ENAMETOOLONG;
+ goto out;
+ }
+
+ out = kmalloc(out_len + 1, GFP_NOFS);
+ if (!out) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(out, "./", 2);
+ p = out + 2;
+ while (up_levels--) {
+ memcpy(p, "../", 3);
+ p += 3;
+ }
+ memcpy(p, tail, tail_len + 1);
+
+ *translated = out;
+ err = 0;
+out:
+ kfree(buf);
+ return err;
+}
+
/*
* Set the index for new reparse data
*/
@@ -357,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
@@ -496,40 +834,158 @@ out:
* Set reparse data for a WSL type symlink
*/
int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
- const __le16 *target, int target_len)
+ const char *target, int target_len)
{
int err = 0;
- int len;
int reparse_len;
- unsigned char *utarget = NULL;
struct reparse_point *reparse;
struct wsl_link_reparse_data *data;
- len = ntfs_ucstonls(ni->vol, target, target_len, &utarget, 0);
- if (len <= 0)
- return -EINVAL;
-
- reparse_len = sizeof(struct reparse_point) + sizeof(data->type) + len;
+ reparse_len = sizeof(struct reparse_point) + sizeof(data->type) +
+ target_len;
reparse = kvzalloc(reparse_len, GFP_NOFS);
+ if (!reparse)
+ return -ENOMEM;
+
+ ni->target = kstrdup(target, GFP_NOFS);
+ if (!ni->target) {
+ kvfree(reparse);
+ return -ENOMEM;
+ }
+
+ data = (struct wsl_link_reparse_data *)reparse->reparse_data;
+ reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
+ reparse->reparse_data_length =
+ cpu_to_le16(sizeof(data->type) + target_len);
+ reparse->reserved = 0;
+ data->type = cpu_to_le32(2);
+ memcpy(data->link, target, target_len);
+ err = ntfs_set_ntfs_reparse_data(ni,
+ (char *)reparse, reparse_len);
+ kvfree(reparse);
+ if (err) {
+ kfree(ni->target);
+ ni->target = NULL;
+ } else {
+ ni->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
+ ni->reparse_flags = 0;
+ }
+ return err;
+}
+
+int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni,
+ const char *target, int target_len)
+{
+ int err = 0;
+ bool is_absolute, prt_sub_shared = true;
+ char *sub_name = NULL;
+ char *prt_name = NULL;
+ __le16 *sub_name_utf16 = NULL;
+ __le16 *prt_name_utf16 = NULL;
+ int sub_len, prt_len;
+ int total_data_len, total_reparse_len;
+ struct reparse_point *reparse = NULL;
+ struct symlink_reparse_data *data;
+ int i;
+
+ /* Determine if target is absolute (starts with drive letter like C:/ or C:\) */
+ is_absolute = target_len > 2 &&
+ ntfs_is_drive_letter(target) &&
+ (target[2] == '/' || target[2] == '\\');
+
+
+ /* Normalize and prepare NLS paths */
+ prt_name = kstrdup(target, GFP_NOFS);
+ if (!prt_name)
+ return -ENOMEM;
+
+ /* Replace '/' with '\' */
+ for (i = 0; i < target_len; i++) {
+ if (prt_name[i] == '/')
+ prt_name[i] = '\\';
+ }
+
+ if (is_absolute) {
+ /* Prepend '\??\' to Substitutename */
+ sub_name = kmalloc(target_len + 5, GFP_NOFS);
+ if (!sub_name) {
+ err = -ENOMEM;
+ goto out;
+ }
+ snprintf(sub_name, target_len + 5, "\\??\\%s", prt_name);
+ prt_sub_shared = false;
+ } else {
+ /* For relative symlinks (including absolute paths without drive letters),
+ * SubstituteName and PrintName are identical.
+ */
+ sub_name = prt_name;
+ }
+
+ /* Convert NLS paths to UTF-16 */
+ sub_len = ntfs_nlstoucs(ni->vol, sub_name, strlen(sub_name),
+ &sub_name_utf16, PATH_MAX);
+ if (sub_len < 0) {
+ err = sub_len;
+ goto out;
+ }
+
+ prt_len = ntfs_nlstoucs(ni->vol, prt_name, strlen(prt_name),
+ &prt_name_utf16, PATH_MAX);
+ if (prt_len < 0) {
+ err = prt_len;
+ goto out;
+ }
+
+ /* Check for buffer size limits */
+ total_data_len = sizeof(struct symlink_reparse_data) +
+ (sub_len + prt_len) * sizeof(__le16);
+ if (total_data_len > 16384) { /* 16KB max reparse tag size */
+ err = -EFBIG;
+ goto out;
+ }
+
+ total_reparse_len = sizeof(struct reparse_point) + total_data_len;
+ reparse = kvzalloc(total_reparse_len, GFP_NOFS);
if (!reparse) {
err = -ENOMEM;
- kfree(utarget);
- } else {
- data = (struct wsl_link_reparse_data *)reparse->reparse_data;
- reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
- reparse->reparse_data_length =
- cpu_to_le16(sizeof(data->type) + len);
- reparse->reserved = 0;
- data->type = cpu_to_le32(2);
- memcpy(data->link, utarget, len);
- err = ntfs_set_ntfs_reparse_data(ni,
- (char *)reparse, reparse_len);
- kvfree(reparse);
- if (!err)
- ni->target = utarget;
- else
- kfree(utarget);
+ goto out;
}
+
+ /* Pack fields in reparse buffer */
+ reparse->reparse_tag = IO_REPARSE_TAG_SYMLINK;
+ reparse->reparse_data_length = cpu_to_le16(total_data_len);
+ reparse->reserved = 0;
+
+ data = (struct symlink_reparse_data *)reparse->reparse_data;
+ data->substitute_name_offset = 0;
+ data->substitute_name_length = cpu_to_le16(sub_len * sizeof(__le16));
+ data->print_name_offset = data->substitute_name_length;
+ data->print_name_length = cpu_to_le16(prt_len * sizeof(__le16));
+ data->flags = is_absolute ? 0 : cpu_to_le32(SYMLINK_FLAG_RELATIVE);
+
+ /* Copy names to path_buffer */
+ memcpy(data->path_buffer, sub_name_utf16, sub_len * sizeof(__le16));
+ memcpy(data->path_buffer + sub_len, prt_name_utf16, prt_len * sizeof(__le16));
+
+ err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, total_reparse_len);
+ if (!err) {
+ strreplace(sub_name, '\\', '/');
+ ni->target = sub_name;
+ sub_name = NULL;
+ if (prt_sub_shared)
+ prt_name = NULL;
+ ni->reparse_tag = IO_REPARSE_TAG_SYMLINK;
+ ni->reparse_flags = is_absolute ? 0 :
+ cpu_to_le32(SYMLINK_FLAG_RELATIVE);
+ }
+
+out:
+ kfree(prt_name);
+ if (!prt_sub_shared)
+ kfree(sub_name);
+ kvfree(sub_name_utf16);
+ kvfree(prt_name_utf16);
+ kvfree(reparse);
return err;
}
@@ -568,6 +1024,10 @@ int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode)
err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse,
reparse_len);
kvfree(reparse);
+ if (!err) {
+ ni->reparse_tag = reparse_tag;
+ ni->reparse_flags = 0;
+ }
}
return err;
diff --git a/fs/ntfs/reparse.h b/fs/ntfs/reparse.h
index 28da40257f2a..b6360b0452e6 100644
--- a/fs/ntfs/reparse.h
+++ b/fs/ntfs/reparse.h
@@ -9,10 +9,14 @@
extern __le16 reparse_index_name[];
-unsigned int ntfs_make_symlink(struct ntfs_inode *ni);
+int ntfs_parse_reparse(struct ntfs_inode *ni, unsigned int *mode);
unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref);
+int ntfs_translate_symlink_path(struct dentry *dentry, const char *target,
+ char **translated);
int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
- const __le16 *target, int target_len);
+ const char *target, int target_len);
+int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni,
+ const char *symname, int symlen);
int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode);
int ntfs_delete_reparse_index(struct ntfs_inode *ni);
int ntfs_remove_ntfs_reparse_data(struct ntfs_inode *ni);
diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c
index da21dbeaaf66..3a61f19bcbee 100644
--- a/fs/ntfs/runlist.c
+++ b/fs/ntfs/runlist.c
@@ -71,29 +71,46 @@ static inline void ntfs_rl_mc(struct runlist_element *dstbase, int dst,
* On success, return a pointer to the newly allocated, or recycled, memory.
* On error, return -errno.
*/
-struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl,
- int old_size, int new_size)
+static inline struct runlist_element *ntfs_rl_realloc_gfp(struct runlist_element *rl,
+ int old_size, int new_size, gfp_t gfp)
{
struct runlist_element *new_rl;
+ size_t new_bytes;
+
+ if (old_size < 0 || new_size < 0)
+ return ERR_PTR(-EINVAL);
- old_size = old_size * sizeof(*rl);
- new_size = new_size * sizeof(*rl);
if (old_size == new_size)
return rl;
- new_rl = kvzalloc(new_size, GFP_NOFS);
+ if (check_mul_overflow(new_size, sizeof(*rl), &new_bytes))
+ return ERR_PTR(-EINVAL);
+
+ new_rl = kvzalloc(new_bytes, gfp);
if (unlikely(!new_rl))
return ERR_PTR(-ENOMEM);
if (likely(rl != NULL)) {
- if (unlikely(old_size > new_size))
- old_size = new_size;
- memcpy(new_rl, rl, old_size);
+ size_t old_bytes;
+
+ if (check_mul_overflow(old_size, sizeof(*rl), &old_bytes)) {
+ kvfree(new_rl);
+ return ERR_PTR(-EINVAL);
+ }
+ if (unlikely(old_bytes > new_bytes))
+ old_bytes = new_bytes;
+ memcpy(new_rl, rl, old_bytes);
kvfree(rl);
}
return new_rl;
}
+struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl,
+ int old_size, int new_size)
+{
+ return ntfs_rl_realloc_gfp(rl, old_size, new_size, GFP_NOFS);
+}
+
/*
* ntfs_rl_realloc_nofail - Reallocate memory for runlists
* @rl: original runlist
@@ -118,21 +135,8 @@ struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl,
static inline struct runlist_element *ntfs_rl_realloc_nofail(struct runlist_element *rl,
int old_size, int new_size)
{
- struct runlist_element *new_rl;
-
- old_size = old_size * sizeof(*rl);
- new_size = new_size * sizeof(*rl);
- if (old_size == new_size)
- return rl;
-
- new_rl = kvmalloc(new_size, GFP_NOFS | __GFP_NOFAIL);
- if (likely(rl != NULL)) {
- if (unlikely(old_size > new_size))
- old_size = new_size;
- memcpy(new_rl, rl, old_size);
- kvfree(rl);
- }
- return new_rl;
+ return ntfs_rl_realloc_gfp(rl, old_size, new_size,
+ GFP_NOFS | __GFP_NOFAIL);
}
/*
@@ -763,11 +767,30 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
buf = (u8 *)attr +
le16_to_cpu(attr->data.non_resident.mapping_pairs_offset);
attr_end = (u8 *)attr + le32_to_cpu(attr->length);
- if (unlikely(buf < (u8 *)attr || buf > attr_end)) {
+ if (unlikely(buf < (u8 *)attr || buf >= attr_end)) {
ntfs_error(vol->sb, "Corrupt attribute.");
return ERR_PTR(-EIO);
}
+ /*
+ * An empty mapping-pairs array is valid only for a zero-length
+ * attribute.
+ */
+ if (!*buf &&
+ (vcn ||
+ le64_to_cpu(attr->data.non_resident.highest_vcn) !=
+ (u64)(vcn - 1) ||
+ le64_to_cpu(attr->data.non_resident.allocated_size) ||
+ le64_to_cpu(attr->data.non_resident.data_size) ||
+ le64_to_cpu(attr->data.non_resident.initialized_size))) {
+ ntfs_error(vol->sb, "Invalid empty mapping pairs array.");
+ return ERR_PTR(-EIO);
+ }
+ if (!vcn && !*buf && old_runlist && old_runlist->rl) {
+ *new_rl_count = old_runlist->count;
+ return old_runlist->rl;
+ }
+
/* Current position in runlist array. */
rlpos = 0;
/* Allocate first page and set current runlist size to one page. */
@@ -811,7 +834,7 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
*/
b = *buf & 0xf;
if (b) {
- if (unlikely(buf + b > attr_end))
+ if (unlikely(buf + b >= attr_end))
goto io_error;
for (deltaxcn = (s8)buf[b--]; b; b--)
deltaxcn = (deltaxcn << 8) + buf[b];
@@ -855,12 +878,16 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
u8 b2 = *buf & 0xf;
b = b2 + ((*buf >> 4) & 0xf);
- if (buf + b > attr_end)
+ if (buf + b >= attr_end)
goto io_error;
for (deltaxcn = (s8)buf[b--]; b > b2; b--)
deltaxcn = (deltaxcn << 8) + buf[b];
/* Change the current lcn to its new value. */
- lcn += deltaxcn;
+ if (unlikely(check_add_overflow(lcn, deltaxcn, &lcn))) {
+ ntfs_error(vol->sb,
+ "LCN overflow in mapping pairs array.");
+ goto err_out;
+ }
#ifdef DEBUG
/*
* On NTFS 1.2-, apparently can have lcn == -1 to
@@ -876,12 +903,41 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
ntfs_error(vol->sb, "lcn == -1");
}
#endif
+ /* Check lcn is within the volume. */
+ if (unlikely(lcn >= (s64)vol->nr_clusters)) {
+ ntfs_error(vol->sb,
+ "LCN >= nr_clusters in mapping pairs array.");
+ goto err_out;
+ }
+
/* Check lcn is not below -1. */
if (unlikely(lcn < -1)) {
ntfs_error(vol->sb, "Invalid s64 < -1 in mapping pairs array.");
goto err_out;
}
+ if (lcn >= 0) {
+ s64 run_end;
+
+ /*
+ * Ensure that the run stays within the volume.
+ * A valid starting LCN is not sufficient because
+ * the run length comes from disk.
+ */
+ if (unlikely(check_add_overflow(lcn,
+ rl[rlpos].length,
+ &run_end))) {
+ ntfs_error(vol->sb,
+ "Run length overflow in mapping pairs array.");
+ goto err_out;
+ }
+ if (unlikely(run_end > (s64)vol->nr_clusters)) {
+ ntfs_error(vol->sb,
+ "Run extends beyond volume boundary.");
+ goto err_out;
+ }
+ }
+
/* chkdsk accepts zero-sized runs only for holes */
if ((lcn != -1) && !rl[rlpos].length) {
ntfs_error(vol->sb,
@@ -1748,7 +1804,7 @@ merge_src_rle:
new_2nd_cnt = src_cnt;
new_cnt = new_1st_cnt + new_2nd_cnt + new_3rd_cnt;
new_cnt += dst_rl_split.lcn >= LCN_HOLE ? 1 : 0;
- new_rl = kvcalloc(new_cnt, sizeof(*new_rl), GFP_NOFS);
+ new_rl = kvzalloc_objs(*new_rl, new_cnt, GFP_NOFS);
if (!new_rl)
return ERR_PTR(-ENOMEM);
@@ -1817,7 +1873,7 @@ struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int d
!ntfs_rle_contain(s_rl, start_vcn))
return ERR_PTR(-EINVAL);
- begin_split = s_rl->vcn != start_vcn ? true : false;
+ begin_split = s_rl->vcn != start_vcn;
e_rl = ntfs_rl_find_vcn_nolock(dst_rl, end_vcn);
if (!e_rl ||
@@ -1825,20 +1881,20 @@ struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int d
!ntfs_rle_contain(e_rl, end_vcn))
return ERR_PTR(-EINVAL);
- end_split = e_rl->vcn + e_rl->length - 1 != end_vcn ? true : false;
+ end_split = e_rl->vcn + e_rl->length - 1 != end_vcn;
/* @s_rl has to be split into left, punched hole, and right */
- one_split_3 = e_rl == s_rl && begin_split && end_split ? true : false;
+ one_split_3 = e_rl == s_rl && begin_split && end_split;
punch_cnt = (int)(e_rl - s_rl) + 1;
- *punch_rl = kvcalloc(punch_cnt + 1, sizeof(struct runlist_element),
- GFP_NOFS);
+ *punch_rl = kvzalloc_objs(struct runlist_element, punch_cnt + 1,
+ GFP_NOFS);
if (!*punch_rl)
return ERR_PTR(-ENOMEM);
new_cnt = dst_cnt - (int)(e_rl - s_rl + 1) + 3;
- new_rl = kvcalloc(new_cnt, sizeof(struct runlist_element), GFP_NOFS);
+ new_rl = kvzalloc_objs(struct runlist_element, new_cnt, GFP_NOFS);
if (!new_rl) {
kvfree(*punch_rl);
*punch_rl = NULL;
@@ -1968,7 +2024,7 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i
!ntfs_rle_contain(s_rl, start_vcn))
return ERR_PTR(-EINVAL);
- begin_split = s_rl->vcn != start_vcn ? true : false;
+ begin_split = s_rl->vcn != start_vcn;
e_rl = ntfs_rl_find_vcn_nolock(dst_rl, end_vcn);
if (!e_rl ||
@@ -1976,19 +2032,19 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i
!ntfs_rle_contain(e_rl, end_vcn))
return ERR_PTR(-EINVAL);
- end_split = e_rl->vcn + e_rl->length - 1 != end_vcn ? true : false;
+ end_split = e_rl->vcn + e_rl->length - 1 != end_vcn;
/* @s_rl has to be split into left, collapsed, and right */
- one_split_3 = e_rl == s_rl && begin_split && end_split ? true : false;
+ one_split_3 = e_rl == s_rl && begin_split && end_split;
punch_cnt = (int)(e_rl - s_rl) + 1;
- *punch_rl = kvcalloc(punch_cnt + 1, sizeof(struct runlist_element),
- GFP_NOFS);
+ *punch_rl = kvzalloc_objs(struct runlist_element, punch_cnt + 1,
+ GFP_NOFS);
if (!*punch_rl)
return ERR_PTR(-ENOMEM);
new_cnt = dst_cnt - (int)(e_rl - s_rl + 1) + 3;
- new_rl = kvcalloc(new_cnt, sizeof(struct runlist_element), GFP_NOFS);
+ new_rl = kvzalloc_objs(struct runlist_element, new_cnt, GFP_NOFS);
if (!new_rl) {
kvfree(*punch_rl);
*punch_rl = NULL;
@@ -2056,10 +2112,11 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i
* consists of holes.
*/
merge_cnt = 0;
- i = new_1st_cnt == 0 ? 1 : new_1st_cnt;
- if (ntfs_rle_lcn_contiguous(&new_rl[i - 1], &new_rl[i])) {
- /* Merge right and left */
- s_rl = &new_rl[new_1st_cnt - 1];
+ if (new_1st_cnt > 0 &&
+ ntfs_rle_lcn_contiguous(&new_rl[new_1st_cnt - 1],
+ &new_rl[new_1st_cnt])) {
+ /* Merge right and left. */
+ s_rl = &new_rl[new_1st_cnt - 1];
s_rl->length += s_rl[1].length;
merge_cnt = 1;
}
diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index 22dc7865eca7..4066bacabe37 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -17,7 +17,6 @@
#include "sysctl.h"
#include "logfile.h"
-#include "quota.h"
#include "index.h"
#include "ntfs.h"
#include "ea.h"
@@ -45,6 +44,28 @@ static const struct constant_table ntfs_param_enums[] = {
};
enum {
+ NATIVE_SYMLINK_RAW,
+ NATIVE_SYMLINK_REL,
+};
+
+static const struct constant_table ntfs_native_symlink_enums[] = {
+ { "raw", NATIVE_SYMLINK_RAW },
+ { "rel", NATIVE_SYMLINK_REL },
+ {}
+};
+
+enum {
+ SYMLINK_WSL,
+ SYMLINK_NATIVE,
+};
+
+static const struct constant_table ntfs_symlink_enums[] = {
+ { "wsl", SYMLINK_WSL },
+ { "native", SYMLINK_NATIVE },
+ {}
+};
+
+enum {
Opt_uid,
Opt_gid,
Opt_umask,
@@ -67,6 +88,8 @@ enum {
Opt_acl,
Opt_discard,
Opt_nocase,
+ Opt_native_symlink,
+ Opt_symlink,
};
static const struct fs_parameter_spec ntfs_parameters[] = {
@@ -92,6 +115,8 @@ static const struct fs_parameter_spec ntfs_parameters[] = {
fsparam_flag("discard", Opt_discard),
fsparam_flag("sparse", Opt_sparse),
fsparam_flag("nocase", Opt_nocase),
+ fsparam_enum("native_symlink", Opt_native_symlink, ntfs_native_symlink_enums),
+ fsparam_enum("symlink", Opt_symlink, ntfs_symlink_enums),
{}
};
@@ -216,6 +241,18 @@ static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
else
NVolClearDisableSparse(vol);
break;
+ case Opt_native_symlink:
+ if (result.uint_32 == NATIVE_SYMLINK_REL)
+ NVolSetNativeSymlinkRel(vol);
+ else
+ NVolClearNativeSymlinkRel(vol);
+ break;
+ case Opt_symlink:
+ if (result.uint_32 == SYMLINK_NATIVE)
+ NVolSetSymlinkNative(vol);
+ else
+ NVolClearSymlinkNative(vol);
+ break;
case Opt_sparse:
break;
default:
@@ -240,8 +277,7 @@ static int ntfs_reconfigure(struct fs_context *fc)
* flags are set. Also, empty the logfile journal as it would become
* stale as soon as something is written to the volume and mark the
* volume dirty so that chkdsk is run if the volume is not umounted
- * cleanly. Finally, mark the quotas out of date so Windows rescans
- * the volume on boot and updates them.
+ * cleanly.
*
* When remounting read-only, mark the volume clean if no volume errors
* have occurred.
@@ -274,12 +310,6 @@ static int ntfs_reconfigure(struct fs_context *fc)
NVolSetErrors(vol);
return -EROFS;
}
- if (!ntfs_mark_quotas_out_of_date(vol)) {
- ntfs_error(sb, "Failed to mark quotas out of date%s",
- es);
- NVolSetErrors(vol);
- return -EROFS;
- }
} else if (!sb_rdonly(sb) && (fc->sb_flags & SB_RDONLY)) {
/* Remounting read-only. */
if (!NVolErrors(vol)) {
@@ -413,6 +443,7 @@ int ntfs_write_volume_label(struct ntfs_volume *vol, char *label)
{
struct ntfs_inode *vol_ni = NTFS_I(vol->vol_ino);
struct ntfs_attr_search_ctx *ctx;
+ char *new_label;
__le16 *uname;
int uname_len, ret;
@@ -425,7 +456,7 @@ int ntfs_write_volume_label(struct ntfs_volume *vol, char *label)
return uname_len;
}
- if (uname_len > NTFS_MAX_LABEL_LEN) {
+ if (uname_len > NTFS_MAX_LABEL_LEN) {
ntfs_error(vol->sb,
"Volume label is too long (max %d characters).",
NTFS_MAX_LABEL_LEN);
@@ -433,30 +464,54 @@ int ntfs_write_volume_label(struct ntfs_volume *vol, char *label)
return -EINVAL;
}
+ /*
+ * Allocate the in-memory label copy up front. If kstrdup() fails we
+ * bail out before touching on-disk metadata, so the in-memory label
+ * and the on-disk label stay in sync.
+ */
+ new_label = kstrdup(label, GFP_KERNEL);
+ if (!new_label) {
+ kvfree(uname);
+ return -ENOMEM;
+ }
+
mutex_lock(&vol_ni->mrec_lock);
ctx = ntfs_attr_get_search_ctx(vol_ni, NULL);
if (!ctx) {
ret = -ENOMEM;
- goto out;
+ goto out;
}
- if (!ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0,
- ctx))
- ntfs_attr_record_rm(ctx);
+ ret = ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0,
+ ctx);
+ if (!ret)
+ ret = ntfs_attr_record_rm(ctx);
+ else if (ret == -ENOENT)
+ ret = 0;
ntfs_attr_put_search_ctx(ctx);
+ if (ret)
+ goto out;
ret = ntfs_resident_attr_record_add(vol_ni, AT_VOLUME_NAME, AT_UNNAMED, 0,
(u8 *)uname, uname_len * sizeof(__le16), 0);
out:
- mutex_unlock(&vol_ni->mrec_lock);
- kvfree(uname);
- mark_inode_dirty_sync(vol->vol_ino);
-
if (ret >= 0) {
- kfree(vol->volume_label);
- vol->volume_label = kstrdup(label, GFP_KERNEL);
+ char *old_label;
+
+ mutex_lock(&vol->volume_label_lock);
+ old_label = vol->volume_label;
+ vol->volume_label = new_label;
+ mutex_unlock(&vol->volume_label_lock);
+
+ kfree(old_label);
+ mark_inode_dirty_sync(vol->vol_ino);
ret = 0;
}
+ mutex_unlock(&vol_ni->mrec_lock);
+ kvfree(uname);
+
+ if (ret < 0)
+ kfree(new_label);
return ret;
}
@@ -502,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. */
@@ -590,7 +645,7 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol,
{
unsigned int sectors_per_cluster, sectors_per_cluster_bits, nr_hidden_sects;
int clusters_per_mft_record, clusters_per_index_record;
- s64 ll;
+ u64 ll;
vol->sector_size = le16_to_cpu(b->bpb.bytes_per_sector);
vol->sector_size_bits = ffs(vol->sector_size) - 1;
@@ -640,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,
@@ -677,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)",
@@ -700,23 +755,23 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol,
* the same as it is much faster on 32-bit CPUs.
*/
ll = le64_to_cpu(b->number_of_sectors) >> sectors_per_cluster_bits;
- if ((u64)ll >= 1ULL << 32) {
+ if (ll >= 1ULL << 32) {
ntfs_error(vol->sb, "Cannot handle 64-bit clusters.");
return false;
}
vol->nr_clusters = ll;
ntfs_debug("vol->nr_clusters = 0x%llx", vol->nr_clusters);
ll = le64_to_cpu(b->mft_lcn);
- if (ll >= vol->nr_clusters) {
- ntfs_error(vol->sb, "MFT LCN (%lli, 0x%llx) is beyond end of volume. Weird.",
+ if (ll >= (u64)vol->nr_clusters) {
+ ntfs_error(vol->sb, "MFT LCN (%llu, 0x%llx) is beyond end of volume. Weird.",
ll, ll);
return false;
}
vol->mft_lcn = ll;
ntfs_debug("vol->mft_lcn = 0x%llx", vol->mft_lcn);
ll = le64_to_cpu(b->mftmirr_lcn);
- if (ll >= vol->nr_clusters) {
- ntfs_error(vol->sb, "MFTMirr LCN (%lli, 0x%llx) is beyond end of volume. Weird.",
+ if (ll >= (u64)vol->nr_clusters) {
+ ntfs_error(vol->sb, "MFTMirr LCN (%llu, 0x%llx) is beyond end of volume. Weird.",
ll, ll);
return false;
}
@@ -979,6 +1034,13 @@ mft_unmap_out:
ntfs_is_baad_recordp((__le32 *)kmirr))
bytes = vol->mft_record_size;
}
+ /* Compare the two records. */
+ if (memcmp(kmft, kmirr, bytes)) {
+ ntfs_error(sb,
+ "$MFT and $MFTMirr record %i do not match. Run chkdsk.",
+ i);
+ goto mm_unmap_out;
+ }
kmft += vol->mft_record_size;
kmirr += vol->mft_record_size;
} while (++i < vol->mftmirr_size);
@@ -1155,73 +1217,6 @@ iput_out:
}
/*
- * load_and_init_quota - load and setup the quota file for a volume if present
- * @vol: ntfs super block describing device whose quota file to load
- *
- * Return 'true' on success or 'false' on error. If $Quota is not present, we
- * leave vol->quota_ino as NULL and return success.
- */
-static bool load_and_init_quota(struct ntfs_volume *vol)
-{
- static const __le16 Quota[7] = { cpu_to_le16('$'),
- cpu_to_le16('Q'), cpu_to_le16('u'),
- cpu_to_le16('o'), cpu_to_le16('t'),
- cpu_to_le16('a'), 0 };
- static __le16 Q[3] = { cpu_to_le16('$'),
- cpu_to_le16('Q'), 0 };
- struct ntfs_name *name = NULL;
- u64 mref;
- struct inode *tmp_ino;
-
- ntfs_debug("Entering.");
- /*
- * Find the inode number for the quota file by looking up the filename
- * $Quota in the extended system files directory $Extend.
- */
- inode_lock(vol->extend_ino);
- mref = ntfs_lookup_inode_by_name(NTFS_I(vol->extend_ino), Quota, 6,
- &name);
- inode_unlock(vol->extend_ino);
- kfree(name);
- if (IS_ERR_MREF(mref)) {
- /*
- * If the file does not exist, quotas are disabled and have
- * never been enabled on this volume, just return success.
- */
- if (MREF_ERR(mref) == -ENOENT) {
- ntfs_debug("$Quota not present. Volume does not have quotas enabled.");
- /*
- * No need to try to set quotas out of date if they are
- * not enabled.
- */
- NVolSetQuotaOutOfDate(vol);
- return true;
- }
- /* A real error occurred. */
- ntfs_error(vol->sb, "Failed to find inode number for $Quota.");
- return false;
- }
- /* Get the inode. */
- tmp_ino = ntfs_iget(vol->sb, MREF(mref));
- if (IS_ERR(tmp_ino)) {
- if (!IS_ERR(tmp_ino))
- iput(tmp_ino);
- ntfs_error(vol->sb, "Failed to load $Quota.");
- return false;
- }
- vol->quota_ino = tmp_ino;
- /* Get the $Q index allocation attribute. */
- tmp_ino = ntfs_index_iget(vol->quota_ino, Q, 2);
- if (IS_ERR(tmp_ino)) {
- ntfs_error(vol->sb, "Failed to load $Quota/$Q index.");
- return false;
- }
- vol->quota_q_ino = tmp_ino;
- ntfs_debug("Done.");
- return true;
-}
-
-/*
* load_and_init_attrdef - load the attribute definitions table for a volume
* @vol: ntfs super block describing device whose attrdef to load
*
@@ -1246,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)
@@ -1302,7 +1297,6 @@ static bool load_and_init_upcase(struct ntfs_volume *vol)
u8 *addr;
pgoff_t index, max_index;
unsigned int size;
- int i, max;
ntfs_debug("Entering.");
/* Read upcase table and setup vol->upcase and vol->upcase_len. */
@@ -1353,16 +1347,11 @@ read_partial_upcase_page:
mutex_unlock(&ntfs_lock);
return true;
}
- max = default_upcase_len;
- if (max > vol->upcase_len)
- max = vol->upcase_len;
- for (i = 0; i < max; i++)
- if (vol->upcase[i] != default_upcase[i])
- break;
- if (i == max) {
+ if (default_upcase_len == vol->upcase_len &&
+ !memcmp(vol->upcase, default_upcase,
+ default_upcase_len * sizeof(*default_upcase))) {
kvfree(vol->upcase);
vol->upcase = default_upcase;
- vol->upcase_len = max;
ntfs_nr_upcase_users++;
mutex_unlock(&ntfs_lock);
ntfs_debug("Volume specified $UpCase matches default. Using default.");
@@ -1510,6 +1499,7 @@ iput_volume_failed:
vol->volume_label = NULL;
}
+ ntfs_attr_reinit_search_ctx(ctx);
if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, NULL, 0, 0, 0, NULL, 0,
ctx) || ctx->attr->non_resident || ctx->attr->flags) {
ntfs_attr_put_search_ctx(ctx);
@@ -1638,18 +1628,6 @@ get_ctx_vol_failed:
ntfs_error(sb, "Failed to load $Extend.");
goto iput_sec_err_out;
}
- /* Find the quota file, load it if present, and set it up. */
- if (!load_and_init_quota(vol) &&
- vol->on_errors == ON_ERRORS_REMOUNT_RO) {
- static const char *es1 = "Failed to load $Quota";
- static const char *es2 = ". Run chkdsk.";
-
- sb->s_flags |= SB_RDONLY;
- ntfs_error(sb, "%s. Mounting read-only%s", es1, es2);
- /* This will prevent a read-write remount. */
- NVolSetErrors(vol);
- }
-
return true;
iput_sec_err_out:
@@ -1671,7 +1649,7 @@ iput_attrdef_err_out:
iput_upcase_err_out:
vol->upcase_len = 0;
mutex_lock(&ntfs_lock);
- if (vol->upcase == default_upcase) {
+ if (vol->upcase && vol->upcase == default_upcase) {
ntfs_nr_upcase_users--;
vol->upcase = NULL;
}
@@ -1701,12 +1679,12 @@ static void ntfs_volume_free(struct ntfs_volume *vol)
* the number of upcase users if we are a user.
*/
mutex_lock(&ntfs_lock);
- if (vol->upcase == default_upcase) {
+ if (vol->upcase && vol->upcase == default_upcase) {
ntfs_nr_upcase_users--;
vol->upcase = NULL;
}
- if (!ntfs_nr_upcase_users && default_upcase) {
+ if (!ntfs_nr_upcase_users) {
kvfree(default_upcase);
default_upcase = NULL;
}
@@ -1721,8 +1699,7 @@ static void ntfs_volume_free(struct ntfs_volume *vol)
unload_nls(vol->nls_map);
- if (vol->lcn_empty_bits_per_page)
- kvfree(vol->lcn_empty_bits_per_page);
+ kvfree(vol->lcn_empty_bits_per_page);
kfree(vol->volume_label);
kfree(vol);
}
@@ -1747,10 +1724,6 @@ static void ntfs_put_super(struct super_block *sb)
/* NTFS 3.0+ specific. */
if (vol->major_ver >= 3) {
- if (vol->quota_q_ino)
- ntfs_commit_inode(vol->quota_q_ino);
- if (vol->quota_ino)
- ntfs_commit_inode(vol->quota_ino);
if (vol->extend_ino)
ntfs_commit_inode(vol->extend_ino);
if (vol->secure_ino)
@@ -1799,14 +1772,6 @@ static void ntfs_put_super(struct super_block *sb)
/* NTFS 3.0+ specific clean up. */
if (vol->major_ver >= 3) {
- if (vol->quota_q_ino) {
- iput(vol->quota_q_ino);
- vol->quota_q_ino = NULL;
- }
- if (vol->quota_ino) {
- iput(vol->quota_ino);
- vol->quota_ino = NULL;
- }
if (vol->extend_ino) {
iput(vol->extend_ino);
vol->extend_ino = NULL;
@@ -1897,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;
}
@@ -1933,7 +1899,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol)
struct address_space *mapping = vol->lcnbmp_ino->i_mapping;
struct folio *folio;
pgoff_t index, max_index;
- struct file_ra_state *ra;
+ struct file_ra_state ra = { 0 };
ntfs_debug("Entering.");
/* Serialize accesses to the cluster bitmap. */
@@ -1941,11 +1907,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol)
if (NVolFreeClusterKnown(vol))
return atomic64_read(&vol->free_clusters);
- ra = kzalloc(sizeof(*ra), GFP_NOFS);
- if (!ra)
- return 0;
-
- file_ra_state_init(ra, mapping);
+ file_ra_state_init(&ra, mapping);
/*
* Convert the number of bits into bytes rounded up, then convert into
@@ -1964,7 +1926,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol)
* Get folio from page cache, getting it from backing store
* if necessary, and increment the use count.
*/
- folio = ntfs_get_locked_folio(mapping, index, max_index, ra);
+ folio = ntfs_get_locked_folio(mapping, index, max_index, &ra);
/* Ignore pages which errored synchronously. */
if (IS_ERR(folio)) {
@@ -2003,7 +1965,6 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol)
else
atomic64_set(&vol->free_clusters, nr_free);
- kfree(ra);
NVolSetFreeClusterKnown(vol);
wake_up_all(&vol->free_waitq);
ntfs_debug("Exiting.");
@@ -2058,15 +2019,11 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol,
struct address_space *mapping = vol->mftbmp_ino->i_mapping;
struct folio *folio;
pgoff_t index;
- struct file_ra_state *ra;
+ struct file_ra_state ra = { 0 };
ntfs_debug("Entering.");
- ra = kzalloc(sizeof(*ra), GFP_NOFS);
- if (!ra)
- return 0;
-
- file_ra_state_init(ra, mapping);
+ file_ra_state_init(&ra, mapping);
/* Use multiples of 4 bytes, thus max_size is PAGE_SIZE / 4. */
ntfs_debug("Reading $MFT/$BITMAP, max_index = 0x%lx, max_size = 0x%lx.",
@@ -2078,7 +2035,7 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol,
* Get folio from page cache, getting it from backing store
* if necessary, and increment the use count.
*/
- folio = ntfs_get_locked_folio(mapping, index, max_index, ra);
+ folio = ntfs_get_locked_folio(mapping, index, max_index, &ra);
/* Ignore pages which errored synchronously. */
if (IS_ERR(folio)) {
@@ -2107,10 +2064,8 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol,
/* If errors occurred we may well have gone below zero, fix this. */
if (nr_free < 0)
nr_free = 0;
- else
- atomic64_set(&vol->free_mft_records, nr_free);
+ atomic64_set(&vol->free_mft_records, nr_free);
- kfree(ra);
ntfs_debug("Exiting.");
return nr_free;
}
@@ -2175,7 +2130,14 @@ static int ntfs_statfs(struct dentry *dentry, struct kstatfs *sfs)
read_unlock_irqrestore(&mft_ni->size_lock, flags);
/* Free inodes in fs (based on current total count). */
- sfs->f_ffree = atomic64_read(&vol->free_mft_records);
+ size = atomic64_read(&vol->free_mft_records);
+ if (unlikely(size < 0 || size > (s64)sfs->f_files))
+ ntfs_warning(vol->sb, "Invalid free MFT record count %lld.", size);
+ if (size < 0)
+ size = 0;
+ else if (size > (s64)sfs->f_files)
+ size = sfs->f_files;
+ sfs->f_ffree = size;
/*
* File system id. This is extremely *nix flavour dependent and even
@@ -2455,14 +2417,6 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
vol->vol_ino = NULL;
/* NTFS 3.0+ specific clean up. */
if (vol->major_ver >= 3) {
- if (vol->quota_q_ino) {
- iput(vol->quota_q_ino);
- vol->quota_q_ino = NULL;
- }
- if (vol->quota_ino) {
- iput(vol->quota_ino);
- vol->quota_ino = NULL;
- }
if (vol->extend_ino) {
iput(vol->extend_ino);
vol->extend_ino = NULL;
@@ -2494,7 +2448,7 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
}
vol->upcase_len = 0;
mutex_lock(&ntfs_lock);
- if (vol->upcase == default_upcase) {
+ if (vol->upcase && vol->upcase == default_upcase) {
ntfs_nr_upcase_users--;
vol->upcase = NULL;
}
@@ -2509,8 +2463,6 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
}
/* Error exit code path. */
unl_upcase_iput_tmp_ino_err_out_now:
- if (vol->lcn_empty_bits_per_page)
- kvfree(vol->lcn_empty_bits_per_page);
/*
* Decrease the number of upcase users and destroy the global default
* upcase table if necessary.
@@ -2530,6 +2482,9 @@ iput_tmp_ino_err_out_now:
/* Errors at this stage are irrelevant. */
err_out_now:
sb->s_fs_info = NULL;
+ kvfree(vol->lcn_empty_bits_per_page);
+ kfree(vol->volume_label);
+ unload_nls(vol->nls_map);
kfree(vol);
ntfs_debug("Failed, returning -EINVAL.");
lockdep_on();
@@ -2590,7 +2545,7 @@ static int ntfs_init_fs_context(struct fs_context *fc)
struct ntfs_volume *vol;
/* Allocate a new struct ntfs_volume and place it in sb->s_fs_info. */
- vol = kmalloc(sizeof(struct ntfs_volume), GFP_NOFS);
+ vol = kmalloc_obj(struct ntfs_volume, GFP_NOFS);
if (!vol)
return -ENOMEM;
@@ -2610,6 +2565,7 @@ static int ntfs_init_fs_context(struct fs_context *fc)
NVolSetCaseSensitive(vol);
init_rwsem(&vol->mftbmp_lock);
init_rwsem(&vol->lcnbmp_lock);
+ mutex_init(&vol->volume_label_lock);
fc->s_fs_info = vol;
fc->ops = &ntfs_context_ops;
@@ -2628,7 +2584,7 @@ MODULE_ALIAS_FS("ntfs");
static int ntfs_workqueue_init(void)
{
- ntfs_wq = alloc_workqueue("ntfs-bg-io", 0, 0);
+ ntfs_wq = alloc_workqueue("ntfs-bg-io", WQ_PERCPU, 0);
if (!ntfs_wq)
return -ENOMEM;
return 0;
@@ -2742,6 +2698,9 @@ static void __exit exit_ntfs_fs(void)
* destroy cache.
*/
rcu_barrier();
+#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
+ ntfs_wof_free_workspaces();
+#endif
kmem_cache_destroy(ntfs_big_inode_cache);
kmem_cache_destroy(ntfs_inode_cache);
kmem_cache_destroy(ntfs_name_cache);
diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h
index af41427ec622..bc85a9592245 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -55,6 +55,10 @@
* @attrdef_size: Size of the attribute definition table in bytes.
* @attrdef: Table of attribute definitions. Obtained from FILE_AttrDef.
* @mft_data_pos: Mft record number at which to allocate the next mft record.
+ * @mft_record_reserve_pos: First record in the in-memory MFT metadata reserve
+ * (protected by mftbmp_lock).
+ * @mft_record_reserve_end: First record beyond the MFT metadata reserve
+ * (protected by mftbmp_lock).
* @mft_zone_start: First cluster of the mft zone.
* @mft_zone_end: First cluster beyond the mft zone.
* @mft_zone_pos: Current position in the mft zone.
@@ -72,12 +76,11 @@
* @vol_flags: Volume flags.
* @major_ver: Ntfs major version of volume.
* @minor_ver: Ntfs minor version of volume.
+ * @volume_label_lock: protects @volume_label.
* @volume_label: volume label.
* @root_ino: The VFS inode of the root directory.
* @secure_ino: The VFS inode of $Secure (NTFS3.0+ only, otherwise NULL).
* @extend_ino: The VFS inode of $Extend (NTFS3.0+ only, otherwise NULL).
- * @quota_ino: The VFS inode of $Quota.
- * @quota_q_ino: Attribute inode for $Quota/$Q.
* @nls_map: NLS (National Language Support) table.
* @nls_utf8: NLS table for UTF-8.
* @free_waitq: Wait queue for threads waiting for free clusters or MFT records.
@@ -120,6 +123,8 @@ struct ntfs_volume {
s32 attrdef_size;
struct attr_def *attrdef;
s64 mft_data_pos;
+ s64 mft_record_reserve_pos;
+ s64 mft_record_reserve_end;
s64 mft_zone_start;
s64 mft_zone_end;
s64 mft_zone_pos;
@@ -133,6 +138,7 @@ struct ntfs_volume {
struct inode *logfile_ino;
struct inode *lcnbmp_ino;
struct rw_semaphore lcnbmp_lock;
+ struct mutex volume_label_lock;
struct inode *vol_ino;
__le16 vol_flags;
u8 major_ver;
@@ -141,8 +147,6 @@ struct ntfs_volume {
struct inode *root_ino;
struct inode *secure_ino;
struct inode *extend_ino;
- struct inode *quota_ino;
- struct inode *quota_q_ino;
struct nls_table *nls_map;
bool nls_utf8;
wait_queue_head_t free_waitq;
@@ -165,7 +169,6 @@ struct ntfs_volume {
* Otherwise be case insensitive but still
* create file names in POSIX namespace.
* NV_LogFileEmpty LogFile journal is empty.
- * NV_QuotaOutOfDate Quota is out of date.
* NV_UsnJrnlStamped UsnJrnl has been stamped.
* NV_ReadOnly Volume is mounted read-only.
* NV_Compression Volume supports compression.
@@ -180,13 +183,13 @@ struct ntfs_volume {
*
* NV_Discard Issue discard/TRIM commands for freed clusters.
* NV_DisableSparse Disable creation of sparse regions.
+ * NV_NativeSymlinkRel Translate absolute Windows reparse targets (native_symlink=rel).
*/
enum {
NV_Errors,
NV_ShowSystemFiles,
NV_CaseSensitive,
NV_LogFileEmpty,
- NV_QuotaOutOfDate,
NV_UsnJrnlStamped,
NV_ReadOnly,
NV_Compression,
@@ -198,6 +201,8 @@ enum {
NV_CheckWindowsNames,
NV_Discard,
NV_DisableSparse,
+ NV_NativeSymlinkRel,
+ NV_SymlinkNative,
};
/*
@@ -223,7 +228,6 @@ DEFINE_NVOL_BIT_OPS(Errors)
DEFINE_NVOL_BIT_OPS(ShowSystemFiles)
DEFINE_NVOL_BIT_OPS(CaseSensitive)
DEFINE_NVOL_BIT_OPS(LogFileEmpty)
-DEFINE_NVOL_BIT_OPS(QuotaOutOfDate)
DEFINE_NVOL_BIT_OPS(UsnJrnlStamped)
DEFINE_NVOL_BIT_OPS(ReadOnly)
DEFINE_NVOL_BIT_OPS(Compression)
@@ -235,6 +239,8 @@ DEFINE_NVOL_BIT_OPS(HideDotFiles)
DEFINE_NVOL_BIT_OPS(CheckWindowsNames)
DEFINE_NVOL_BIT_OPS(Discard)
DEFINE_NVOL_BIT_OPS(DisableSparse)
+DEFINE_NVOL_BIT_OPS(NativeSymlinkRel)
+DEFINE_NVOL_BIT_OPS(SymlinkNative)
static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
{
@@ -252,17 +258,11 @@ static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr)
static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr)
{
- if (!NVolFreeClusterKnown(vol))
- return;
-
atomic64_add(nr, &vol->free_mft_records);
}
static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr)
{
- if (!NVolFreeClusterKnown(vol))
- return;
-
atomic64_sub(nr, &vol->free_mft_records);
}
diff --git a/fs/ntfs/wof.c b/fs/ntfs/wof.c
new file mode 100644
index 000000000000..9847259e5b1a
--- /dev/null
+++ b/fs/ntfs/wof.c
@@ -0,0 +1,757 @@
+// 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;
+}