summaryrefslogtreecommitdiffstats
path: root/fs/ntfs/ea.c
diff options
context:
space:
mode:
authorKees Cook <kees+treewide@kernel.org>2026-09-02 15:31:14 -0700
committerKees Cook <kees@kernel.org>2026-09-04 21:37:00 -0700
commit3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d (patch)
treec65086f9bdcd48c6360fb7cb4598bca084da1f32 /fs/ntfs/ea.c
downloadlinux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.tar.gz
linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.zip
treewide: refresh kmalloc_obj() conversionsgrafted
This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook <kees+treewide@kernel.org>
Diffstat (limited to 'fs/ntfs/ea.c')
-rw-r--r--fs/ntfs/ea.c1141
1 files changed, 1141 insertions, 0 deletions
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
new file mode 100644
index 000000000..b4fcfbe2d
--- /dev/null
+++ b/fs/ntfs/ea.c
@@ -0,0 +1,1141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Pocessing of EA's
+ *
+ * Part of this file is based on code from the NTFS-3G.
+ *
+ * Copyright (c) 2014-2021 Jean-Pierre Andre
+ * Copyright (c) 2025 LG Electronics Co., Ltd.
+ */
+
+#include <linux/fs.h>
+#include <linux/posix_acl.h>
+#include <linux/posix_acl_xattr.h>
+#include <linux/xattr.h>
+
+#include "layout.h"
+#include "attrib.h"
+#include "index.h"
+#include "dir.h"
+#include "ea.h"
+
+static int ntfs_write_ea(struct ntfs_inode *ni, __le32 type, char *value, s64 ea_off,
+ s64 ea_size, bool need_truncate)
+{
+ struct inode *ea_vi;
+ int err = 0;
+ s64 written;
+
+ ea_vi = ntfs_attr_iget(VFS_I(ni), type, AT_UNNAMED, 0);
+ if (IS_ERR(ea_vi))
+ return PTR_ERR(ea_vi);
+
+ written = ntfs_inode_attr_pwrite(ea_vi, ea_off, ea_size, value, false);
+ if (written != ea_size)
+ err = -EIO;
+ else {
+ struct ntfs_inode *ea_ni = NTFS_I(ea_vi);
+
+ if (need_truncate && ea_ni->data_size > ea_off + ea_size)
+ ntfs_attr_truncate(ea_ni, ea_off + ea_size);
+ mark_mft_record_dirty(ni);
+ }
+
+ iput(ea_vi);
+ return err;
+}
+
+static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name,
+ int name_len, s64 *ea_offset, s64 *ea_size)
+{
+ const struct ea_attr *p_ea;
+ size_t actual_size;
+ loff_t offset, p_ea_size;
+ unsigned int next;
+
+ 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);
+
+ if (p_ea_size < sizeof(struct ea_attr) ||
+ offset + p_ea_size > ea_buf_size)
+ break;
+
+ if ((s64)p_ea->ea_name_length + 1 >
+ p_ea_size - offsetof(struct ea_attr, ea_name))
+ break;
+
+ actual_size = ALIGN(struct_size(p_ea, ea_name, 1 + p_ea->ea_name_length +
+ le16_to_cpu(p_ea->ea_value_length)), 4);
+ if (actual_size > p_ea_size)
+ break;
+
+ if (p_ea->ea_name_length == name_len &&
+ !memcmp(p_ea->ea_name, name, name_len)) {
+ *ea_offset = offset;
+ *ea_size = next ? next : actual_size;
+
+ if (ea_buf_size < *ea_offset + *ea_size)
+ goto out;
+
+ return 0;
+ }
+ offset += next;
+ } while (next > 0 && offset < ea_buf_size);
+
+out:
+ return -ENOENT;
+}
+
+/*
+ * Return the existing EA
+ *
+ * The EA_INFORMATION is not examined and the consistency of the
+ * existing EA is not checked.
+ *
+ * If successful, the full attribute is returned unchanged
+ * and its size is returned.
+ * If the designated buffer is too small, the needed size is
+ * returned, and the buffer is left unchanged.
+ * If there is an error, a negative value is returned and errno
+ * is set according to the error.
+ */
+static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len,
+ void *buffer, size_t size)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ const struct ea_attr *p_ea;
+ char *ea_buf;
+ s64 ea_off, ea_size, all_ea_size, ea_info_size;
+ int err;
+ u32 ea_info_qlen;
+ u16 ea_value_len;
+ struct ea_information *p_ea_info;
+
+ if (!NInoHasEA(ni))
+ return -ENODATA;
+
+ p_ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
+ &ea_info_size);
+ 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 -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 (IS_ERR(ea_buf))
+ return PTR_ERR(ea_buf);
+
+ if (ea_info_qlen > all_ea_size) {
+ err = -EIO;
+ goto free_ea_buf;
+ }
+
+ err = ntfs_ea_lookup(ea_buf, ea_info_qlen, name, name_len, &ea_off,
+ &ea_size);
+ if (!err) {
+ p_ea = (struct ea_attr *)&ea_buf[ea_off];
+ ea_value_len = le16_to_cpu(p_ea->ea_value_length);
+ if (!buffer) {
+ kvfree(ea_buf);
+ return ea_value_len;
+ }
+
+ if (ea_value_len > size) {
+ err = -ERANGE;
+ goto free_ea_buf;
+ }
+
+ memcpy(buffer, &p_ea->ea_name[p_ea->ea_name_length + 1],
+ ea_value_len);
+ kvfree(ea_buf);
+ return ea_value_len;
+ }
+
+ err = -ENODATA;
+free_ea_buf:
+ kvfree(ea_buf);
+ return err;
+}
+
+static inline int ea_packed_size(const struct ea_attr *p_ea)
+{
+ /*
+ * 4 bytes for header (flags and lengths) + name length + 1 +
+ * value length.
+ */
+ return 5 + p_ea->ea_name_length + le16_to_cpu(p_ea->ea_value_length);
+}
+
+/*
+ * Set a new EA, and set EA_INFORMATION accordingly
+ *
+ * This is roughly the same as ZwSetEaFile() on Windows, however
+ * the "offset to next" of the last EA should not be cleared.
+ *
+ * Consistency of the new EA is first checked.
+ *
+ * EA_INFORMATION is set first, and it is restored to its former
+ * state if setting EA fails.
+ */
+static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
+ const void *value, size_t val_size, int flags,
+ __le16 *packed_ea_size)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ struct ea_information *p_ea_info = NULL;
+ int ea_packed, err = 0;
+ 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;
+
+ if (name_len > 255)
+ return -ENAMETOOLONG;
+
+ 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 (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);
+ goto create_ea_info;
+ }
+
+ ea_info_qsize = le32_to_cpu(p_ea_info->ea_query_length);
+ } else {
+create_ea_info:
+ p_ea_info = kzalloc_obj(struct ea_information, GFP_NOFS);
+ if (!p_ea_info)
+ return -ENOMEM;
+
+ ea_info_qsize = 0;
+ err = ntfs_attr_add(ni, AT_EA_INFORMATION, AT_UNNAMED, 0,
+ (char *)p_ea_info, sizeof(struct ea_information));
+ if (err)
+ goto out;
+
+ if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
+ err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0);
+ if (err)
+ goto out;
+ }
+
+ goto alloc_new_ea;
+ }
+
+ if (ea_info_qsize > all_ea_size) {
+ err = -EIO;
+ goto out;
+ }
+
+ err = ntfs_ea_lookup(ea_buf, ea_info_qsize, name, name_len, &ea_off,
+ &ea_size);
+ if (ea_info_qsize && !err) {
+ if (flags & XATTR_CREATE) {
+ 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);
+
+ if (val_size &&
+ le16_to_cpu(p_ea->ea_value_length) == val_size &&
+ !memcmp(p_ea->ea_name + p_ea->ea_name_length + 1, value,
+ val_size))
+ goto out;
+
+ le16_add_cpu(&p_ea_info->ea_length, 0 - ea_packed_size(p_ea));
+
+ if (p_ea->flags & NEED_EA)
+ le16_add_cpu(&p_ea_info->need_ea_count, -1);
+
+ memmove((char *)p_ea, (char *)p_ea + ea_size, ea_info_qsize - (ea_off + ea_size));
+ ea_info_qsize -= ea_size;
+ p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize);
+
+ 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_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) {
+ 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 {
+ if (flags & XATTR_REPLACE) {
+ err = -ENODATA;
+ goto out;
+ }
+
+ if (ntfs_attr_size_bounds_check(ni->vol, AT_EA,
+ ea_info_qsize + new_ea_size)) {
+ err = -EFBIG;
+ goto out;
+ }
+ }
+alloc_new_ea:
+ 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.
+ */
+ memcpy(p_ea->ea_name, name, name_len);
+ p_ea->ea_name_length = name_len;
+ p_ea->ea_name[name_len] = 0;
+ memcpy(p_ea->ea_name + name_len + 1, value, val_size);
+ p_ea->ea_value_length = cpu_to_le16(val_size);
+ p_ea->next_entry_offset = cpu_to_le32(new_ea_size);
+
+ ea_packed = le16_to_cpu(p_ea_info->ea_length) + ea_packed_size(p_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) {
+ err = -EFBIG;
+ goto out;
+ }
+
+ /*
+ * 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, ea_buf,
+ ea_info_qsize + new_ea_size);
+ if (err)
+ goto out;
+ } else {
+ err = ntfs_write_ea(ni, AT_EA, ea_buf, 0,
+ ea_info_qsize + new_ea_size, true);
+ if (err)
+ goto out;
+ }
+
+ err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
+ sizeof(struct ea_information), false);
+ if (err)
+ goto out;
+
+ if (packed_ea_size)
+ *packed_ea_size = p_ea_info->ea_length;
+ mark_mft_record_dirty(ni);
+out:
+ 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;
+}
+
+/*
+ * 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,
+ 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 == 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 == 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;
+ }
+
+ /* Load mode to lxdev EA */
+ err = ntfs_get_ea(inode, "$LXDEV", sizeof("$LXDEV") - 1, &v, sizeof(v));
+ if (err == sizeof(v))
+ *rdevp = le32_to_cpu(v);
+ err = 0;
+
+ return err;
+}
+
+int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size,
+ unsigned int flags)
+{
+ __le32 v;
+ int err = 0;
+
+ if (ea_size)
+ *ea_size = 0;
+
+ if (flags & NTFS_EA_UID) {
+ /* Store uid to lxuid EA */
+ v = cpu_to_le32(i_uid_read(inode));
+ err = ntfs_set_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v,
+ sizeof(v), 0, ea_size);
+ if (err)
+ return err;
+ }
+
+ if (flags & NTFS_EA_GID) {
+ /* Store gid to lxgid EA */
+ v = cpu_to_le32(i_gid_read(inode));
+ err = ntfs_set_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v,
+ sizeof(v), 0, ea_size);
+ if (err)
+ return err;
+ }
+
+ if (flags & NTFS_EA_MODE) {
+ /* Store mode to lxmod EA */
+ v = cpu_to_le32(inode->i_mode);
+ err = ntfs_set_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v,
+ sizeof(v), 0, ea_size);
+ if (err)
+ return err;
+ }
+
+ if (rdev) {
+ v = cpu_to_le32(rdev);
+ err = ntfs_set_ea(inode, "$LXDEV", sizeof("$LXDEV") - 1, &v, sizeof(v),
+ 0, ea_size);
+ }
+
+ return err;
+}
+
+ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
+{
+ struct inode *inode = d_inode(dentry);
+ struct ntfs_inode *ni = NTFS_I(inode);
+ const struct ea_attr *p_ea;
+ s64 offset, ea_buf_size, ea_info_size;
+ s64 ea_size;
+ u32 next;
+ int err = 0;
+ u32 ea_info_qsize;
+ char *ea_buf = NULL;
+ ssize_t ret = 0;
+ struct ea_information *ea_info;
+
+ if (!NInoHasEA(ni))
+ return 0;
+
+ mutex_lock(&NTFS_I(inode)->mrec_lock);
+ ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
+ &ea_info_size);
+ 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 (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;
+
+ 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);
+
+ if (ea_size < sizeof(struct ea_attr) ||
+ offset + ea_size > ea_info_qsize) {
+ err = -EIO;
+ goto out;
+ }
+
+ if ((int)p_ea->ea_name_length + 1 >
+ ea_size - offsetof(struct ea_attr, ea_name)) {
+ err = -EIO;
+ goto out;
+ }
+
+ if (buffer) {
+ if (ret + p_ea->ea_name_length + 1 > size) {
+ err = -ERANGE;
+ goto out;
+ }
+
+ memcpy(buffer + ret, p_ea->ea_name, p_ea->ea_name_length);
+ buffer[ret + p_ea->ea_name_length] = 0;
+ }
+
+ ret += p_ea->ea_name_length + 1;
+ offset += ea_size;
+ } while (next > 0 && offset < ea_info_qsize);
+
+out:
+ mutex_unlock(&NTFS_I(inode)->mrec_lock);
+ kvfree(ea_info);
+ kvfree(ea_buf);
+
+ return err ? err : ret;
+}
+
+// clang-format off
+#define SYSTEM_DOS_ATTRIB "system.dos_attrib"
+#define SYSTEM_NTFS_ATTRIB "system.ntfs_attrib"
+#define SYSTEM_NTFS_ATTRIB_BE "system.ntfs_attrib_be"
+// clang-format on
+
+static int ntfs_getxattr(const struct xattr_handler *handler,
+ struct dentry *unused, struct inode *inode, const char *name,
+ void *buffer, size_t size)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ int err;
+
+ if (NVolShutdown(ni->vol))
+ return -EIO;
+
+ if (!strcmp(name, SYSTEM_DOS_ATTRIB)) {
+ if (!buffer) {
+ err = sizeof(u8);
+ } else if (size < sizeof(u8)) {
+ err = -ERANGE;
+ } else {
+ err = sizeof(u8);
+ *(u8 *)buffer = (u8)(le32_to_cpu(ni->flags) & 0x3F);
+ }
+ goto out;
+ }
+
+ if (!strcmp(name, SYSTEM_NTFS_ATTRIB) ||
+ !strcmp(name, SYSTEM_NTFS_ATTRIB_BE)) {
+ if (!buffer) {
+ err = sizeof(u32);
+ } else if (size < sizeof(u32)) {
+ err = -ERANGE;
+ } else {
+ err = sizeof(u32);
+ *(u32 *)buffer = le32_to_cpu(ni->flags);
+ if (!strcmp(name, SYSTEM_NTFS_ATTRIB_BE))
+ *(__be32 *)buffer = cpu_to_be32(*(u32 *)buffer);
+ }
+ goto out;
+ }
+
+ mutex_lock(&ni->mrec_lock);
+ err = ntfs_get_ea(inode, name, strlen(name), buffer, size);
+ mutex_unlock(&ni->mrec_lock);
+
+out:
+ return err;
+}
+
+static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
+{
+ struct ntfs_attr_search_ctx *ctx;
+ struct mft_record *m;
+ struct attr_record *a;
+ __le16 new_aflags;
+ 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))
+ return PTR_ERR(m);
+
+ ctx = ntfs_attr_get_search_ctx(ni, m);
+ if (!ctx) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ 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;
+ new_aflags = ctx->attr->flags;
+
+ if (fattr & FILE_ATTR_SPARSE_FILE)
+ new_aflags |= ATTR_IS_SPARSE;
+ else
+ new_aflags &= ~ATTR_IS_SPARSE;
+
+ if (fattr & FILE_ATTR_COMPRESSED)
+ new_aflags |= ATTR_IS_COMPRESSED;
+ else
+ new_aflags &= ~ATTR_IS_COMPRESSED;
+
+ 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)) {
+ pr_err("file can't be sparsed and compressed\n");
+ err = -EOPNOTSUPP;
+ goto err_out;
+ }
+
+ if (!a->non_resident) {
+ if (!(new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))) {
+ err = 0;
+ 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) +
+ sizeof(a->data.non_resident.compressed_size) + 7) & ~7;
+ else
+ name_ofs = (offsetof(struct attr_record,
+ data.non_resident.compressed_size) + 7) & ~7;
+
+ mp_size = ntfs_get_size_for_mapping_pairs(ni->vol, ni->runlist.rl, 0, -1, -1);
+ if (unlikely(mp_size < 0)) {
+ err = mp_size;
+ ntfs_debug("Failed to get size for mapping pairs array, error code %i.\n", err);
+ goto err_out;
+ }
+
+ 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(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)
+ a->data.non_resident.compression_unit = 4;
+ a->data.non_resident.compressed_size = 0;
+ ni->itype.compressed.size = 0;
+ if (a->data.non_resident.compression_unit) {
+ ni->itype.compressed.block_size = 1U <<
+ (a->data.non_resident.compression_unit +
+ ni->vol->cluster_size_bits);
+ ni->itype.compressed.block_size_bits =
+ ffs(ni->itype.compressed.block_size) -
+ 1;
+ ni->itype.compressed.block_clusters = 1U <<
+ a->data.non_resident.compression_unit;
+ } else {
+ ni->itype.compressed.block_size = 0;
+ ni->itype.compressed.block_size_bits = 0;
+ ni->itype.compressed.block_clusters = 0;
+ }
+ } else {
+ a->data.non_resident.compression_unit = 0;
+ }
+
+ a->name_offset = cpu_to_le16(name_ofs);
+ a->data.non_resident.mapping_pairs_offset = cpu_to_le16(mp_ofs);
+
+ 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)
+ ntfs_attr_put_search_ctx(ctx);
+ unmap_mft_record(ni);
+ 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,
+ size_t size, int flags)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ int err;
+ __le32 fattr;
+
+ 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((le32_to_cpu(ni->flags) & ~0xffU) |
+ *(u8 *)value);
+ goto set_fattr;
+ }
+
+ if (!strcmp(name, SYSTEM_NTFS_ATTRIB) ||
+ !strcmp(name, SYSTEM_NTFS_ATTRIB_BE)) {
+ if (size != sizeof(u32)) {
+ err = -EINVAL;
+ goto out;
+ }
+ if (!strcmp(name, SYSTEM_NTFS_ATTRIB_BE))
+ fattr = cpu_to_le32(be32_to_cpu(*(__be32 *)value));
+ 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);
+ mutex_unlock(&ni->mrec_lock);
+ if (err)
+ goto out;
+ }
+
+set_fattr:
+ if (S_ISDIR(inode->i_mode))
+ fattr |= FILE_ATTR_DIRECTORY;
+ 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)
+ inode->i_mode &= ~0222;
+ else
+ inode->i_mode |= 0222;
+ NInoSetFileNameDirty(ni);
+ mark_inode_dirty(inode);
+ }
+ err = 0;
+ goto out;
+ }
+
+ mutex_lock(&ni->mrec_lock);
+ err = ntfs_set_ea(inode, name, strlen(name), value, size, flags, NULL);
+ mutex_unlock(&ni->mrec_lock);
+
+out:
+ if (!err) {
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+ }
+ return err;
+}
+
+static bool ntfs_xattr_user_list(struct dentry *dentry)
+{
+ return true;
+}
+
+// clang-format off
+static const struct xattr_handler ntfs_other_xattr_handler = {
+ .prefix = "",
+ .get = ntfs_getxattr,
+ .set = ntfs_setxattr,
+ .list = ntfs_xattr_user_list,
+};
+
+const struct xattr_handler * const ntfs_xattr_handlers[] = {
+ &ntfs_other_xattr_handler,
+ NULL,
+};
+// clang-format on
+
+#ifdef CONFIG_NTFS_FS_POSIX_ACL
+struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry,
+ int type)
+{
+ struct inode *inode = d_inode(dentry);
+ struct ntfs_inode *ni = NTFS_I(inode);
+ const char *name;
+ size_t name_len;
+ struct posix_acl *acl;
+ int err;
+ void *buf;
+
+ buf = kmalloc(PATH_MAX, GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+ /* Possible values of 'type' was already checked above. */
+ if (type == ACL_TYPE_ACCESS) {
+ name = XATTR_NAME_POSIX_ACL_ACCESS;
+ name_len = sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1;
+ } else {
+ name = XATTR_NAME_POSIX_ACL_DEFAULT;
+ name_len = sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1;
+ }
+
+ mutex_lock(&ni->mrec_lock);
+ err = ntfs_get_ea(inode, name, name_len, buf, PATH_MAX);
+ mutex_unlock(&ni->mrec_lock);
+
+ /* Translate extended attribute to acl. */
+ if (err >= 0)
+ acl = posix_acl_from_xattr(&init_user_ns, buf, err);
+ else if (err == -ENODATA)
+ acl = NULL;
+ else
+ acl = ERR_PTR(err);
+
+ if (!IS_ERR(acl))
+ set_cached_acl(inode, type, acl);
+
+ kfree(buf);
+
+ return acl;
+}
+
+static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap,
+ struct inode *inode, struct posix_acl *acl,
+ int type, bool init_acl)
+{
+ const char *name;
+ size_t size, name_len;
+ void *value;
+ int err;
+ int flags;
+ umode_t mode;
+
+ if (S_ISLNK(inode->i_mode))
+ return -EOPNOTSUPP;
+
+ mode = inode->i_mode;
+ switch (type) {
+ case ACL_TYPE_ACCESS:
+ /* Do not change i_mode if we are in init_acl */
+ if (acl && !init_acl) {
+ err = posix_acl_update_mode(idmap, inode, &mode, &acl);
+ if (err)
+ return err;
+ }
+ name = XATTR_NAME_POSIX_ACL_ACCESS;
+ name_len = sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1;
+ break;
+
+ case ACL_TYPE_DEFAULT:
+ if (!S_ISDIR(inode->i_mode))
+ return acl ? -EACCES : 0;
+ name = XATTR_NAME_POSIX_ACL_DEFAULT;
+ name_len = sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (!acl) {
+ /* Remove xattr if it can be presented via mode. */
+ size = 0;
+ value = NULL;
+ flags = XATTR_REPLACE;
+ } else {
+ value = posix_acl_to_xattr(&init_user_ns, acl, &size, GFP_NOFS);
+ if (!value)
+ return -ENOMEM;
+ flags = 0;
+ }
+
+ mutex_lock(&NTFS_I(inode)->mrec_lock);
+ err = ntfs_set_ea(inode, name, name_len, value, size, flags, NULL);
+ mutex_unlock(&NTFS_I(inode)->mrec_lock);
+ if (err == -ENODATA && !size)
+ err = 0; /* Removing non existed xattr. */
+ if (!err) {
+ __le16 ea_size = 0;
+ umode_t old_mode = inode->i_mode;
+
+ inode->i_mode = mode;
+ mutex_lock(&NTFS_I(inode)->mrec_lock);
+ err = ntfs_ea_set_wsl_inode(inode, 0, &ea_size, NTFS_EA_MODE);
+ if (err) {
+ ntfs_set_ea(inode, name, name_len, NULL, 0,
+ XATTR_REPLACE, NULL);
+ mutex_unlock(&NTFS_I(inode)->mrec_lock);
+ inode->i_mode = old_mode;
+ goto out;
+ }
+ mutex_unlock(&NTFS_I(inode)->mrec_lock);
+
+ set_cached_acl(inode, type, acl);
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+ }
+
+out:
+ kfree(value);
+
+ return err;
+}
+
+int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct posix_acl *acl, int type)
+{
+ return ntfs_set_acl_ex(idmap, d_inode(dentry), acl, type, false);
+}
+
+int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode,
+ struct inode *dir)
+{
+ struct posix_acl *default_acl, *acl;
+ int err;
+
+ err = posix_acl_create(dir, &inode->i_mode, &default_acl, &acl);
+ if (err)
+ return err;
+
+ if (default_acl) {
+ err = ntfs_set_acl_ex(idmap, inode, default_acl,
+ ACL_TYPE_DEFAULT, true);
+ posix_acl_release(default_acl);
+ } else {
+ inode->i_default_acl = NULL;
+ }
+
+ if (acl) {
+ if (!err)
+ err = ntfs_set_acl_ex(idmap, inode, acl,
+ ACL_TYPE_ACCESS, true);
+ posix_acl_release(acl);
+ } else {
+ inode->i_acl = NULL;
+ }
+
+ return err;
+}
+#endif