1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Attribute list attribute handling code.
4 * Part of this file is based on code from the NTFS-3G.
5 *
6 * Copyright (c) 2004-2005 Anton Altaparmakov
7 * Copyright (c) 2004-2005 Yura Pakhuchiy
8 * Copyright (c) 2006 Szabolcs Szakacsits
9 * Copyright (c) 2025 LG Electronics Co., Ltd.
10 */
11
12 #include "mft.h"
13 #include "attrib.h"
14 #include "attrlist.h"
15
16 /*
17 * ntfs_attrlist_need - check whether inode need attribute list
18 * @ni: opened ntfs inode for which perform check
19 *
20 * Check whether all are attributes belong to one MFT record, in that case
21 * attribute list is not needed.
22 *
23 * Return 1 if inode need attribute list, 0 if not, or -errno on error.
24 */
ntfs_attrlist_need(struct ntfs_inode * ni)25 int ntfs_attrlist_need(struct ntfs_inode *ni)
26 {
27 struct attr_list_entry *ale;
28
29 if (!ni) {
30 ntfs_debug("Invalid arguments.\n");
31 return -EINVAL;
32 }
33 ntfs_debug("Entering for inode 0x%llx.\n", (long long) ni->mft_no);
34
35 if (!NInoAttrList(ni)) {
36 ntfs_debug("Inode haven't got attribute list.\n");
37 return -EINVAL;
38 }
39
40 if (!ni->attr_list) {
41 ntfs_debug("Corrupt in-memory struct.\n");
42 return -EINVAL;
43 }
44
45 ale = (struct attr_list_entry *)ni->attr_list;
46 while ((u8 *)ale < ni->attr_list + ni->attr_list_size) {
47 if (MREF_LE(ale->mft_reference) != ni->mft_no)
48 return 1;
49 ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length));
50 }
51 return 0;
52 }
53
ntfs_attrlist_update(struct ntfs_inode * base_ni)54 int ntfs_attrlist_update(struct ntfs_inode *base_ni)
55 {
56 struct inode *attr_vi;
57 struct ntfs_inode *attr_ni;
58 int err;
59
60 attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0);
61 if (IS_ERR(attr_vi)) {
62 err = PTR_ERR(attr_vi);
63 return err;
64 }
65 attr_ni = NTFS_I(attr_vi);
66
67 err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO);
68 if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) {
69 err = ntfs_attr_truncate(attr_ni, 0);
70 if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) {
71 iput(attr_vi);
72 ntfs_error(base_ni->vol->sb,
73 "Failed to truncate attribute list of inode %#llx",
74 (long long)base_ni->mft_no);
75 return -EIO;
76 }
77 } else if (err) {
78 iput(attr_vi);
79 ntfs_error(base_ni->vol->sb,
80 "Failed to truncate attribute list of inode %#llx",
81 (long long)base_ni->mft_no);
82 return -EIO;
83 }
84
85 i_size_write(attr_vi, base_ni->attr_list_size);
86
87 if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
88 NInoSetAttrListNonResident(base_ni);
89
90 if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
91 base_ni->attr_list, false) !=
92 base_ni->attr_list_size) {
93 iput(attr_vi);
94 ntfs_error(base_ni->vol->sb,
95 "Failed to write attribute list of inode %#llx",
96 (long long)base_ni->mft_no);
97 return -EIO;
98 }
99
100 NInoSetAttrListDirty(base_ni);
101 iput(attr_vi);
102 return 0;
103 }
104
105 /*
106 * ntfs_attrlist_entry_add - add an attribute list attribute entry
107 * @ni: opened ntfs inode, which contains that attribute
108 * @attr: attribute record to add to attribute list
109 *
110 * Return 0 on success and -errno on error.
111 */
ntfs_attrlist_entry_add(struct ntfs_inode * ni,struct attr_record * attr)112 int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
113 {
114 struct attr_list_entry *ale;
115 __le64 mref;
116 struct ntfs_attr_search_ctx *ctx;
117 u8 *new_al;
118 int entry_len, entry_offset, err;
119 struct mft_record *ni_mrec;
120 u8 *old_al;
121
122 if (!ni || !attr) {
123 ntfs_debug("Invalid arguments.\n");
124 return -EINVAL;
125 }
126
127 ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n",
128 ni->mft_no, (unsigned int) le32_to_cpu(attr->type));
129
130 ni_mrec = map_mft_record(ni);
131 if (IS_ERR(ni_mrec)) {
132 ntfs_debug("Invalid arguments.\n");
133 return -EIO;
134 }
135
136 mref = MK_LE_MREF(ni->mft_no, le16_to_cpu(ni_mrec->sequence_number));
137 unmap_mft_record(ni);
138
139 if (ni->nr_extents == -1)
140 ni = ni->ext.base_ntfs_ino;
141
142 if (!NInoAttrList(ni)) {
143 ntfs_debug("Attribute list isn't present.\n");
144 return -ENOENT;
145 }
146
147 /* Determine size and allocate memory for new attribute list. */
148 entry_len = (sizeof(struct attr_list_entry) + sizeof(__le16) *
149 attr->name_length + 7) & ~7;
150 new_al = kvzalloc(ni->attr_list_size + entry_len, GFP_NOFS);
151 if (!new_al)
152 return -ENOMEM;
153
154 /* Find place for the new entry. */
155 ctx = ntfs_attr_get_search_ctx(ni, NULL);
156 if (!ctx) {
157 err = -ENOMEM;
158 ntfs_error(ni->vol->sb, "Failed to get search context");
159 goto err_out;
160 }
161
162 err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *)
163 ((u8 *)attr + le16_to_cpu(attr->name_offset)) :
164 AT_UNNAMED, attr->name_length, CASE_SENSITIVE,
165 (attr->non_resident) ? le64_to_cpu(attr->data.non_resident.lowest_vcn) :
166 0, (attr->non_resident) ? NULL : ((u8 *)attr +
167 le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ?
168 0 : le32_to_cpu(attr->data.resident.value_length), ctx);
169 if (!err) {
170 /* Found some extent, check it to be before new extent. */
171 if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) {
172 err = -EEXIST;
173 ntfs_debug("Such attribute already present in the attribute list.\n");
174 ntfs_attr_put_search_ctx(ctx);
175 goto err_out;
176 }
177 /* Add new entry after this extent. */
178 ale = (struct attr_list_entry *)((u8 *)ctx->al_entry +
179 le16_to_cpu(ctx->al_entry->length));
180 } else {
181 /* Check for real errors. */
182 if (err != -ENOENT) {
183 ntfs_debug("Attribute lookup failed.\n");
184 ntfs_attr_put_search_ctx(ctx);
185 goto err_out;
186 }
187 /* No previous extents found. */
188 ale = ctx->al_entry;
189 }
190 /* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */
191 ntfs_attr_put_search_ctx(ctx);
192
193 /* Determine new entry offset. */
194 entry_offset = ((u8 *)ale - ni->attr_list);
195 /* Set pointer to new entry. */
196 ale = (struct attr_list_entry *)(new_al + entry_offset);
197 memset(ale, 0, entry_len);
198 /* Form new entry. */
199 ale->type = attr->type;
200 ale->length = cpu_to_le16(entry_len);
201 ale->name_length = attr->name_length;
202 ale->name_offset = offsetof(struct attr_list_entry, name);
203 if (attr->non_resident)
204 ale->lowest_vcn = attr->data.non_resident.lowest_vcn;
205 else
206 ale->lowest_vcn = 0;
207 ale->mft_reference = mref;
208 ale->instance = attr->instance;
209 memcpy(ale->name, (u8 *)attr + le16_to_cpu(attr->name_offset),
210 attr->name_length * sizeof(__le16));
211
212 /* Copy entries from old attribute list to new. */
213 memcpy(new_al, ni->attr_list, entry_offset);
214 memcpy(new_al + entry_offset + entry_len, ni->attr_list +
215 entry_offset, ni->attr_list_size - entry_offset);
216
217 /* Set new runlist. */
218 old_al = ni->attr_list;
219 ni->attr_list = new_al;
220 ni->attr_list_size = ni->attr_list_size + entry_len;
221
222 err = ntfs_attrlist_update(ni);
223 if (err) {
224 ni->attr_list = old_al;
225 ni->attr_list_size -= entry_len;
226 goto err_out;
227 }
228 kvfree(old_al);
229 return 0;
230 err_out:
231 kvfree(new_al);
232 return err;
233 }
234
235 /*
236 * ntfs_attrlist_entry_rm - remove an attribute list attribute entry
237 * @ctx: attribute search context describing the attribute list entry
238 *
239 * Remove the attribute list entry @ctx->al_entry from the attribute list.
240 *
241 * Return 0 on success and -errno on error.
242 */
ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx * ctx)243 int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx)
244 {
245 u8 *new_al;
246 int new_al_len;
247 struct ntfs_inode *base_ni;
248 struct attr_list_entry *ale;
249
250 if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) {
251 ntfs_debug("Invalid arguments.\n");
252 return -EINVAL;
253 }
254
255 if (ctx->base_ntfs_ino)
256 base_ni = ctx->base_ntfs_ino;
257 else
258 base_ni = ctx->ntfs_ino;
259 ale = ctx->al_entry;
260
261 ntfs_debug("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld.\n",
262 (long long)ctx->ntfs_ino->mft_no,
263 (unsigned int)le32_to_cpu(ctx->al_entry->type),
264 (long long)le64_to_cpu(ctx->al_entry->lowest_vcn));
265
266 if (!NInoAttrList(base_ni)) {
267 ntfs_debug("Attribute list isn't present.\n");
268 return -ENOENT;
269 }
270
271 /* Allocate memory for new attribute list. */
272 new_al_len = base_ni->attr_list_size - le16_to_cpu(ale->length);
273 new_al = kvzalloc(new_al_len, GFP_NOFS);
274 if (!new_al)
275 return -ENOMEM;
276
277 /* Copy entries from old attribute list to new. */
278 memcpy(new_al, base_ni->attr_list, (u8 *)ale - base_ni->attr_list);
279 memcpy(new_al + ((u8 *)ale - base_ni->attr_list), (u8 *)ale + le16_to_cpu(
280 ale->length), new_al_len - ((u8 *)ale - base_ni->attr_list));
281
282 /* Set new runlist. */
283 kvfree(base_ni->attr_list);
284 base_ni->attr_list = new_al;
285 base_ni->attr_list_size = new_al_len;
286
287 return ntfs_attrlist_update(base_ni);
288 }
289