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 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
hfsplus_alloc_attr_entry(void)83 hfsplus_attr_entry *hfsplus_alloc_attr_entry(void)
84 {
85 return kmem_cache_alloc(hfsplus_attr_tree_cachep, GFP_KERNEL);
86 }
87
hfsplus_destroy_attr_entry(hfsplus_attr_entry * entry)88 void 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
hfsplus_attr_build_record(hfsplus_attr_entry * entry,int record_type,u32 cnid,const void * value,size_t size)96 static 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 hfs_dbg("value size %zu is too big\n", size);
122 return HFSPLUS_INVALID_ATTR_RECORD;
123 }
124 entry->inline_data.length = cpu_to_be16(len);
125 memcpy(entry->inline_data.raw_bytes, value, len);
126 /*
127 * Align len on two-byte boundary.
128 * It needs to add pad byte if we have odd len.
129 */
130 len = round_up(len, 2);
131 return offsetof(struct hfsplus_attr_inline_data, raw_bytes) +
132 len;
133 } else /* invalid input */
134 memset(entry, 0, sizeof(*entry));
135
136 return HFSPLUS_INVALID_ATTR_RECORD;
137 }
138
hfsplus_find_attr(struct super_block * sb,u32 cnid,const char * name,struct hfs_find_data * fd)139 int hfsplus_find_attr(struct super_block *sb, u32 cnid,
140 const char *name, struct hfs_find_data *fd)
141 {
142 int err = 0;
143
144 hfs_dbg("name %s, cnid %d\n", name ? name : NULL, cnid);
145
146 if (!HFSPLUS_SB(sb)->attr_tree) {
147 pr_err("attributes file doesn't exist\n");
148 return -EINVAL;
149 }
150
151 if (name) {
152 err = hfsplus_attr_build_key(sb, fd->search_key, cnid, name);
153 if (err)
154 goto failed_find_attr;
155 err = hfs_brec_find(fd, hfs_find_rec_by_key);
156 if (err)
157 goto failed_find_attr;
158 } else {
159 err = hfsplus_attr_build_key(sb, fd->search_key, cnid, NULL);
160 if (err)
161 goto failed_find_attr;
162 err = hfs_brec_find(fd, hfs_find_1st_rec_by_cnid);
163 if (err)
164 goto failed_find_attr;
165 }
166
167 failed_find_attr:
168 return err;
169 }
170
hfsplus_attr_exists(struct inode * inode,const char * name)171 int hfsplus_attr_exists(struct inode *inode, const char *name)
172 {
173 int err = 0;
174 struct super_block *sb = inode->i_sb;
175 struct hfs_find_data fd;
176
177 if (!HFSPLUS_SB(sb)->attr_tree)
178 return 0;
179
180 err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
181 if (err)
182 return 0;
183
184 err = hfsplus_find_attr(sb, inode->i_ino, name, &fd);
185 if (err)
186 goto attr_not_found;
187
188 hfs_find_exit(&fd);
189 return 1;
190
191 attr_not_found:
192 hfs_find_exit(&fd);
193 return 0;
194 }
195
196 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)197 int hfsplus_create_attr_nolock(struct inode *inode, const char *name,
198 const void *value, size_t size,
199 struct hfs_find_data *fd,
200 hfsplus_attr_entry *entry_ptr)
201 {
202 struct super_block *sb = inode->i_sb;
203 int entry_size;
204 int err;
205
206 hfs_dbg("name %s, ino %ld\n",
207 name ? name : NULL, inode->i_ino);
208
209 if (name) {
210 err = hfsplus_attr_build_key(sb, fd->search_key,
211 inode->i_ino, name);
212 if (err)
213 return err;
214 } else
215 return -EINVAL;
216
217 /* Mac OS X supports only inline data attributes. */
218 entry_size = hfsplus_attr_build_record(entry_ptr,
219 HFSPLUS_ATTR_INLINE_DATA,
220 inode->i_ino,
221 value, size);
222 if (entry_size == HFSPLUS_INVALID_ATTR_RECORD) {
223 if (size > HFSPLUS_MAX_INLINE_DATA_SIZE)
224 err = -E2BIG;
225 else
226 err = -EINVAL;
227 hfs_dbg("unable to store value: err %d\n", err);
228 return err;
229 }
230
231 err = hfs_brec_find(fd, hfs_find_rec_by_key);
232 if (err != -ENOENT) {
233 if (!err)
234 err = -EEXIST;
235 return err;
236 }
237
238 err = hfs_brec_insert(fd, entry_ptr, entry_size);
239 if (err) {
240 hfs_dbg("unable to store value: err %d\n", err);
241 return err;
242 }
243
244 hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
245
246 return 0;
247 }
248
hfsplus_create_attr(struct inode * inode,const char * name,const void * value,size_t size)249 int hfsplus_create_attr(struct inode *inode,
250 const char *name,
251 const void *value, size_t size)
252 {
253 struct super_block *sb = inode->i_sb;
254 struct hfs_find_data fd;
255 hfsplus_attr_entry *entry_ptr;
256 int err;
257
258 hfs_dbg("name %s, ino %ld\n",
259 name ? name : NULL, inode->i_ino);
260
261 if (!HFSPLUS_SB(sb)->attr_tree) {
262 pr_err("attributes file doesn't exist\n");
263 return -EINVAL;
264 }
265
266 entry_ptr = hfsplus_alloc_attr_entry();
267 if (!entry_ptr)
268 return -ENOMEM;
269
270 err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
271 if (err)
272 goto failed_init_create_attr;
273
274 /* Fail early and avoid ENOSPC during the btree operation */
275 err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
276 if (err)
277 goto failed_create_attr;
278
279 err = hfsplus_create_attr_nolock(inode, name, value, size,
280 &fd, entry_ptr);
281 if (err)
282 goto failed_create_attr;
283
284 failed_create_attr:
285 hfs_find_exit(&fd);
286
287 failed_init_create_attr:
288 hfsplus_destroy_attr_entry(entry_ptr);
289 return err;
290 }
291
__hfsplus_delete_attr(struct inode * inode,u32 cnid,struct hfs_find_data * fd)292 static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
293 struct hfs_find_data *fd)
294 {
295 int err = 0;
296 __be32 found_cnid, record_type;
297
298 hfs_bnode_read(fd->bnode, &found_cnid,
299 fd->keyoffset +
300 offsetof(struct hfsplus_attr_key, cnid),
301 sizeof(__be32));
302 if (cnid != be32_to_cpu(found_cnid))
303 return -ENOENT;
304
305 hfs_bnode_read(fd->bnode, &record_type,
306 fd->entryoffset, sizeof(record_type));
307
308 switch (be32_to_cpu(record_type)) {
309 case HFSPLUS_ATTR_INLINE_DATA:
310 /* All is OK. Do nothing. */
311 break;
312 case HFSPLUS_ATTR_FORK_DATA:
313 case HFSPLUS_ATTR_EXTENTS:
314 pr_err("only inline data xattr are supported\n");
315 return -EOPNOTSUPP;
316 default:
317 pr_err("invalid extended attribute record\n");
318 return -ENOENT;
319 }
320
321 /* Avoid btree corruption */
322 hfs_bnode_read(fd->bnode, fd->search_key,
323 fd->keyoffset, fd->keylength);
324
325 err = hfs_brec_remove(fd);
326 if (err)
327 return err;
328
329 hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
330 return err;
331 }
332
333 static
hfsplus_delete_attr_nolock(struct inode * inode,const char * name,struct hfs_find_data * fd)334 int hfsplus_delete_attr_nolock(struct inode *inode, const char *name,
335 struct hfs_find_data *fd)
336 {
337 struct super_block *sb = inode->i_sb;
338 int err;
339
340 hfs_dbg("name %s, ino %ld\n",
341 name ? name : NULL, inode->i_ino);
342
343 if (name) {
344 err = hfsplus_attr_build_key(sb, fd->search_key,
345 inode->i_ino, name);
346 if (err)
347 return err;
348 } else {
349 pr_err("invalid extended attribute name\n");
350 return -EINVAL;
351 }
352
353 err = hfs_brec_find(fd, hfs_find_rec_by_key);
354 if (err)
355 return err;
356
357 err = __hfsplus_delete_attr(inode, inode->i_ino, fd);
358 if (err)
359 return err;
360
361 return 0;
362 }
363
hfsplus_delete_attr(struct inode * inode,const char * name)364 int hfsplus_delete_attr(struct inode *inode, const char *name)
365 {
366 int err = 0;
367 struct super_block *sb = inode->i_sb;
368 struct hfs_find_data fd;
369
370 hfs_dbg("name %s, ino %ld\n",
371 name ? name : NULL, inode->i_ino);
372
373 if (!HFSPLUS_SB(sb)->attr_tree) {
374 pr_err("attributes file doesn't exist\n");
375 return -EINVAL;
376 }
377
378 err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
379 if (err)
380 return err;
381
382 /* Fail early and avoid ENOSPC during the btree operation */
383 err = hfs_bmap_reserve(fd.tree, fd.tree->depth);
384 if (err)
385 goto out;
386
387 err = hfsplus_delete_attr_nolock(inode, name, &fd);
388 if (err)
389 goto out;
390
391 out:
392 hfs_find_exit(&fd);
393 return err;
394 }
395
hfsplus_delete_all_attrs(struct inode * dir,u32 cnid)396 int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid)
397 {
398 int err = 0;
399 struct hfs_find_data fd;
400
401 hfs_dbg("cnid %d\n", cnid);
402
403 if (!HFSPLUS_SB(dir->i_sb)->attr_tree) {
404 pr_err("attributes file doesn't exist\n");
405 return -EINVAL;
406 }
407
408 err = hfs_find_init(HFSPLUS_SB(dir->i_sb)->attr_tree, &fd);
409 if (err)
410 return err;
411
412 for (;;) {
413 err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd);
414 if (err) {
415 if (err != -ENOENT)
416 pr_err("xattr search failed\n");
417 goto end_delete_all;
418 }
419
420 err = __hfsplus_delete_attr(dir, cnid, &fd);
421 if (err)
422 goto end_delete_all;
423 }
424
425 end_delete_all:
426 hfs_find_exit(&fd);
427 return err;
428 }
429
hfsplus_replace_attr(struct inode * inode,const char * name,const void * value,size_t size)430 int hfsplus_replace_attr(struct inode *inode,
431 const char *name,
432 const void *value, size_t size)
433 {
434 struct super_block *sb = inode->i_sb;
435 struct hfs_find_data fd;
436 hfsplus_attr_entry *entry_ptr;
437 int err = 0;
438
439 hfs_dbg("name %s, ino %ld\n",
440 name ? name : NULL, inode->i_ino);
441
442 if (!HFSPLUS_SB(sb)->attr_tree) {
443 pr_err("attributes file doesn't exist\n");
444 return -EINVAL;
445 }
446
447 entry_ptr = hfsplus_alloc_attr_entry();
448 if (!entry_ptr)
449 return -ENOMEM;
450
451 err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
452 if (err)
453 goto failed_init_replace_attr;
454
455 /* Fail early and avoid ENOSPC during the btree operation */
456 err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
457 if (err)
458 goto failed_replace_attr;
459
460 err = hfsplus_delete_attr_nolock(inode, name, &fd);
461 if (err)
462 goto failed_replace_attr;
463
464 err = hfsplus_create_attr_nolock(inode, name, value, size,
465 &fd, entry_ptr);
466 if (err)
467 goto failed_replace_attr;
468
469 failed_replace_attr:
470 hfs_find_exit(&fd);
471
472 failed_init_replace_attr:
473 hfsplus_destroy_attr_entry(entry_ptr);
474 return err;
475 }
476