[PATCH 09/11] fs/ntfs3: Optimize to store sorted attribute definition table

From: Konstantin Komarov
Date: Wed Apr 17 2024 - 09:08:56 EST


0x18 bytes instead of 0xa0

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@xxxxxxxxxxxxxxxxxxxx>
---
 fs/ntfs3/fsntfs.c  | 60 ++++++++++++++++++++++++++++-
 fs/ntfs3/inode.c   | 30 +++++++--------
 fs/ntfs3/ntfs.h    |  4 --
 fs/ntfs3/ntfs_fs.h | 40 ++++++++++---------
 fs/ntfs3/super.c   | 95 +++++++++++++++-------------------------------
 fs/ntfs3/xattr.c   |  6 +--
 6 files changed, 126 insertions(+), 109 deletions(-)

diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c
index ae2ef5c11868..f9c60f3cadaf 100644
--- a/fs/ntfs3/fsntfs.c
+++ b/fs/ntfs3/fsntfs.c
@@ -2698,4 +2698,62 @@ int ntfs_set_label(struct ntfs_sb_info *sbi, u8 *label, int len)
 out:
     __putname(uni);
     return err;
-}
\ No newline at end of file
+}
+
+/*
+ * Check $AttrDef content and store sorted small $AttrDef entries
+ */
+int ntfs_check_attr_def(struct ntfs_sb_info *sbi,
+            const struct ATTR_DEF_ENTRY *raw, u32 bytes)
+{
+    const struct ATTR_DEF_ENTRY *de_s;
+    struct ATTR_DEF_ENTRY_SMALL *de_d;
+    u32 i, j;
+    u32 max_attr_type;
+    u32 n = (bytes / sizeof(*raw)) * sizeof(*raw);
+
+    for (i = 0, max_attr_type = 0, de_s = raw; i < n; i++, de_s++) {
+        u64 sz;
+        u32 attr_type = le32_to_cpu(de_s->type);
+
+        if (!attr_type)
+            break;
+
+        if ((attr_type & 0xf) || (!i && ATTR_STD != de_s->type) ||
+            (i && le32_to_cpu(de_s[-1].type) >= attr_type)) {
+            return -EINVAL;
+        }
+
+        max_attr_type = attr_type;
+
+        sz = le64_to_cpu(de_s->max_sz);
+        if (de_s->type == ATTR_REPARSE)
+            sbi->attrdef.rp_max_size = sz;
+        else if (de_s->type == ATTR_EA)
+            sbi->attrdef.ea_max_size = sz;
+        else if (de_s->type == ATTR_LABEL)
+            sbi->attrdef.label_max_size = sz;
+    }
+
+    /* Last known attribute type is 0x100. */
+    if (!max_attr_type || max_attr_type > 0x200)
+        return -EINVAL;
+
+    n = max_attr_type >> 4;
+    sbi->attrdef.table = kcalloc(n, sizeof(*de_d), GFP_KERNEL);
+    if (!sbi->attrdef.table)
+        return -ENOMEM;
+
+    for (j = 0, de_s = raw; j < i; j++, de_s++) {
+        u32 idx = (le32_to_cpu(de_s->type) >> 4) - 1;
+        de_d = sbi->attrdef.table + idx;
+
+        de_d->type = de_s->type;
+        de_d->flags = de_s->flags;
+        de_d->min_sz = le64_to_cpu(de_s->min_sz);
+        de_d->max_sz = le64_to_cpu(de_s->max_sz);
+    }
+    sbi->attrdef.entries = n;
+
+    return 0;
+}
diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index 94177c1dd818..ae4465bf099f 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -18,7 +18,7 @@
 #include "ntfs_fs.h"

 /*
- * ntfs_read_mft - Read record and parses MFT.
+ * ntfs_read_mft - Read record and parse MFT.
  */
 static struct inode *ntfs_read_mft(struct inode *inode,
                    const struct cpu_str *name,
@@ -1090,29 +1090,27 @@ int ntfs_flush_inodes(struct super_block *sb, struct inode *i1,
     return ret;
 }

-int inode_write_data(struct inode *inode, const void *data, size_t bytes)
+/*
+ * Helper function to read file.
+ */
+int inode_read_data(struct inode *inode, void *data, size_t bytes)
 {
     pgoff_t idx;
+    struct address_space *mapping = inode->i_mapping;

-    /* Write non resident data. */
     for (idx = 0; bytes; idx++) {
         size_t op = bytes > PAGE_SIZE ? PAGE_SIZE : bytes;
-        struct page *page = ntfs_map_page(inode->i_mapping, idx);
+        struct page *page = read_mapping_page(mapping, idx, NULL);
+        void *kaddr;

         if (IS_ERR(page))
             return PTR_ERR(page);

-        lock_page(page);
-        WARN_ON(!PageUptodate(page));
-        ClearPageUptodate(page);
-
-        memcpy(page_address(page), data, op);
-
-        flush_dcache_page(page);
-        SetPageUptodate(page);
-        unlock_page(page);
+        kaddr = kmap_atomic(page);
+        memcpy(data, kaddr, op);
+        kunmap_atomic(kaddr);

-        ntfs_unmap_page(page);
+        put_page(page);

         bytes -= op;
         data = Add2Ptr(data, PAGE_SIZE);
@@ -1160,7 +1158,7 @@ ntfs_create_reparse_buffer(struct ntfs_sb_info *sbi, const char *symname,
     /* err = the length of unicode name of symlink. */
     *nsize = ntfs_reparse_bytes(err);

-    if (*nsize > sbi->reparse.max_size) {
+    if (*nsize > sbi->attrdef.rp_max_size) {
         err = -EFBIG;
         goto out;
     }
@@ -1954,7 +1952,7 @@ static noinline int ntfs_readlink_hlp(const struct dentry *link_de,
         rp = NULL;
     }

-    if (size > sbi->reparse.max_size || size <= sizeof(u32))
+    if (size > sbi->attrdef.rp_max_size || size <= sizeof(u32))
         goto out;

     if (!rp) {
diff --git a/fs/ntfs3/ntfs.h b/fs/ntfs3/ntfs.h
index 3d6143c7abc0..1dd03ba1dc93 100644
--- a/fs/ntfs3/ntfs.h
+++ b/fs/ntfs3/ntfs.h
@@ -817,7 +817,6 @@ struct VOLUME_INFO {

 #define SIZEOF_ATTRIBUTE_VOLUME_INFO 0xc

-#define NTFS_LABEL_MAX_LENGTH        (0x100 / sizeof(short))
 #define NTFS_ATTR_INDEXABLE        cpu_to_le32(0x00000002)
 #define NTFS_ATTR_DUPALLOWED        cpu_to_le32(0x00000004)
 #define NTFS_ATTR_MUST_BE_INDEXED    cpu_to_le32(0x00000010)
@@ -1002,9 +1001,6 @@ struct REPARSE_POINT {

 static_assert(sizeof(struct REPARSE_POINT) == 0x18);

-/* Maximum allowed size of the reparse data. */
-#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE    (16 * 1024)
-
 /*
  * The value of the following constant needs to satisfy the following
  * conditions:
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index 00dec0ec5648..1d4fb6f87dea 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -201,6 +201,15 @@ struct ntfs_index {
     u8 type; // index_mutex_classed
 };

+/* NOT ondisk!. Just a small copy of $AttrDef file entry. */
+struct ATTR_DEF_ENTRY_SMALL {
+    enum ATTR_TYPE type;
+    __le32 flags;
+    u64 min_sz;
+    u64 max_sz;
+};
+static_assert(sizeof(struct ATTR_DEF_ENTRY_SMALL) == 0x18);
+
 /* Minimum MFT zone. */
 #define NTFS_MIN_MFT_ZONE 100
 /* Step to increase the MFT. */
@@ -242,9 +251,13 @@ struct ntfs_sb_info {
     CLST reparse_no;
     CLST usn_jrnl_no;

-    struct ATTR_DEF_ENTRY *def_table; // Attribute definition table.
-    u32 def_entries;
-    u32 ea_max_size;
+    struct {
+        u64 rp_max_size; // 16K
+        u32 entries;
+        u32 ea_max_size;
+        u32 label_max_size;
+        struct ATTR_DEF_ENTRY_SMALL *table; // 'entries'.
+    } attrdef;

     struct MFT_REC *new_rec;

@@ -296,7 +309,6 @@ struct ntfs_sb_info {
     struct {
         struct ntfs_index index_r;
         struct ntfs_inode *ni;
-        u64 max_size; // 16K
     } reparse;

     struct {
@@ -658,6 +670,8 @@ int run_deallocate(struct ntfs_sb_info *sbi, const struct runs_tree *run,
            bool trim);
 bool valid_windows_name(struct ntfs_sb_info *sbi, const struct le_str *name);
 int ntfs_set_label(struct ntfs_sb_info *sbi, u8 *label, int len);
+int ntfs_check_attr_def(struct ntfs_sb_info *sbi,
+            const struct ATTR_DEF_ENTRY *raw, u32 bytes);

 /* Globals from index.c */
 int indx_used_bit(struct ntfs_index *indx, struct ntfs_inode *ni, size_t *bit);
@@ -714,7 +728,7 @@ int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc);
 int ntfs_sync_inode(struct inode *inode);
 int ntfs_flush_inodes(struct super_block *sb, struct inode *i1,
               struct inode *i2);
-int inode_write_data(struct inode *inode, const void *data, size_t bytes);
+int inode_read_data(struct inode *inode, void *data, size_t bytes);
 int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
               struct dentry *dentry, const struct cpu_str *uni,
               umode_t mode, dev_t dev, const char *symname, u32 size,
@@ -908,22 +922,6 @@ static inline bool ntfs_is_meta_file(struct ntfs_sb_info *sbi, CLST rno)
            rno == sbi->usn_jrnl_no;
 }

-static inline void ntfs_unmap_page(struct page *page)
-{
-    kunmap(page);
-    put_page(page);
-}
-
-static inline struct page *ntfs_map_page(struct address_space *mapping,
-                     unsigned long index)
-{
-    struct page *page = read_mapping_page(mapping, index, NULL);
-
-    if (!IS_ERR(page))
-        kmap(page);
-    return page;
-}
-
 static inline size_t wnd_zone_bit(const struct wnd_bitmap *wnd)
 {
     return wnd->zone_bit;
diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index ac4722011140..8beefbca5769 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -624,7 +624,7 @@ static void ntfs3_free_sbi(struct ntfs_sb_info *sbi)
 {
     kfree(sbi->new_rec);
     kvfree(ntfs_put_shared(sbi->upcase));
-    kvfree(sbi->def_table);
+    kfree(sbi->attrdef.table);
     kfree(sbi->compress.lznt);
 #ifdef CONFIG_NTFS3_LZX_XPRESS
     xpress_free_decompressor(sbi->compress.xpress);
@@ -1157,8 +1157,6 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
     CLST vcn, lcn, len;
     struct ATTRIB *attr;
     const struct VOLUME_INFO *info;
-    u32 idx, done, bytes;
-    struct ATTR_DEF_ENTRY *t;
     u16 *shared;
     struct MFT_REF ref;
     bool ro = sb_rdonly(sb);
@@ -1199,7 +1197,7 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)

     /*
      * Load $Volume. This should be done before $LogFile
-     * 'cause 'sbi->volume.ni' is used 'ntfs_set_state'.
+     * 'cause 'sbi->volume.ni' is used in 'ntfs_set_state'.
      */
     ref.low = cpu_to_le32(MFT_REC_VOL);
     ref.seq = cpu_to_le16(MFT_REC_VOL);
@@ -1422,54 +1420,28 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
         goto put_inode_out;
     }

-    bytes = inode->i_size;
-    sbi->def_table = t = kvmalloc(bytes, GFP_KERNEL);
-    if (!t) {
-        err = -ENOMEM;
-        goto put_inode_out;
-    }
-
-    for (done = idx = 0; done < bytes; done += PAGE_SIZE, idx++) {
-        unsigned long tail = bytes - done;
-        struct page *page = ntfs_map_page(inode->i_mapping, idx);
-
-        if (IS_ERR(page)) {
-            err = PTR_ERR(page);
-            ntfs_err(sb, "Failed to read $AttrDef (%d).", err);
+    {
+        u32 bytes = inode->i_size;
+        struct ATTR_DEF_ENTRY *def_table = kmalloc(bytes, GFP_KERNEL);
+        if (!def_table) {
+            err = -ENOMEM;
             goto put_inode_out;
         }
-        memcpy(Add2Ptr(t, done), page_address(page),
-               min(PAGE_SIZE, tail));
-        ntfs_unmap_page(page);

-        if (!idx && ATTR_STD != t->type) {
-            ntfs_err(sb, "$AttrDef is corrupted.");
-            err = -EINVAL;
-            goto put_inode_out;
+        /* Read the entire file. */
+        err = inode_read_data(inode, def_table, bytes);
+        if (err) {
+            ntfs_err(sb, "Failed to read $AttrDef (%d).", err);
+        } else {
+            /* Check content and store sorted array. */
+            err = ntfs_check_attr_def(sbi, def_table, bytes);
+            if (err)
+                ntfs_err(sb, "$AttrDef is corrupted.");
         }
-    }
-
-    t += 1;
-    sbi->def_entries = 1;
-    done = sizeof(struct ATTR_DEF_ENTRY);
-    sbi->reparse.max_size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
-    sbi->ea_max_size = 0x10000; /* default formatter value */
-
-    while (done + sizeof(struct ATTR_DEF_ENTRY) <= bytes) {
-        u32 t32 = le32_to_cpu(t->type);
-        u64 sz = le64_to_cpu(t->max_sz);
-
-        if ((t32 & 0xF) || le32_to_cpu(t[-1].type) >= t32)
-            break;
-
-        if (t->type == ATTR_REPARSE)
-            sbi->reparse.max_size = sz;
-        else if (t->type == ATTR_EA)
-            sbi->ea_max_size = sz;

-        done += sizeof(struct ATTR_DEF_ENTRY);
-        t += 1;
-        sbi->def_entries += 1;
+        kfree(def_table);
+        if (err)
+            goto put_inode_out;
     }
     iput(inode);

@@ -1489,27 +1461,22 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
         goto put_inode_out;
     }

-    for (idx = 0; idx < (0x10000 * sizeof(short) >> PAGE_SHIFT); idx++) {
-        const __le16 *src;
-        u16 *dst = Add2Ptr(sbi->upcase, idx << PAGE_SHIFT);
-        struct page *page = ntfs_map_page(inode->i_mapping, idx);
-
-        if (IS_ERR(page)) {
-            err = PTR_ERR(page);
-            ntfs_err(sb, "Failed to read $UpCase (%d).", err);
-            goto put_inode_out;
-        }
-
-        src = page_address(page);
+    /* Read the entire file. */
+    err = inode_read_data(inode, sbi->upcase, 0x10000 * sizeof(short));
+    if (err) {
+        ntfs_err(sb, "Failed to read $UpCase (%d).", err);
+        goto put_inode_out;
+    }

 #ifdef __BIG_ENDIAN
-        for (i = 0; i < PAGE_SIZE / sizeof(u16); i++)
+    {
+        const __le16 *src = sbi->upcase;
+        u16 *dst = sbi->upcase;
+
+        for (i = 0; i < 0x10000; i++)
             *dst++ = le16_to_cpu(*src++);
-#else
-        memcpy(dst, src, PAGE_SIZE);
-#endif
-        ntfs_unmap_page(page);
     }
+#endif

     shared = ntfs_set_shared(sbi->upcase, 0x10000 * sizeof(short));
     if (shared && sbi->upcase != shared) {
diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c
index 872df2197202..a7f122e51c04 100644
--- a/fs/ntfs3/xattr.c
+++ b/fs/ntfs3/xattr.c
@@ -99,12 +99,12 @@ static int ntfs_read_ea(struct ntfs_inode *ni, struct EA_FULL **ea,

     /* Check Ea limit. */
     size = le32_to_cpu((*info)->size);
-    if (size > sbi->ea_max_size) {
+    if (size > sbi->attrdef.ea_max_size) {
         err = -EFBIG;
         goto out;
     }

-    if (attr_size(attr_ea) > sbi->ea_max_size) {
+    if (attr_size(attr_ea) > sbi->attrdef.ea_max_size) {
         err = -EFBIG;
         goto out;
     }
@@ -430,7 +430,7 @@ static noinline int ntfs_set_ea(struct inode *inode, const char *name,
      * 1. Check ea_info.size_pack for overflow.
      * 2. New attribute size must fit value from $AttrDef
      */
-    if (new_pack > 0xffff || size > sbi->ea_max_size) {
+    if (new_pack > 0xffff || size > sbi->attrdef.ea_max_size) {
         ntfs_inode_warn(
             inode,
             "The size of extended attributes must not exceed 64KiB");
--
2.34.1