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