// SPDX-License-Identifier: GPL-2.0-or-later /* * Attribute list attribute handling code. * Part of this file is based on code from the NTFS-3G. * * Copyright (c) 2004-2005 Anton Altaparmakov * Copyright (c) 2004-2005 Yura Pakhuchiy * Copyright (c) 2006 Szabolcs Szakacsits * Copyright (c) 2025 LG Electronics Co., Ltd. */ #include "mft.h" #include "attrib.h" #include "attrlist.h" /* * ntfs_attrlist_need - check whether inode need attribute list * @ni: opened ntfs inode for which perform check * * Check whether all are attributes belong to one MFT record, in that case * attribute list is not needed. * * Return 1 if inode need attribute list, 0 if not, or -errno on error. */ int ntfs_attrlist_need(struct ntfs_inode *ni) { struct attr_list_entry *ale; if (!ni) { ntfs_debug("Invalid arguments.\n"); return -EINVAL; } ntfs_debug("Entering for inode 0x%llx.\n", (long long) ni->mft_no); if (!NInoAttrList(ni)) { ntfs_debug("Inode haven't got attribute list.\n"); return -EINVAL; } if (!ni->attr_list) { ntfs_debug("Corrupt in-memory struct.\n"); return -EINVAL; } ale = (struct attr_list_entry *)ni->attr_list; while ((u8 *)ale < ni->attr_list + ni->attr_list_size) { if (MREF_LE(ale->mft_reference) != ni->mft_no) return 1; ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length)); } return 0; } int ntfs_attrlist_update(struct ntfs_inode *base_ni) { struct inode *attr_vi; struct ntfs_inode *attr_ni; int err; attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0); if (IS_ERR(attr_vi)) { err = PTR_ERR(attr_vi); return err; } attr_ni = NTFS_I(attr_vi); err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO); if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) { err = ntfs_attr_truncate(attr_ni, 0); if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) { iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to truncate attribute list of inode %#llx", (long long)base_ni->mft_no); return -EIO; } } else if (err) { iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to truncate attribute list of inode %#llx", (long long)base_ni->mft_no); return -EIO; } i_size_write(attr_vi, base_ni->attr_list_size); if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni)) NInoSetAttrListNonResident(base_ni); if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size, base_ni->attr_list, false) != base_ni->attr_list_size) { iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to write attribute list of inode %#llx", (long long)base_ni->mft_no); return -EIO; } NInoSetAttrListDirty(base_ni); iput(attr_vi); return 0; } /* * ntfs_attrlist_entry_add - add an attribute list attribute entry * @ni: opened ntfs inode, which contains that attribute * @attr: attribute record to add to attribute list * * Return 0 on success and -errno on error. */ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr) { struct attr_list_entry *ale; __le64 mref; struct ntfs_attr_search_ctx *ctx; u8 *new_al; int entry_len, entry_offset, err; struct mft_record *ni_mrec; u8 *old_al; ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n", (long long) ni->mft_no, (unsigned int) le32_to_cpu(attr->type)); if (!ni || !attr) { ntfs_debug("Invalid arguments.\n"); return -EINVAL; } ni_mrec = map_mft_record(ni); if (IS_ERR(ni_mrec)) { ntfs_debug("Invalid arguments.\n"); return -EIO; } mref = MK_LE_MREF(ni->mft_no, le16_to_cpu(ni_mrec->sequence_number)); unmap_mft_record(ni); if (ni->nr_extents == -1) ni = ni->ext.base_ntfs_ino; if (!NInoAttrList(ni)) { ntfs_debug("Attribute list isn't present.\n"); return -ENOENT; } /* Determine size and allocate memory for new attribute list. */ entry_len = (sizeof(struct attr_list_entry) + sizeof(__le16) * attr->name_length + 7) & ~7; new_al = kvzalloc(ni->attr_list_size + entry_len, GFP_NOFS); if (!new_al) return -ENOMEM; /* Find place for the new entry. */ ctx = ntfs_attr_get_search_ctx(ni, NULL); if (!ctx) { err = -ENOMEM; ntfs_error(ni->vol->sb, "Failed to get search context"); goto err_out; } err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *) ((u8 *)attr + le16_to_cpu(attr->name_offset)) : AT_UNNAMED, attr->name_length, CASE_SENSITIVE, (attr->non_resident) ? le64_to_cpu(attr->data.non_resident.lowest_vcn) : 0, (attr->non_resident) ? NULL : ((u8 *)attr + le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ? 0 : le32_to_cpu(attr->data.resident.value_length), ctx); if (!err) { /* Found some extent, check it to be before new extent. */ if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) { err = -EEXIST; ntfs_debug("Such attribute already present in the attribute list.\n"); ntfs_attr_put_search_ctx(ctx); goto err_out; } /* Add new entry after this extent. */ ale = (struct attr_list_entry *)((u8 *)ctx->al_entry + le16_to_cpu(ctx->al_entry->length)); } else { /* Check for real errors. */ if (err != -ENOENT) { ntfs_debug("Attribute lookup failed.\n"); ntfs_attr_put_search_ctx(ctx); goto err_out; } /* No previous extents found. */ ale = ctx->al_entry; } /* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */ ntfs_attr_put_search_ctx(ctx); /* Determine new entry offset. */ entry_offset = ((u8 *)ale - ni->attr_list); /* Set pointer to new entry. */ ale = (struct attr_list_entry *)(new_al + entry_offset); memset(ale, 0, entry_len); /* Form new entry. */ ale->type = attr->type; ale->length = cpu_to_le16(entry_len); ale->name_length = attr->name_length; ale->name_offset = offsetof(struct attr_list_entry, name); if (attr->non_resident) ale->lowest_vcn = attr->data.non_resident.lowest_vcn; else ale->lowest_vcn = 0; ale->mft_reference = mref; ale->instance = attr->instance; memcpy(ale->name, (u8 *)attr + le16_to_cpu(attr->name_offset), attr->name_length * sizeof(__le16)); /* Copy entries from old attribute list to new. */ memcpy(new_al, ni->attr_list, entry_offset); memcpy(new_al + entry_offset + entry_len, ni->attr_list + entry_offset, ni->attr_list_size - entry_offset); /* Set new runlist. */ old_al = ni->attr_list; ni->attr_list = new_al; ni->attr_list_size = ni->attr_list_size + entry_len; err = ntfs_attrlist_update(ni); if (err) { ni->attr_list = old_al; ni->attr_list_size -= entry_len; goto err_out; } kvfree(old_al); return 0; err_out: kvfree(new_al); return err; } /* * ntfs_attrlist_entry_rm - remove an attribute list attribute entry * @ctx: attribute search context describing the attribute list entry * * Remove the attribute list entry @ctx->al_entry from the attribute list. * * Return 0 on success and -errno on error. */ int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx) { u8 *new_al; int new_al_len; struct ntfs_inode *base_ni; struct attr_list_entry *ale; if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) { ntfs_debug("Invalid arguments.\n"); return -EINVAL; } if (ctx->base_ntfs_ino) base_ni = ctx->base_ntfs_ino; else base_ni = ctx->ntfs_ino; ale = ctx->al_entry; ntfs_debug("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld.\n", (long long)ctx->ntfs_ino->mft_no, (unsigned int)le32_to_cpu(ctx->al_entry->type), (long long)le64_to_cpu(ctx->al_entry->lowest_vcn)); if (!NInoAttrList(base_ni)) { ntfs_debug("Attribute list isn't present.\n"); return -ENOENT; } /* Allocate memory for new attribute list. */ new_al_len = base_ni->attr_list_size - le16_to_cpu(ale->length); new_al = kvzalloc(new_al_len, GFP_NOFS); if (!new_al) return -ENOMEM; /* Copy entries from old attribute list to new. */ memcpy(new_al, base_ni->attr_list, (u8 *)ale - base_ni->attr_list); memcpy(new_al + ((u8 *)ale - base_ni->attr_list), (u8 *)ale + le16_to_cpu( ale->length), new_al_len - ((u8 *)ale - base_ni->attr_list)); /* Set new runlist. */ kvfree(base_ni->attr_list); base_ni->attr_list = new_al; base_ni->attr_list_size = new_al_len; return ntfs_attrlist_update(base_ni); }