root/fs/ntfs/ea.c
// 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 (!p_ea_info || ea_info_size != sizeof(struct ea_information)) {
                kvfree(p_ea_info);
                return -ENODATA;
        }

        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 (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;
        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 (!p_ea_info || ea_info_size != sizeof(struct ea_information))
                        goto out;

                ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size);
                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(sizeof(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;
                }

                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);

                err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
                                sizeof(struct ea_information), false);
                if (err)
                        goto out;

                err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true);
                if (err)
                        goto out;

                if ((flags & XATTR_REPLACE) && !val_size) {
                        /* Remove xattr. */
                        goto out;
                }
        } else {
                if (flags & XATTR_REPLACE) {
                        err = -ENODATA;
                        goto out;
                }
        }
        kvfree(ea_buf);

alloc_new_ea:
        ea_buf = kzalloc(new_ea_size, GFP_NOFS);
        if (!ea_buf) {
                err = -ENOMEM;
                goto out;
        }

        /*
         * 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;
        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 ||
            ntfs_attr_size_bounds_check(ni->vol, AT_EA, new_ea_size)) {
                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, (char *)p_ea,
                                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);
                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 (ea_info_qsize > 0)
                NInoSetHasEA(ni);
        else
                NInoClearHasEA(ni);

        kvfree(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)
{
        int err;
        __le32 v;

        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 (!(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));
        }

        /* 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);
        } 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 (!ea_info || ea_info_size != sizeof(struct ea_information))
                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)
                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 = -ENODATA;
                } 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 = -ENODATA;
                } 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;
        int mp_size, mp_ofs, name_ofs, 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)
                return 0;

        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)
                goto 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 (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;

        err = ntfs_attr_record_resize(m, a, arec_size);
        if (unlikely(err))
                goto err_out;

        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;
                }

                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;
        mark_mft_record_dirty(ctx->ntfs_ino);
err_out:
        if (ctx)
                ntfs_attr_put_search_ctx(ctx);
        unmap_mft_record(ni);
        return err;
}

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 (!strcmp(name, SYSTEM_DOS_ATTRIB)) {
                if (sizeof(u8) != size) {
                        err = -EINVAL;
                        goto out;
                }
                fattr = cpu_to_le32(*(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);

                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;

                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:
        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