// 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 #include #include #include #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; if (ea_buf_size < sizeof(struct ea_attr)) goto out; offset = 0; do { 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; 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; if (ea_info_qsize < sizeof(struct ea_attr)) { err = -EIO; goto out; } offset = 0; do { 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