]> www.infradead.org Git - users/willy/linux.git/commitdiff
fs/ntfs3: Add support for the compression attribute
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Thu, 22 Aug 2024 12:09:02 +0000 (15:09 +0300)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Tue, 3 Sep 2024 13:58:44 +0000 (16:58 +0300)
Support added for empty files and directories only.

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/attrib.c
fs/ntfs3/file.c
fs/ntfs3/frecord.c
fs/ntfs3/ntfs_fs.h

index d2a9cd9634295a3a8b605c2a4d38f5e5750f9bb2..0763202d00c9924579934160b0555d2d9ded8615 100644 (file)
@@ -2605,3 +2605,74 @@ int attr_force_nonresident(struct ntfs_inode *ni)
 
        return err;
 }
+
+/*
+ * Change the compression of data attribute
+ */
+int attr_set_compress(struct ntfs_inode *ni, bool compr)
+{
+       struct ATTRIB *attr;
+       struct mft_inode *mi;
+
+       attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi);
+       if (!attr)
+               return -ENOENT;
+
+       if (is_attr_compressed(attr) == !!compr) {
+               /* Already required compressed state. */
+               return 0;
+       }
+
+       if (attr->non_res) {
+               u16 run_off;
+               u32 run_size;
+               char *run;
+
+               if (attr->nres.data_size) {
+                       /*
+                        * There are rare cases when it possible to change
+                        * compress state without big changes.
+                        * TODO: Process these cases.
+                        */
+                       return -EOPNOTSUPP;
+               }
+
+               run_off = le16_to_cpu(attr->nres.run_off);
+               run_size = le32_to_cpu(attr->size) - run_off;
+               run = Add2Ptr(attr, run_off);
+
+               if (!compr) {
+                       /* remove field 'attr->nres.total_size'. */
+                       memmove(run - 8, run, run_size);
+                       run_off -= 8;
+               }
+
+               if (!mi_resize_attr(mi, attr, compr ? +8 : -8)) {
+                       /*
+                        * Ignore rare case when there are no 8 bytes in record with attr.
+                        * TODO: split attribute.
+                        */
+                       return -EOPNOTSUPP;
+               }
+
+               if (compr) {
+                       /* Make a gap for 'attr->nres.total_size'. */
+                       memmove(run + 8, run, run_size);
+                       run_off += 8;
+                       attr->nres.total_size = attr->nres.alloc_size;
+               }
+               attr->nres.run_off = cpu_to_le16(run_off);
+       }
+
+       /* Update data attribute flags. */
+       if (compr) {
+               attr->flags |= ATTR_FLAG_COMPRESSED;
+               attr->nres.c_unit = NTFS_LZNT_CUNIT;
+       } else {
+               attr->flags &= ~ATTR_FLAG_COMPRESSED;
+               attr->nres.c_unit = 0;
+       }
+       mi->dirty = true;
+
+       return 0;
+}
index 1cbaab1163b95ba52cbc155e5353e21b9b72dfe9..77f96665744e7c4b18c425516c67e63bec35afd2 100644 (file)
@@ -82,13 +82,14 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
                      struct fileattr *fa)
 {
        struct inode *inode = d_inode(dentry);
+       struct ntfs_inode *ni = ntfs_i(inode);
        u32 flags = fa->flags;
        unsigned int new_fl = 0;
 
        if (fileattr_has_fsx(fa))
                return -EOPNOTSUPP;
 
-       if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL))
+       if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_COMPR_FL))
                return -EOPNOTSUPP;
 
        if (flags & FS_IMMUTABLE_FL)
@@ -97,6 +98,15 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
        if (flags & FS_APPEND_FL)
                new_fl |= S_APPEND;
 
+       /* Allowed to change compression for empty files and for directories only. */
+       if (!is_dedup(ni) && !is_encrypted(ni) &&
+           (S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) {
+               /* Change compress state. */
+               int err = ni_set_compress(inode, flags & FS_COMPR_FL);
+               if (err)
+                       return err;
+       }
+
        inode_set_flags(inode, new_fl, S_IMMUTABLE | S_APPEND);
 
        inode_set_ctime_current(inode);
index 60c975ac38e610252955ee31a560f1d13b27d132..7d4e54161291753e60b3b4d250478661198fc0ac 100644 (file)
@@ -3450,3 +3450,75 @@ out:
 
        return 0;
 }
+
+/*
+ * ni_set_compress
+ *
+ * Helper for 'ntfs_fileattr_set'.
+ * Changes compression for empty files and directories only.
+ */
+int ni_set_compress(struct inode *inode, bool compr)
+{
+       int err;
+       struct ntfs_inode *ni = ntfs_i(inode);
+       struct ATTR_STD_INFO *std;
+       const char *bad_inode;
+
+       if (is_compressed(ni) == !!compr)
+               return 0;
+
+       if (is_sparsed(ni)) {
+               /* sparse and compress not compatible. */
+               return -EOPNOTSUPP;
+       }
+
+       if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) {
+               /*Skip other inodes. (symlink,fifo,...) */
+               return -EOPNOTSUPP;
+       }
+
+       bad_inode = NULL;
+
+       ni_lock(ni);
+
+       std = ni_std(ni);
+       if (!std) {
+               bad_inode = "no std";
+               goto out;
+       }
+
+       if (S_ISREG(inode->i_mode)) {
+               err = attr_set_compress(ni, compr);
+               if (err) {
+                       if (err == -ENOENT) {
+                               /* Fix on the fly? */
+                               /* Each file must contain data attribute. */
+                               bad_inode = "no data attribute";
+                       }
+                       goto out;
+               }
+       }
+
+       ni->std_fa = std->fa;
+       if (compr)
+               std->fa |= FILE_ATTRIBUTE_COMPRESSED;
+       else
+               std->fa &= ~FILE_ATTRIBUTE_COMPRESSED;
+
+       if (ni->std_fa != std->fa) {
+               ni->std_fa = std->fa;
+               ni->mi.dirty = true;
+       }
+       /* update duplicate information and directory entries in ni_write_inode.*/
+       ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
+       err = 0;
+
+out:
+       ni_unlock(ni);
+       if (bad_inode) {
+               ntfs_bad_inode(inode, bad_inode);
+               err = -EINVAL;
+       }
+
+       return err;
+}
index 79047cd54611719b1337300f9367bce7aa22964a..c8bcbd06f223d27547c0a888a453aec7daaa6353 100644 (file)
@@ -453,6 +453,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes);
 int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes);
 int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size);
 int attr_force_nonresident(struct ntfs_inode *ni);
+int attr_set_compress(struct ntfs_inode *ni, bool compr);
 
 /* Functions from attrlist.c */
 void al_destroy(struct ntfs_inode *ni);
@@ -588,6 +589,7 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
              bool *is_bad);
 
 bool ni_is_dirty(struct inode *inode);
+int ni_set_compress(struct inode *inode, bool compr);
 
 /* Globals from fslog.c */
 bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes);