diff options
| author | Kees Cook <kees+treewide@kernel.org> | 2026-09-02 15:31:14 -0700 |
|---|---|---|
| committer | Kees Cook <kees@kernel.org> | 2026-09-04 21:37:00 -0700 |
| commit | 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d (patch) | |
| tree | c65086f9bdcd48c6360fb7cb4598bca084da1f32 /fs/ntfs/ea.c | |
| download | linux-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.c | 1141 |
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 |
