xref: /linux/fs/ntfs/attrlist.c (revision bfda5a01aa99c5c363ac9967b81cbd5902d6c940)
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 #include "lcnalloc.h"
16 
17 #define NTFS_MAX_ATTR_LIST_SIZE	(256 * 1024)
18 
19 /*
20  * ntfs_attrlist_need - check whether inode need attribute list
21  * @ni:	opened ntfs inode for which perform check
22  *
23  * Check whether all are attributes belong to one MFT record, in that case
24  * attribute list is not needed.
25  *
26  * Return 1 if inode need attribute list, 0 if not, or -errno on error.
27  */
ntfs_attrlist_need(struct ntfs_inode * ni)28 int ntfs_attrlist_need(struct ntfs_inode *ni)
29 {
30 	struct attr_list_entry *ale;
31 
32 	if (!ni) {
33 		ntfs_debug("Invalid arguments.\n");
34 		return -EINVAL;
35 	}
36 	ntfs_debug("Entering for inode 0x%llx.\n", (long long) ni->mft_no);
37 
38 	if (!NInoAttrList(ni)) {
39 		ntfs_debug("Inode haven't got attribute list.\n");
40 		return -EINVAL;
41 	}
42 
43 	if (!ni->attr_list) {
44 		ntfs_debug("Corrupt in-memory struct.\n");
45 		return -EINVAL;
46 	}
47 
48 	ale = (struct attr_list_entry *)ni->attr_list;
49 	while ((u8 *)ale < ni->attr_list + ni->attr_list_size) {
50 		if (MREF_LE(ale->mft_reference) != ni->mft_no)
51 			return 1;
52 		ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length));
53 	}
54 	return 0;
55 }
56 
57 /*
58  * Repack the $MFT/$ATTRIBUTE_LIST data into one run.
59  *
60  * The mapping pairs for an $ATTRIBUTE_LIST must remain in the base MFT
61  * record. Once that record has no room left, extending a fragmented list
62  * can require one more mapping-pairs byte than the record can hold. There
63  * is no attribute that can legally be moved out in that state: $STANDARD_
64  * INFORMATION, $ATTRIBUTE_LIST, and the first $MFT/$DATA extent all have to
65  * stay in the base record.  Move the list data to one contiguous run. The
66  * caller supplies the minimum allocation size so a recovery can use the
67  * smallest useful run while normal updates can still request the maximum
68  * legal list size as a reserve.
69  */
ntfs_attrlist_repack(struct inode * attr_vi,struct ntfs_inode * attr_ni,s64 min_alloc_size,struct ntfs_inode * locked_ni)70 static int ntfs_attrlist_repack(struct inode *attr_vi,
71 		struct ntfs_inode *attr_ni, s64 min_alloc_size,
72 		struct ntfs_inode *locked_ni)
73 {
74 	struct ntfs_volume *vol = attr_ni->vol;
75 	struct runlist_element *old_rl, *new_rl;
76 	u8 *data = NULL;
77 	s64 data_size, alloc_size, nr_clusters, written;
78 	s64 old_alloc_size;
79 	size_t old_rl_count, new_rl_count;
80 	unsigned long flags;
81 	int err, restore_err;
82 	if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) ||
83 		min_alloc_size < 0)
84 		return -EINVAL;
85 	/* The buffered I/O below can reacquire the attribute runlist lock. */
86 	if (attr_ni == locked_ni)
87 		return -ENOSPC;
88 
89 	err = ntfs_attr_map_whole_runlist(attr_ni);
90 	if (err)
91 		return err;
92 
93 	data_size = attr_ni->data_size;
94 	if (data_size < 0)
95 		return -EIO;
96 
97 	if (data_size) {
98 		data = kvmalloc(data_size, GFP_NOFS);
99 		if (!data)
100 			return -ENOMEM;
101 
102 		written = ntfs_inode_attr_pread(attr_vi, 0, data_size, data);
103 		if (written != data_size) {
104 			err = written < 0 ? (int)written : -EIO;
105 			goto out_free_data;
106 		}
107 	}
108 
109 	old_alloc_size = attr_ni->allocated_size;
110 	alloc_size = max_t(s64, old_alloc_size, min_alloc_size);
111 	nr_clusters = ntfs_bytes_to_cluster(vol,
112 			alloc_size + vol->cluster_size - 1);
113 	if (nr_clusters <= 0) {
114 		err = -EFBIG;
115 		goto out_free_data;
116 	}
117 
118 	/* A single run keeps the mapping pairs at the minimum size. */
119 	new_rl = ntfs_cluster_alloc(vol, 0, nr_clusters, -1, DATA_ZONE,
120 				    true, true, false);
121 	if (IS_ERR(new_rl)) {
122 		err = PTR_ERR(new_rl);
123 		goto out_free_data;
124 	}
125 
126 	new_rl_count = 0;
127 	if (new_rl->vcn == 0 && new_rl->length == nr_clusters &&
128 		!new_rl[1].length)
129 		new_rl_count = 2;
130 
131 	if (new_rl_count != 2) {
132 		ntfs_cluster_free_from_rl(vol, new_rl);
133 		kvfree(new_rl);
134 		err = -ENOSPC;
135 		goto out_free_data;
136 	}
137 	old_rl = attr_ni->runlist.rl;
138 	old_rl_count = attr_ni->runlist.count;
139 	down_write(&attr_ni->runlist.lock);
140 	attr_ni->runlist.rl = new_rl;
141 	attr_ni->runlist.count = new_rl_count;
142 	up_write(&attr_ni->runlist.lock);
143 
144 	write_lock_irqsave(&attr_ni->size_lock, flags);
145 	attr_ni->allocated_size = ntfs_cluster_to_bytes(vol, nr_clusters);
146 	write_unlock_irqrestore(&attr_ni->size_lock, flags);
147 
148 	/* Populate the replacement extent before publishing its mapping pairs. */
149 	if (data_size) {
150 		written = ntfs_inode_attr_pwrite(attr_vi, 0, data_size, data, true);
151 		if (written != data_size) {
152 			err = written < 0 ? (int)written : -EIO;
153 			goto restore_old_runlist;
154 		}
155 	}
156 
157 	err = ntfs_attr_update_mapping_pairs_locked(attr_ni, 0, locked_ni);
158 	if (err)
159 		goto restore_old_runlist;
160 
161 	/* The new mapping is now authoritative; release the old data runs. */
162 	if (ntfs_cluster_free_from_rl(vol, old_rl)) {
163 		ntfs_error(vol->sb,
164 			   "Failed to free old ATTRIBUTE_LIST extent: inode %#llx",
165 			   (long long)attr_ni->mft_no);
166 		NVolSetErrors(vol);
167 	}
168 	kvfree(old_rl);
169 	kvfree(data);
170 	return 0;
171 
172 restore_old_runlist:
173 	down_write(&attr_ni->runlist.lock);
174 	attr_ni->runlist.rl = old_rl;
175 	attr_ni->runlist.count = old_rl_count;
176 	up_write(&attr_ni->runlist.lock);
177 
178 	write_lock_irqsave(&attr_ni->size_lock, flags);
179 	attr_ni->allocated_size = old_alloc_size;
180 	write_unlock_irqrestore(&attr_ni->size_lock, flags);
181 
182 	restore_err = ntfs_attr_update_mapping_pairs_locked(
183 			attr_ni, 0, locked_ni);
184 	if (restore_err) {
185 		ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)",
186 			   restore_err);
187 		NVolSetErrors(vol);
188 	}
189 
190 	ntfs_cluster_free_from_rl(vol, new_rl);
191 	kvfree(new_rl);
192 	err = err ? err : restore_err;
193 
194 out_free_data:
195 	kvfree(data);
196 	return err;
197 }
198 
ntfs_attrlist_update_locked(struct ntfs_inode * base_ni,struct ntfs_inode * locked_ni)199 int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni,
200 				struct ntfs_inode *locked_ni)
201 {
202 	struct inode *attr_vi;
203 	struct ntfs_inode *attr_ni;
204 	s64 written;
205 	int err, retry_err;
206 
207 	/*
208 	 * generic_shutdown_super() clears SB_ACTIVE before evicting cached
209 	 * inodes. Do not look up the attribute-list inode after SB_ACTIVE has
210 	 * been cleared; it may already be I_FREEING, and waiting on it can
211 	 * self-deadlock.
212 	 */
213 	if (!(VFS_I(base_ni)->i_sb->s_flags & SB_ACTIVE))
214 		return -EIO;
215 
216 	attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0);
217 	if (IS_ERR(attr_vi)) {
218 		err = PTR_ERR(attr_vi);
219 		return err;
220 	}
221 	attr_ni = NTFS_I(attr_vi);
222 	/* Truncation and page-cache writes can reacquire this runlist lock. */
223 	if (attr_ni == locked_ni) {
224 		iput(attr_vi);
225 		return -ENOSPC;
226 	}
227 
228 	err = ntfs_attr_truncate_i_locked(
229 			attr_ni, base_ni->attr_list_size, HOLES_NO, locked_ni);
230 	if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT &&
231 		NInoNonResident(attr_ni)) {
232 		retry_err = ntfs_attrlist_repack(attr_vi, attr_ni,
233 					base_ni->attr_list_size, locked_ni);
234 		if (retry_err) {
235 			ntfs_error(base_ni->vol->sb, "Failed to repack attribute list");
236 			iput(attr_vi);
237 			return retry_err;
238 		}
239 
240 		retry_err = ntfs_attr_truncate_i_locked(
241 				attr_ni, base_ni->attr_list_size,
242 				HOLES_NO, locked_ni);
243 		if (retry_err) {
244 			ntfs_error(base_ni->vol->sb,
245 				   "Failed to resize attribute list after repack");
246 			iput(attr_vi);
247 			return retry_err;
248 		}
249 	} else if (err) {
250 		iput(attr_vi);
251 		ntfs_error(base_ni->vol->sb,
252 			   "Failed to truncate attribute list of inode %#llx",
253 			   (long long)base_ni->mft_no);
254 		return err;
255 	}
256 
257 	/*
258 	 * Reserve the maximum legal list size while the MFT metadata area is
259 	 * still easy to allocate contiguously. This prevents a later list entry
260 	 * from needing another mapping-pairs byte in the full base MFT record.
261 	 * Failure to obtain the optional reserve must not reject the current
262 	 * metadata update; the repack retry above remains available if needed.
263 	 */
264 	if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) &&
265 		attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) {
266 		retry_err = ntfs_attr_expand_locked(
267 				attr_ni, base_ni->attr_list_size,
268 				NTFS_MAX_ATTR_LIST_SIZE, locked_ni);
269 		if (retry_err == -ENOSPC) {
270 			retry_err = ntfs_attrlist_repack(
271 					attr_vi, attr_ni,
272 					NTFS_MAX_ATTR_LIST_SIZE, locked_ni);
273 			if (retry_err == -ENOSPC)
274 				retry_err = 0;
275 		}
276 		if (retry_err) {
277 			ntfs_error(base_ni->vol->sb,
278 				   "Failed to reserve attribute list space");
279 			iput(attr_vi);
280 			return retry_err;
281 		}
282 	}
283 
284 	i_size_write(attr_vi, base_ni->attr_list_size);
285 
286 	if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
287 		NInoSetAttrListNonResident(base_ni);
288 
289 	written = ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
290 					 base_ni->attr_list, false);
291 	if (written != base_ni->attr_list_size) {
292 		err = written < 0 ? (int)written : -EIO;
293 		iput(attr_vi);
294 		ntfs_error(base_ni->vol->sb,
295 			   "Failed to write attribute list of inode %#llx",
296 			   (long long)base_ni->mft_no);
297 		return err;
298 	}
299 
300 	NInoSetAttrListDirty(base_ni);
301 	iput(attr_vi);
302 	return 0;
303 }
304 
ntfs_attrlist_update(struct ntfs_inode * base_ni)305 int ntfs_attrlist_update(struct ntfs_inode *base_ni)
306 {
307 	return ntfs_attrlist_update_locked(base_ni, NULL);
308 }
309 
310 /*
311  * ntfs_attrlist_entry_add - add an attribute list attribute entry
312  * @ni:	opened ntfs inode, which contains that attribute
313  * @attr: attribute record to add to attribute list
314  *
315  * Return 0 on success and -errno on error.
316  */
ntfs_attrlist_entry_add(struct ntfs_inode * ni,struct attr_record * attr)317 int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
318 {
319 	struct attr_list_entry *ale;
320 	__le64 mref;
321 	struct ntfs_attr_search_ctx *ctx;
322 	u8 *new_al;
323 	int entry_len, entry_offset, err;
324 	struct mft_record *ni_mrec;
325 	u8 *old_al;
326 	__le64 lowest_vcn;
327 
328 	if (!ni || !attr) {
329 		ntfs_debug("Invalid arguments.\n");
330 		return -EINVAL;
331 	}
332 
333 	ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n",
334 			ni->mft_no, (unsigned int) le32_to_cpu(attr->type));
335 
336 	ni_mrec = map_mft_record(ni);
337 	if (IS_ERR(ni_mrec)) {
338 		ntfs_debug("Invalid arguments.\n");
339 		return -EIO;
340 	}
341 
342 	mref = MK_LE_MREF(ni->mft_no, le16_to_cpu(ni_mrec->sequence_number));
343 	unmap_mft_record(ni);
344 
345 	if (ni->nr_extents == -1)
346 		ni = ni->ext.base_ntfs_ino;
347 
348 	if (!NInoAttrList(ni)) {
349 		ntfs_debug("Attribute list isn't present.\n");
350 		return -ENOENT;
351 	}
352 
353 	/* Determine size and allocate memory for new attribute list. */
354 	entry_len = (sizeof(struct attr_list_entry) + sizeof(__le16) *
355 			attr->name_length + 7) & ~7;
356 	new_al = kvzalloc(ni->attr_list_size + entry_len, GFP_NOFS);
357 	if (!new_al)
358 		return -ENOMEM;
359 
360 	/* Find place for the new entry. */
361 	ctx = ntfs_attr_get_search_ctx(ni, NULL);
362 	if (!ctx) {
363 		err = -ENOMEM;
364 		ntfs_error(ni->vol->sb, "Failed to get search context");
365 		goto err_out;
366 	}
367 	if (attr->non_resident)
368 		lowest_vcn = attr->data.non_resident.lowest_vcn;
369 	else
370 		lowest_vcn = 0;
371 
372 	err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *)
373 			((u8 *)attr + le16_to_cpu(attr->name_offset)) :
374 			AT_UNNAMED, attr->name_length, CASE_SENSITIVE,
375 			le64_to_cpu(lowest_vcn),
376 			(attr->non_resident) ? NULL : ((u8 *)attr +
377 			le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ?
378 			0 : le32_to_cpu(attr->data.resident.value_length), ctx);
379 	if (!err) {
380 		/* Found some extent, check it to be before new extent. */
381 		if (ctx->al_entry->lowest_vcn == lowest_vcn) {
382 			err = -EEXIST;
383 			ntfs_debug("Such attribute already present in the attribute list.\n");
384 			ntfs_attr_put_search_ctx(ctx);
385 			goto err_out;
386 		}
387 		/* Add new entry after this extent. */
388 		ale = (struct attr_list_entry *)((u8 *)ctx->al_entry +
389 				le16_to_cpu(ctx->al_entry->length));
390 	} else {
391 		/* Check for real errors. */
392 		if (err != -ENOENT) {
393 			ntfs_debug("Attribute lookup failed.\n");
394 			ntfs_attr_put_search_ctx(ctx);
395 			goto err_out;
396 		}
397 		/* No previous extents found. */
398 		ale = ctx->al_entry;
399 	}
400 	/* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */
401 	ntfs_attr_put_search_ctx(ctx);
402 
403 	/* Determine new entry offset. */
404 	entry_offset = ((u8 *)ale - ni->attr_list);
405 	/* Set pointer to new entry. */
406 	ale = (struct attr_list_entry *)(new_al + entry_offset);
407 	memset(ale, 0, entry_len);
408 	/* Form new entry. */
409 	ale->type = attr->type;
410 	ale->length = cpu_to_le16(entry_len);
411 	ale->name_length = attr->name_length;
412 	ale->name_offset = offsetof(struct attr_list_entry, name);
413 	if (attr->non_resident)
414 		ale->lowest_vcn = attr->data.non_resident.lowest_vcn;
415 	else
416 		ale->lowest_vcn = 0;
417 	ale->mft_reference = mref;
418 	ale->instance = attr->instance;
419 	memcpy(ale->name, (u8 *)attr + le16_to_cpu(attr->name_offset),
420 			attr->name_length * sizeof(__le16));
421 
422 	/* Copy entries from old attribute list to new. */
423 	memcpy(new_al, ni->attr_list, entry_offset);
424 	memcpy(new_al + entry_offset + entry_len, ni->attr_list +
425 			entry_offset, ni->attr_list_size - entry_offset);
426 
427 	/* Set new runlist. */
428 	old_al = ni->attr_list;
429 	ni->attr_list = new_al;
430 	ni->attr_list_size = ni->attr_list_size + entry_len;
431 
432 	err = ntfs_attrlist_update(ni);
433 	if (err) {
434 		ni->attr_list = old_al;
435 		ni->attr_list_size -= entry_len;
436 		goto err_out;
437 	}
438 	kvfree(old_al);
439 	return 0;
440 err_out:
441 	kvfree(new_al);
442 	return err;
443 }
444 
445 /*
446  * ntfs_attrlist_entry_rm - remove an attribute list attribute entry
447  * @ctx:	attribute search context describing the attribute list entry
448  *
449  * Remove the attribute list entry @ctx->al_entry from the attribute list.
450  *
451  * Return 0 on success and -errno on error.
452  */
ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx * ctx)453 int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx)
454 {
455 	u8 *new_al;
456 	int new_al_len;
457 	struct ntfs_inode *base_ni;
458 	struct attr_list_entry *ale;
459 
460 	if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) {
461 		ntfs_debug("Invalid arguments.\n");
462 		return -EINVAL;
463 	}
464 
465 	if (ctx->base_ntfs_ino)
466 		base_ni = ctx->base_ntfs_ino;
467 	else
468 		base_ni = ctx->ntfs_ino;
469 	ale = ctx->al_entry;
470 
471 	ntfs_debug("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld.\n",
472 			(long long)ctx->ntfs_ino->mft_no,
473 			(unsigned int)le32_to_cpu(ctx->al_entry->type),
474 			(long long)le64_to_cpu(ctx->al_entry->lowest_vcn));
475 
476 	if (!NInoAttrList(base_ni)) {
477 		ntfs_debug("Attribute list isn't present.\n");
478 		return -ENOENT;
479 	}
480 
481 	/* Allocate memory for new attribute list. */
482 	new_al_len = base_ni->attr_list_size - le16_to_cpu(ale->length);
483 	new_al = kvzalloc(new_al_len, GFP_NOFS);
484 	if (!new_al)
485 		return -ENOMEM;
486 
487 	/* Copy entries from old attribute list to new. */
488 	memcpy(new_al, base_ni->attr_list, (u8 *)ale - base_ni->attr_list);
489 	memcpy(new_al + ((u8 *)ale - base_ni->attr_list), (u8 *)ale + le16_to_cpu(
490 				ale->length), new_al_len - ((u8 *)ale - base_ni->attr_list));
491 
492 	/* Set new runlist. */
493 	kvfree(base_ni->attr_list);
494 	base_ni->attr_list = new_al;
495 	base_ni->attr_list_size = new_al_len;
496 
497 	return ntfs_attrlist_update(base_ni);
498 }
499