xref: /linux/fs/hfsplus/attributes.c (revision 4d9981429aa61c31e67371ac09e7dbba6b59de14)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * linux/fs/hfsplus/attributes.c
4  *
5  * Vyacheslav Dubeyko <slava@dubeyko.com>
6  *
7  * Handling of records in attributes tree
8  */
9 
10 #include "hfsplus_fs.h"
11 #include "hfsplus_raw.h"
12 
13 static struct kmem_cache *hfsplus_attr_tree_cachep;
14 
hfsplus_create_attr_tree_cache(void)15 int __init hfsplus_create_attr_tree_cache(void)
16 {
17 	if (hfsplus_attr_tree_cachep)
18 		return -EEXIST;
19 
20 	hfsplus_attr_tree_cachep =
21 		kmem_cache_create("hfsplus_attr_cache",
22 			sizeof(hfsplus_attr_entry), 0,
23 			SLAB_HWCACHE_ALIGN, NULL);
24 	if (!hfsplus_attr_tree_cachep)
25 		return -ENOMEM;
26 
27 	return 0;
28 }
29 
hfsplus_destroy_attr_tree_cache(void)30 void hfsplus_destroy_attr_tree_cache(void)
31 {
32 	kmem_cache_destroy(hfsplus_attr_tree_cachep);
33 }
34 
hfsplus_attr_bin_cmp_key(const hfsplus_btree_key * k1,const hfsplus_btree_key * k2)35 int hfsplus_attr_bin_cmp_key(const hfsplus_btree_key *k1,
36 				const hfsplus_btree_key *k2)
37 {
38 	__be32 k1_cnid, k2_cnid;
39 
40 	k1_cnid = k1->attr.cnid;
41 	k2_cnid = k2->attr.cnid;
42 	if (k1_cnid != k2_cnid)
43 		return be32_to_cpu(k1_cnid) < be32_to_cpu(k2_cnid) ? -1 : 1;
44 
45 	return hfsplus_strcmp(
46 			(const struct hfsplus_unistr *)&k1->attr.key_name,
47 			(const struct hfsplus_unistr *)&k2->attr.key_name);
48 }
49 
hfsplus_attr_build_key(struct super_block * sb,hfsplus_btree_key * key,u32 cnid,const char * name)50 int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key,
51 			u32 cnid, const char *name)
52 {
53 	int len;
54 
55 	memset(key, 0, sizeof(struct hfsplus_attr_key));
56 	key->attr.cnid = cpu_to_be32(cnid);
57 	if (name) {
58 		int res = hfsplus_asc2uni(sb,
59 				(struct hfsplus_unistr *)&key->attr.key_name,
60 				HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name),
61 				HFS_XATTR_NAME);
62 		if (res)
63 			return res;
64 		len = be16_to_cpu(key->attr.key_name.length);
65 	} else {
66 		key->attr.key_name.length = 0;
67 		len = 0;
68 	}
69 
70 	/* The length of the key, as stored in key_len field, does not include
71 	 * the size of the key_len field itself.
72 	 * So, offsetof(hfsplus_attr_key, key_name) is a trick because
73 	 * it takes into consideration key_len field (__be16) of
74 	 * hfsplus_attr_key structure instead of length field (__be16) of
75 	 * hfsplus_attr_unistr structure.
76 	 */
77 	key->key_len =
78 		cpu_to_be16(offsetof(struct hfsplus_attr_key, key_name) +
79 				2 * len);
80 
81 	return 0;
82 }
83 
hfsplus_alloc_attr_entry(void)84 hfsplus_attr_entry *hfsplus_alloc_attr_entry(void)
85 {
86 	return kmem_cache_alloc(hfsplus_attr_tree_cachep, GFP_KERNEL);
87 }
88 
hfsplus_destroy_attr_entry(hfsplus_attr_entry * entry)89 void hfsplus_destroy_attr_entry(hfsplus_attr_entry *entry)
90 {
91 	if (entry)
92 		kmem_cache_free(hfsplus_attr_tree_cachep, entry);
93 }
94 
95 #define HFSPLUS_INVALID_ATTR_RECORD -1
96 
hfsplus_attr_build_record(hfsplus_attr_entry * entry,int record_type,u32 cnid,const void * value,size_t size)97 static int hfsplus_attr_build_record(hfsplus_attr_entry *entry, int record_type,
98 				u32 cnid, const void *value, size_t size)
99 {
100 	if (record_type == HFSPLUS_ATTR_FORK_DATA) {
101 		/*
102 		 * Mac OS X supports only inline data attributes.
103 		 * Do nothing
104 		 */
105 		memset(entry, 0, sizeof(*entry));
106 		return sizeof(struct hfsplus_attr_fork_data);
107 	} else if (record_type == HFSPLUS_ATTR_EXTENTS) {
108 		/*
109 		 * Mac OS X supports only inline data attributes.
110 		 * Do nothing.
111 		 */
112 		memset(entry, 0, sizeof(*entry));
113 		return sizeof(struct hfsplus_attr_extents);
114 	} else if (record_type == HFSPLUS_ATTR_INLINE_DATA) {
115 		u16 len;
116 
117 		memset(entry, 0, sizeof(struct hfsplus_attr_inline_data));
118 		entry->inline_data.record_type = cpu_to_be32(record_type);
119 		if (size <= HFSPLUS_MAX_INLINE_DATA_SIZE)
120 			len = size;
121 		else {
122 			hfs_dbg("value size %zu is too big\n", size);
123 			return HFSPLUS_INVALID_ATTR_RECORD;
124 		}
125 		entry->inline_data.length = cpu_to_be16(len);
126 		memcpy(entry->inline_data.raw_bytes, value, len);
127 		/*
128 		 * Align len on two-byte boundary.
129 		 * It needs to add pad byte if we have odd len.
130 		 */
131 		len = round_up(len, 2);
132 		return offsetof(struct hfsplus_attr_inline_data, raw_bytes) +
133 					len;
134 	} else /* invalid input */
135 		memset(entry, 0, sizeof(*entry));
136 
137 	return HFSPLUS_INVALID_ATTR_RECORD;
138 }
139 
hfsplus_find_attr(struct super_block * sb,u32 cnid,const char * name,struct hfs_find_data * fd)140 int hfsplus_find_attr(struct super_block *sb, u32 cnid,
141 			const char *name, struct hfs_find_data *fd)
142 {
143 	int err = 0;
144 
145 	hfs_dbg("name %s, cnid %d\n", name ? name : NULL, cnid);
146 
147 	if (!HFSPLUS_SB(sb)->attr_tree) {
148 		pr_err("attributes file doesn't exist\n");
149 		return -EINVAL;
150 	}
151 
152 	if (name) {
153 		err = hfsplus_attr_build_key(sb, fd->search_key, cnid, name);
154 		if (err)
155 			goto failed_find_attr;
156 		err = hfs_brec_find(fd, hfs_find_rec_by_key);
157 		if (err == -ENOENT) {
158 			/* file exists but xattr is absent */
159 			err = -ENODATA;
160 			goto failed_find_attr;
161 		} else if (err)
162 			goto failed_find_attr;
163 	} else {
164 		err = hfsplus_attr_build_key(sb, fd->search_key, cnid, NULL);
165 		if (err)
166 			goto failed_find_attr;
167 		err = hfs_brec_find(fd, hfs_find_1st_rec_by_cnid);
168 		if (err == -ENOENT) {
169 			/* file exists but xattr is absent */
170 			err = -ENODATA;
171 			goto failed_find_attr;
172 		} else if (err)
173 			goto failed_find_attr;
174 	}
175 
176 failed_find_attr:
177 	return err;
178 }
179 
hfsplus_attr_exists(struct inode * inode,const char * name)180 int hfsplus_attr_exists(struct inode *inode, const char *name)
181 {
182 	int err = 0;
183 	struct super_block *sb = inode->i_sb;
184 	struct hfs_find_data fd;
185 
186 	hfs_dbg("name %s, ino %llu\n",
187 		name ? name : NULL, inode->i_ino);
188 
189 	if (!HFSPLUS_SB(sb)->attr_tree)
190 		return 0;
191 
192 	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
193 	if (err)
194 		return 0;
195 
196 	err = hfsplus_find_attr(sb, inode->i_ino, name, &fd);
197 	if (err)
198 		goto attr_not_found;
199 
200 	hfs_find_exit(&fd);
201 	return 1;
202 
203 attr_not_found:
204 	hfs_find_exit(&fd);
205 	return 0;
206 }
207 
208 static
hfsplus_create_attr_nolock(struct inode * inode,const char * name,const void * value,size_t size,struct hfs_find_data * fd,hfsplus_attr_entry * entry_ptr)209 int hfsplus_create_attr_nolock(struct inode *inode, const char *name,
210 				const void *value, size_t size,
211 				struct hfs_find_data *fd,
212 				hfsplus_attr_entry *entry_ptr)
213 {
214 	struct super_block *sb = inode->i_sb;
215 	int entry_size;
216 	int err;
217 
218 	hfs_dbg("name %s, ino %llu\n",
219 		name ? name : NULL, inode->i_ino);
220 
221 	if (name) {
222 		err = hfsplus_attr_build_key(sb, fd->search_key,
223 						inode->i_ino, name);
224 		if (err)
225 			return err;
226 	} else
227 		return -EINVAL;
228 
229 	/* Mac OS X supports only inline data attributes. */
230 	entry_size = hfsplus_attr_build_record(entry_ptr,
231 					HFSPLUS_ATTR_INLINE_DATA,
232 					inode->i_ino,
233 					value, size);
234 	if (entry_size == HFSPLUS_INVALID_ATTR_RECORD) {
235 		if (size > HFSPLUS_MAX_INLINE_DATA_SIZE)
236 			err = -E2BIG;
237 		else
238 			err = -EINVAL;
239 		hfs_dbg("unable to store value: err %d\n", err);
240 		return err;
241 	}
242 
243 	err = hfs_brec_find(fd, hfs_find_rec_by_key);
244 	if (err != -ENOENT) {
245 		if (!err)
246 			err = -EEXIST;
247 		return err;
248 	}
249 
250 	err = hfs_brec_insert(fd, entry_ptr, entry_size);
251 	if (err) {
252 		hfs_dbg("unable to store value: err %d\n", err);
253 		return err;
254 	}
255 
256 	hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
257 	hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
258 
259 	return 0;
260 }
261 
hfsplus_create_attr(struct inode * inode,const char * name,const void * value,size_t size)262 int hfsplus_create_attr(struct inode *inode,
263 			const char *name,
264 			const void *value, size_t size)
265 {
266 	struct super_block *sb = inode->i_sb;
267 	struct hfs_find_data fd;
268 	hfsplus_attr_entry *entry_ptr;
269 	int err;
270 
271 	hfs_dbg("name %s, ino %llu\n",
272 		name ? name : NULL, inode->i_ino);
273 
274 	if (!HFSPLUS_SB(sb)->attr_tree) {
275 		pr_err("attributes file doesn't exist\n");
276 		return -EINVAL;
277 	}
278 
279 	entry_ptr = hfsplus_alloc_attr_entry();
280 	if (!entry_ptr)
281 		return -ENOMEM;
282 
283 	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
284 	if (err)
285 		goto failed_init_create_attr;
286 
287 	/* Fail early and avoid ENOSPC during the btree operation */
288 	err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
289 	if (err)
290 		goto failed_create_attr;
291 
292 	err = hfsplus_create_attr_nolock(inode, name, value, size,
293 					 &fd, entry_ptr);
294 	if (err)
295 		goto failed_create_attr;
296 
297 failed_create_attr:
298 	hfs_find_exit(&fd);
299 
300 failed_init_create_attr:
301 	hfsplus_destroy_attr_entry(entry_ptr);
302 	return err;
303 }
304 
__hfsplus_delete_attr(struct inode * inode,u32 cnid,struct hfs_find_data * fd)305 static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
306 					struct hfs_find_data *fd)
307 {
308 	int err;
309 	__be32 found_cnid, record_type;
310 
311 	found_cnid = U32_MAX;
312 	hfs_bnode_read(fd->bnode, &found_cnid,
313 			fd->keyoffset +
314 			offsetof(struct hfsplus_attr_key, cnid),
315 			sizeof(__be32));
316 	if (cnid != be32_to_cpu(found_cnid))
317 		return -ENODATA;
318 
319 	hfs_bnode_read(fd->bnode, &record_type,
320 			fd->entryoffset, sizeof(record_type));
321 
322 	switch (be32_to_cpu(record_type)) {
323 	case HFSPLUS_ATTR_INLINE_DATA:
324 		/* All is OK. Do nothing. */
325 		break;
326 	case HFSPLUS_ATTR_FORK_DATA:
327 	case HFSPLUS_ATTR_EXTENTS:
328 		pr_err("only inline data xattr are supported\n");
329 		return -EOPNOTSUPP;
330 	default:
331 		pr_err("invalid extended attribute record\n");
332 		return -ENOENT;
333 	}
334 
335 	/* Avoid btree corruption */
336 	hfs_bnode_read(fd->bnode, fd->search_key,
337 			fd->keyoffset, fd->keylength);
338 
339 	err = hfs_brec_remove(fd);
340 	if (err)
341 		return err;
342 
343 	hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(inode->i_sb),
344 				 HFSPLUS_I_ATTR_DIRTY);
345 	hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
346 	return 0;
347 }
348 
349 static
hfsplus_delete_attr_nolock(struct inode * inode,const char * name,struct hfs_find_data * fd)350 int hfsplus_delete_attr_nolock(struct inode *inode, const char *name,
351 				struct hfs_find_data *fd)
352 {
353 	struct super_block *sb = inode->i_sb;
354 	int err;
355 
356 	hfs_dbg("name %s, ino %llu\n",
357 		name ? name : NULL, inode->i_ino);
358 
359 	if (name) {
360 		err = hfsplus_attr_build_key(sb, fd->search_key,
361 						inode->i_ino, name);
362 		if (err)
363 			return err;
364 	} else {
365 		pr_err("invalid extended attribute name\n");
366 		return -EINVAL;
367 	}
368 
369 	err = hfs_brec_find(fd, hfs_find_rec_by_key);
370 	if (err == -ENOENT) {
371 		/* file exists but xattr is absent */
372 		return -ENODATA;
373 	} else if (err)
374 		return err;
375 
376 	err = __hfsplus_delete_attr(inode, inode->i_ino, fd);
377 	if (err)
378 		return err;
379 
380 	return 0;
381 }
382 
hfsplus_delete_attr(struct inode * inode,const char * name)383 int hfsplus_delete_attr(struct inode *inode, const char *name)
384 {
385 	int err = 0;
386 	struct super_block *sb = inode->i_sb;
387 	struct hfs_find_data fd;
388 
389 	hfs_dbg("name %s, ino %llu\n",
390 		name ? name : NULL, inode->i_ino);
391 
392 	if (!HFSPLUS_SB(sb)->attr_tree) {
393 		pr_err("attributes file doesn't exist\n");
394 		return -EINVAL;
395 	}
396 
397 	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
398 	if (err)
399 		return err;
400 
401 	/* Fail early and avoid ENOSPC during the btree operation */
402 	err = hfs_bmap_reserve(fd.tree, fd.tree->depth);
403 	if (err)
404 		goto out;
405 
406 	err = hfsplus_delete_attr_nolock(inode, name, &fd);
407 	if (err)
408 		goto out;
409 
410 out:
411 	hfs_find_exit(&fd);
412 	return err;
413 }
414 
hfsplus_delete_all_attrs(struct inode * dir,u32 cnid)415 int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid)
416 {
417 	int err = 0;
418 	struct hfs_find_data fd;
419 
420 	hfs_dbg("cnid %d\n", cnid);
421 
422 	if (!HFSPLUS_SB(dir->i_sb)->attr_tree) {
423 		pr_err("attributes file doesn't exist\n");
424 		return -EINVAL;
425 	}
426 
427 	err = hfs_find_init(HFSPLUS_SB(dir->i_sb)->attr_tree, &fd);
428 	if (err)
429 		return err;
430 
431 	for (;;) {
432 		err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd);
433 		if (err == -ENOENT || err == -ENODATA) {
434 			/*
435 			 * xattr has not been found
436 			 */
437 			err = -ENODATA;
438 			goto end_delete_all;
439 		} else if (err) {
440 			pr_err("xattr search failed\n");
441 			goto end_delete_all;
442 		}
443 
444 		err = __hfsplus_delete_attr(dir, cnid, &fd);
445 		if (err)
446 			goto end_delete_all;
447 	}
448 
449 end_delete_all:
450 	hfs_find_exit(&fd);
451 	return err;
452 }
453 
hfsplus_replace_attr(struct inode * inode,const char * name,const void * value,size_t size)454 int hfsplus_replace_attr(struct inode *inode,
455 			 const char *name,
456 			 const void *value, size_t size)
457 {
458 	struct super_block *sb = inode->i_sb;
459 	struct hfs_find_data fd;
460 	hfsplus_attr_entry *entry_ptr;
461 	int err = 0;
462 
463 	hfs_dbg("name %s, ino %llu\n",
464 		name ? name : NULL, inode->i_ino);
465 
466 	if (!HFSPLUS_SB(sb)->attr_tree) {
467 		pr_err("attributes file doesn't exist\n");
468 		return -EINVAL;
469 	}
470 
471 	entry_ptr = hfsplus_alloc_attr_entry();
472 	if (!entry_ptr)
473 		return -ENOMEM;
474 
475 	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
476 	if (err)
477 		goto failed_init_replace_attr;
478 
479 	/* Fail early and avoid ENOSPC during the btree operation */
480 	err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
481 	if (err)
482 		goto failed_replace_attr;
483 
484 	err = hfsplus_delete_attr_nolock(inode, name, &fd);
485 	if (err)
486 		goto failed_replace_attr;
487 
488 	err = hfsplus_create_attr_nolock(inode, name, value, size,
489 					 &fd, entry_ptr);
490 	if (err)
491 		goto failed_replace_attr;
492 
493 failed_replace_attr:
494 	hfs_find_exit(&fd);
495 
496 failed_init_replace_attr:
497 	hfsplus_destroy_attr_entry(entry_ptr);
498 	return err;
499 }
500