xref: /linux/fs/ntfs3/attrlist.c (revision c22760885fd6f7161fabde8e7db63d7f085b125a)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *
4  * Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
5  *
6  */
7 
8 #include <linux/fs.h>
9 
10 #include "debug.h"
11 #include "ntfs.h"
12 #include "ntfs_fs.h"
13 
14 /*
15  * al_is_valid_le
16  *
17  * Return: True if @le is valid.
18  */
19 static inline bool al_is_valid_le(const struct ntfs_inode *ni,
20 				  struct ATTR_LIST_ENTRY *le)
21 {
22 	if (!le || !ni->attr_list.le || !ni->attr_list.size)
23 		return false;
24 
25 	return PtrOffset(ni->attr_list.le, le) + le16_to_cpu(le->size) <=
26 	       ni->attr_list.size;
27 }
28 
29 void al_destroy(struct ntfs_inode *ni)
30 {
31 	run_close(&ni->attr_list.run);
32 	kfree(ni->attr_list.le);
33 	ni->attr_list.le = NULL;
34 	ni->attr_list.size = 0;
35 	ni->attr_list.dirty = false;
36 }
37 
38 /*
39  * ntfs_load_attr_list
40  *
41  * This method makes sure that the ATTRIB list, if present,
42  * has been properly set up.
43  */
44 int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)
45 {
46 	int err;
47 	size_t lsize;
48 	void *le = NULL;
49 
50 	if (ni->attr_list.size)
51 		return 0;
52 
53 	if (!attr->non_res) {
54 		lsize = le32_to_cpu(attr->res.data_size);
55 		le = kmalloc(al_aligned(lsize), GFP_NOFS);
56 		if (!le) {
57 			err = -ENOMEM;
58 			goto out;
59 		}
60 		memcpy(le, resident_data(attr), lsize);
61 	} else if (attr->nres.svcn) {
62 		err = -EINVAL;
63 		goto out;
64 	} else {
65 		u16 run_off = le16_to_cpu(attr->nres.run_off);
66 
67 		lsize = le64_to_cpu(attr->nres.data_size);
68 
69 		run_init(&ni->attr_list.run);
70 
71 		err = run_unpack_ex(&ni->attr_list.run, ni->mi.sbi, ni->mi.rno,
72 				    0, le64_to_cpu(attr->nres.evcn), 0,
73 				    Add2Ptr(attr, run_off),
74 				    le32_to_cpu(attr->size) - run_off);
75 		if (err < 0)
76 			goto out;
77 
78 		le = kmalloc(al_aligned(lsize), GFP_NOFS);
79 		if (!le) {
80 			err = -ENOMEM;
81 			goto out;
82 		}
83 
84 		err = ntfs_read_run_nb(ni->mi.sbi, &ni->attr_list.run, 0, le,
85 				       lsize, NULL);
86 		if (err)
87 			goto out;
88 	}
89 
90 	ni->attr_list.size = lsize;
91 	ni->attr_list.le = le;
92 
93 	return 0;
94 
95 out:
96 	ni->attr_list.le = le;
97 	al_destroy(ni);
98 
99 	return err;
100 }
101 
102 /*
103  * al_enumerate
104  *
105  * Return:
106  * * The next list le.
107  * * If @le is NULL then return the first le.
108  */
109 struct ATTR_LIST_ENTRY *al_enumerate(struct ntfs_inode *ni,
110 				     struct ATTR_LIST_ENTRY *le)
111 {
112 	size_t off;
113 	u16 sz;
114 
115 	if (!le) {
116 		le = ni->attr_list.le;
117 	} else {
118 		sz = le16_to_cpu(le->size);
119 		if (sz < sizeof(struct ATTR_LIST_ENTRY)) {
120 			/* Impossible 'cause we should not return such le. */
121 			return NULL;
122 		}
123 		le = Add2Ptr(le, sz);
124 	}
125 
126 	/* Check boundary. */
127 	off = PtrOffset(ni->attr_list.le, le);
128 	if (off + sizeof(struct ATTR_LIST_ENTRY) > ni->attr_list.size) {
129 		/* The regular end of list. */
130 		return NULL;
131 	}
132 
133 	sz = le16_to_cpu(le->size);
134 
135 	/* Check le for errors. */
136 	if (sz < sizeof(struct ATTR_LIST_ENTRY) ||
137 	    off + sz > ni->attr_list.size ||
138 	    sz < le->name_off + le->name_len * sizeof(short)) {
139 		return NULL;
140 	}
141 
142 	return le;
143 }
144 
145 /*
146  * al_find_le
147  *
148  * Find the first le in the list which matches type, name and VCN.
149  *
150  * Return: NULL if not found.
151  */
152 struct ATTR_LIST_ENTRY *al_find_le(struct ntfs_inode *ni,
153 				   struct ATTR_LIST_ENTRY *le,
154 				   const struct ATTRIB *attr)
155 {
156 	CLST svcn = attr_svcn(attr);
157 
158 	return al_find_ex(ni, le, attr->type, attr_name(attr), attr->name_len,
159 			  &svcn);
160 }
161 
162 /*
163  * al_find_ex
164  *
165  * Find the first le in the list which matches type, name and VCN.
166  *
167  * Return: NULL if not found.
168  */
169 struct ATTR_LIST_ENTRY *al_find_ex(struct ntfs_inode *ni,
170 				   struct ATTR_LIST_ENTRY *le,
171 				   enum ATTR_TYPE type, const __le16 *name,
172 				   u8 name_len, const CLST *vcn)
173 {
174 	struct ATTR_LIST_ENTRY *ret = NULL;
175 	u32 type_in = le32_to_cpu(type);
176 
177 	while ((le = al_enumerate(ni, le))) {
178 		u64 le_vcn;
179 		int diff = le32_to_cpu(le->type) - type_in;
180 
181 		/* List entries are sorted by type, name and VCN. */
182 		if (diff < 0)
183 			continue;
184 
185 		if (diff > 0)
186 			return ret;
187 
188 		if (le->name_len != name_len)
189 			continue;
190 
191 		le_vcn = le64_to_cpu(le->vcn);
192 		if (!le_vcn) {
193 			/*
194 			 * Compare entry names only for entry with vcn == 0.
195 			 */
196 			diff = ntfs_cmp_names(le_name(le), name_len, name,
197 					      name_len, ni->mi.sbi->upcase,
198 					      true);
199 			if (diff < 0)
200 				continue;
201 
202 			if (diff > 0)
203 				return ret;
204 		}
205 
206 		if (!vcn)
207 			return le;
208 
209 		if (*vcn == le_vcn)
210 			return le;
211 
212 		if (*vcn < le_vcn)
213 			return ret;
214 
215 		ret = le;
216 	}
217 
218 	return ret;
219 }
220 
221 /*
222  * al_find_le_to_insert
223  *
224  * Find the first list entry which matches type, name and VCN.
225  */
226 static struct ATTR_LIST_ENTRY *al_find_le_to_insert(struct ntfs_inode *ni,
227 						    enum ATTR_TYPE type,
228 						    const __le16 *name,
229 						    u8 name_len, CLST vcn)
230 {
231 	struct ATTR_LIST_ENTRY *le = NULL, *prev;
232 	u32 type_in = le32_to_cpu(type);
233 
234 	/* List entries are sorted by type, name and VCN. */
235 	while ((le = al_enumerate(ni, prev = le))) {
236 		int diff = le32_to_cpu(le->type) - type_in;
237 
238 		if (diff < 0)
239 			continue;
240 
241 		if (diff > 0)
242 			return le;
243 
244 		if (!le->vcn) {
245 			/*
246 			 * Compare entry names only for entry with vcn == 0.
247 			 */
248 			diff = ntfs_cmp_names(le_name(le), le->name_len, name,
249 					      name_len, ni->mi.sbi->upcase,
250 					      true);
251 			if (diff < 0)
252 				continue;
253 
254 			if (diff > 0)
255 				return le;
256 		}
257 
258 		if (le64_to_cpu(le->vcn) >= vcn)
259 			return le;
260 	}
261 
262 	return prev ? Add2Ptr(prev, le16_to_cpu(prev->size)) : ni->attr_list.le;
263 }
264 
265 /*
266  * al_add_le
267  *
268  * Add an "attribute list entry" to the list.
269  */
270 int al_add_le(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name,
271 	      u8 name_len, CLST svcn, __le16 id, const struct MFT_REF *ref,
272 	      struct ATTR_LIST_ENTRY **new_le)
273 {
274 	int err;
275 	struct ATTRIB *attr;
276 	struct ATTR_LIST_ENTRY *le;
277 	size_t off;
278 	u16 sz;
279 	size_t asize, new_asize, old_size;
280 	u64 new_size;
281 	typeof(ni->attr_list) *al = &ni->attr_list;
282 
283 	/*
284 	 * Compute the size of the new 'le'
285 	 */
286 	sz = le_size(name_len);
287 	old_size = al->size;
288 	new_size = old_size + sz;
289 	asize = al_aligned(old_size);
290 	new_asize = al_aligned(new_size);
291 
292 	/* Scan forward to the point at which the new 'le' should be inserted. */
293 	le = al_find_le_to_insert(ni, type, name, name_len, svcn);
294 	off = PtrOffset(al->le, le);
295 
296 	if (new_size > asize) {
297 		void *ptr = kmalloc(new_asize, GFP_NOFS);
298 
299 		if (!ptr)
300 			return -ENOMEM;
301 
302 		memcpy(ptr, al->le, off);
303 		memcpy(Add2Ptr(ptr, off + sz), le, old_size - off);
304 		le = Add2Ptr(ptr, off);
305 		kfree(al->le);
306 		al->le = ptr;
307 	} else {
308 		memmove(Add2Ptr(le, sz), le, old_size - off);
309 	}
310 	*new_le = le;
311 
312 	al->size = new_size;
313 
314 	le->type = type;
315 	le->size = cpu_to_le16(sz);
316 	le->name_len = name_len;
317 	le->name_off = offsetof(struct ATTR_LIST_ENTRY, name);
318 	le->vcn = cpu_to_le64(svcn);
319 	le->ref = *ref;
320 	le->id = id;
321 	memcpy(le->name, name, sizeof(short) * name_len);
322 
323 	err = attr_set_size(ni, ATTR_LIST, NULL, 0, &al->run, new_size,
324 			    &new_size, true, &attr);
325 	if (err) {
326 		/* Undo memmove above. */
327 		memmove(le, Add2Ptr(le, sz), old_size - off);
328 		al->size = old_size;
329 		return err;
330 	}
331 
332 	al->dirty = true;
333 
334 	if (attr && attr->non_res) {
335 		err = ntfs_sb_write_run(ni->mi.sbi, &al->run, 0, al->le,
336 					al->size, 0);
337 		if (err)
338 			return err;
339 		al->dirty = false;
340 	}
341 
342 	return 0;
343 }
344 
345 /*
346  * al_remove_le - Remove @le from attribute list.
347  */
348 bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le)
349 {
350 	u16 size;
351 	size_t off;
352 	typeof(ni->attr_list) *al = &ni->attr_list;
353 
354 	if (!al_is_valid_le(ni, le))
355 		return false;
356 
357 	/* Save on stack the size of 'le' */
358 	size = le16_to_cpu(le->size);
359 	off = PtrOffset(al->le, le);
360 
361 	memmove(le, Add2Ptr(le, size), al->size - (off + size));
362 
363 	al->size -= size;
364 	al->dirty = true;
365 
366 	return true;
367 }
368 
369 /*
370  * al_delete_le - Delete first le from the list which matches its parameters.
371  */
372 bool al_delete_le(struct ntfs_inode *ni, enum ATTR_TYPE type, CLST vcn,
373 		  const __le16 *name, size_t name_len,
374 		  const struct MFT_REF *ref)
375 {
376 	u16 size;
377 	struct ATTR_LIST_ENTRY *le;
378 	size_t off;
379 	typeof(ni->attr_list) *al = &ni->attr_list;
380 
381 	/* Scan forward to the first le that matches the input. */
382 	le = al_find_ex(ni, NULL, type, name, name_len, &vcn);
383 	if (!le)
384 		return false;
385 
386 	off = PtrOffset(al->le, le);
387 
388 next:
389 	if (off >= al->size)
390 		return false;
391 	if (le->type != type)
392 		return false;
393 	if (le->name_len != name_len)
394 		return false;
395 	if (name_len && ntfs_cmp_names(le_name(le), name_len, name, name_len,
396 				       ni->mi.sbi->upcase, true))
397 		return false;
398 	if (le64_to_cpu(le->vcn) != vcn)
399 		return false;
400 
401 	/*
402 	 * The caller specified a segment reference, so we have to
403 	 * scan through the matching entries until we find that segment
404 	 * reference or we run of matching entries.
405 	 */
406 	if (ref && memcmp(ref, &le->ref, sizeof(*ref))) {
407 		off += le16_to_cpu(le->size);
408 		le = Add2Ptr(al->le, off);
409 		goto next;
410 	}
411 
412 	/* Save on stack the size of 'le'. */
413 	size = le16_to_cpu(le->size);
414 	/* Delete the le. */
415 	memmove(le, Add2Ptr(le, size), al->size - (off + size));
416 
417 	al->size -= size;
418 	al->dirty = true;
419 
420 	return true;
421 }
422 
423 int al_update(struct ntfs_inode *ni, int sync)
424 {
425 	int err;
426 	struct ATTRIB *attr;
427 	typeof(ni->attr_list) *al = &ni->attr_list;
428 
429 	if (!al->dirty || !al->size)
430 		return 0;
431 
432 	/*
433 	 * Attribute list increased on demand in al_add_le.
434 	 * Attribute list decreased here.
435 	 */
436 	err = attr_set_size(ni, ATTR_LIST, NULL, 0, &al->run, al->size, NULL,
437 			    false, &attr);
438 	if (err)
439 		goto out;
440 
441 	if (!attr->non_res) {
442 		memcpy(resident_data(attr), al->le, al->size);
443 	} else {
444 		err = ntfs_sb_write_run(ni->mi.sbi, &al->run, 0, al->le,
445 					al->size, sync);
446 		if (err)
447 			goto out;
448 
449 		attr->nres.valid_size = attr->nres.data_size;
450 	}
451 
452 	ni->mi.dirty = true;
453 	al->dirty = false;
454 
455 out:
456 	return err;
457 }
458