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
13static struct kmem_cache *hfsplus_attr_tree_cachep;
14
15int __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
30void hfsplus_destroy_attr_tree_cache(void)
31{
32	kmem_cache_destroy(hfsplus_attr_tree_cachep);
33}
34
35int 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
50int 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		if (res)
62			return res;
63		len = be16_to_cpu(key->attr.key_name.length);
64	} else {
65		key->attr.key_name.length = 0;
66		len = 0;
67	}
68
69	/* The length of the key, as stored in key_len field, does not include
70	 * the size of the key_len field itself.
71	 * So, offsetof(hfsplus_attr_key, key_name) is a trick because
72	 * it takes into consideration key_len field (__be16) of
73	 * hfsplus_attr_key structure instead of length field (__be16) of
74	 * hfsplus_attr_unistr structure.
75	 */
76	key->key_len =
77		cpu_to_be16(offsetof(struct hfsplus_attr_key, key_name) +
78				2 * len);
79
80	return 0;
81}
82
83hfsplus_attr_entry *hfsplus_alloc_attr_entry(void)
84{
85	return kmem_cache_alloc(hfsplus_attr_tree_cachep, GFP_KERNEL);
86}
87
88void hfsplus_destroy_attr_entry(hfsplus_attr_entry *entry)
89{
90	if (entry)
91		kmem_cache_free(hfsplus_attr_tree_cachep, entry);
92}
93
94#define HFSPLUS_INVALID_ATTR_RECORD -1
95
96static int hfsplus_attr_build_record(hfsplus_attr_entry *entry, int record_type,
97				u32 cnid, const void *value, size_t size)
98{
99	if (record_type == HFSPLUS_ATTR_FORK_DATA) {
100		/*
101		 * Mac OS X supports only inline data attributes.
102		 * Do nothing
103		 */
104		memset(entry, 0, sizeof(*entry));
105		return sizeof(struct hfsplus_attr_fork_data);
106	} else if (record_type == HFSPLUS_ATTR_EXTENTS) {
107		/*
108		 * Mac OS X supports only inline data attributes.
109		 * Do nothing.
110		 */
111		memset(entry, 0, sizeof(*entry));
112		return sizeof(struct hfsplus_attr_extents);
113	} else if (record_type == HFSPLUS_ATTR_INLINE_DATA) {
114		u16 len;
115
116		memset(entry, 0, sizeof(struct hfsplus_attr_inline_data));
117		entry->inline_data.record_type = cpu_to_be32(record_type);
118		if (size <= HFSPLUS_MAX_INLINE_DATA_SIZE)
119			len = size;
120		else
121			return HFSPLUS_INVALID_ATTR_RECORD;
122		entry->inline_data.length = cpu_to_be16(len);
123		memcpy(entry->inline_data.raw_bytes, value, len);
124		/*
125		 * Align len on two-byte boundary.
126		 * It needs to add pad byte if we have odd len.
127		 */
128		len = round_up(len, 2);
129		return offsetof(struct hfsplus_attr_inline_data, raw_bytes) +
130					len;
131	} else /* invalid input */
132		memset(entry, 0, sizeof(*entry));
133
134	return HFSPLUS_INVALID_ATTR_RECORD;
135}
136
137int hfsplus_find_attr(struct super_block *sb, u32 cnid,
138			const char *name, struct hfs_find_data *fd)
139{
140	int err = 0;
141
142	hfs_dbg(ATTR_MOD, "find_attr: %s,%d\n", name ? name : NULL, cnid);
143
144	if (!HFSPLUS_SB(sb)->attr_tree) {
145		pr_err("attributes file doesn't exist\n");
146		return -EINVAL;
147	}
148
149	if (name) {
150		err = hfsplus_attr_build_key(sb, fd->search_key, cnid, name);
151		if (err)
152			goto failed_find_attr;
153		err = hfs_brec_find(fd, hfs_find_rec_by_key);
154		if (err)
155			goto failed_find_attr;
156	} else {
157		err = hfsplus_attr_build_key(sb, fd->search_key, cnid, NULL);
158		if (err)
159			goto failed_find_attr;
160		err = hfs_brec_find(fd, hfs_find_1st_rec_by_cnid);
161		if (err)
162			goto failed_find_attr;
163	}
164
165failed_find_attr:
166	return err;
167}
168
169int hfsplus_attr_exists(struct inode *inode, const char *name)
170{
171	int err = 0;
172	struct super_block *sb = inode->i_sb;
173	struct hfs_find_data fd;
174
175	if (!HFSPLUS_SB(sb)->attr_tree)
176		return 0;
177
178	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
179	if (err)
180		return 0;
181
182	err = hfsplus_find_attr(sb, inode->i_ino, name, &fd);
183	if (err)
184		goto attr_not_found;
185
186	hfs_find_exit(&fd);
187	return 1;
188
189attr_not_found:
190	hfs_find_exit(&fd);
191	return 0;
192}
193
194int hfsplus_create_attr(struct inode *inode,
195				const char *name,
196				const void *value, size_t size)
197{
198	struct super_block *sb = inode->i_sb;
199	struct hfs_find_data fd;
200	hfsplus_attr_entry *entry_ptr;
201	int entry_size;
202	int err;
203
204	hfs_dbg(ATTR_MOD, "create_attr: %s,%ld\n",
205		name ? name : NULL, inode->i_ino);
206
207	if (!HFSPLUS_SB(sb)->attr_tree) {
208		pr_err("attributes file doesn't exist\n");
209		return -EINVAL;
210	}
211
212	entry_ptr = hfsplus_alloc_attr_entry();
213	if (!entry_ptr)
214		return -ENOMEM;
215
216	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
217	if (err)
218		goto failed_init_create_attr;
219
220	/* Fail early and avoid ENOSPC during the btree operation */
221	err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
222	if (err)
223		goto failed_create_attr;
224
225	if (name) {
226		err = hfsplus_attr_build_key(sb, fd.search_key,
227						inode->i_ino, name);
228		if (err)
229			goto failed_create_attr;
230	} else {
231		err = -EINVAL;
232		goto failed_create_attr;
233	}
234
235	/* Mac OS X supports only inline data attributes. */
236	entry_size = hfsplus_attr_build_record(entry_ptr,
237					HFSPLUS_ATTR_INLINE_DATA,
238					inode->i_ino,
239					value, size);
240	if (entry_size == HFSPLUS_INVALID_ATTR_RECORD) {
241		err = -EINVAL;
242		goto failed_create_attr;
243	}
244
245	err = hfs_brec_find(&fd, hfs_find_rec_by_key);
246	if (err != -ENOENT) {
247		if (!err)
248			err = -EEXIST;
249		goto failed_create_attr;
250	}
251
252	err = hfs_brec_insert(&fd, entry_ptr, entry_size);
253	if (err)
254		goto failed_create_attr;
255
256	hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
257
258failed_create_attr:
259	hfs_find_exit(&fd);
260
261failed_init_create_attr:
262	hfsplus_destroy_attr_entry(entry_ptr);
263	return err;
264}
265
266static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
267					struct hfs_find_data *fd)
268{
269	int err = 0;
270	__be32 found_cnid, record_type;
271
272	hfs_bnode_read(fd->bnode, &found_cnid,
273			fd->keyoffset +
274			offsetof(struct hfsplus_attr_key, cnid),
275			sizeof(__be32));
276	if (cnid != be32_to_cpu(found_cnid))
277		return -ENOENT;
278
279	hfs_bnode_read(fd->bnode, &record_type,
280			fd->entryoffset, sizeof(record_type));
281
282	switch (be32_to_cpu(record_type)) {
283	case HFSPLUS_ATTR_INLINE_DATA:
284		/* All is OK. Do nothing. */
285		break;
286	case HFSPLUS_ATTR_FORK_DATA:
287	case HFSPLUS_ATTR_EXTENTS:
288		pr_err("only inline data xattr are supported\n");
289		return -EOPNOTSUPP;
290	default:
291		pr_err("invalid extended attribute record\n");
292		return -ENOENT;
293	}
294
295	/* Avoid btree corruption */
296	hfs_bnode_read(fd->bnode, fd->search_key,
297			fd->keyoffset, fd->keylength);
298
299	err = hfs_brec_remove(fd);
300	if (err)
301		return err;
302
303	hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
304	return err;
305}
306
307int hfsplus_delete_attr(struct inode *inode, const char *name)
308{
309	int err = 0;
310	struct super_block *sb = inode->i_sb;
311	struct hfs_find_data fd;
312
313	hfs_dbg(ATTR_MOD, "delete_attr: %s,%ld\n",
314		name ? name : NULL, inode->i_ino);
315
316	if (!HFSPLUS_SB(sb)->attr_tree) {
317		pr_err("attributes file doesn't exist\n");
318		return -EINVAL;
319	}
320
321	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
322	if (err)
323		return err;
324
325	/* Fail early and avoid ENOSPC during the btree operation */
326	err = hfs_bmap_reserve(fd.tree, fd.tree->depth);
327	if (err)
328		goto out;
329
330	if (name) {
331		err = hfsplus_attr_build_key(sb, fd.search_key,
332						inode->i_ino, name);
333		if (err)
334			goto out;
335	} else {
336		pr_err("invalid extended attribute name\n");
337		err = -EINVAL;
338		goto out;
339	}
340
341	err = hfs_brec_find(&fd, hfs_find_rec_by_key);
342	if (err)
343		goto out;
344
345	err = __hfsplus_delete_attr(inode, inode->i_ino, &fd);
346	if (err)
347		goto out;
348
349out:
350	hfs_find_exit(&fd);
351	return err;
352}
353
354int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid)
355{
356	int err = 0;
357	struct hfs_find_data fd;
358
359	hfs_dbg(ATTR_MOD, "delete_all_attrs: %d\n", cnid);
360
361	if (!HFSPLUS_SB(dir->i_sb)->attr_tree) {
362		pr_err("attributes file doesn't exist\n");
363		return -EINVAL;
364	}
365
366	err = hfs_find_init(HFSPLUS_SB(dir->i_sb)->attr_tree, &fd);
367	if (err)
368		return err;
369
370	for (;;) {
371		err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd);
372		if (err) {
373			if (err != -ENOENT)
374				pr_err("xattr search failed\n");
375			goto end_delete_all;
376		}
377
378		err = __hfsplus_delete_attr(dir, cnid, &fd);
379		if (err)
380			goto end_delete_all;
381	}
382
383end_delete_all:
384	hfs_find_exit(&fd);
385	return err;
386}
387