1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * linux/fs/hfsplus/xattr.c |
4 | * |
5 | * Vyacheslav Dubeyko <slava@dubeyko.com> |
6 | * |
7 | * Logic of processing extended attributes |
8 | */ |
9 | |
10 | #include "hfsplus_fs.h" |
11 | #include <linux/nls.h> |
12 | #include "xattr.h" |
13 | |
14 | static int hfsplus_removexattr(struct inode *inode, const char *name); |
15 | |
16 | const struct xattr_handler * const hfsplus_xattr_handlers[] = { |
17 | &hfsplus_xattr_osx_handler, |
18 | &hfsplus_xattr_user_handler, |
19 | &hfsplus_xattr_trusted_handler, |
20 | &hfsplus_xattr_security_handler, |
21 | NULL |
22 | }; |
23 | |
24 | static int strcmp_xattr_finder_info(const char *name) |
25 | { |
26 | if (name) { |
27 | return strncmp(name, HFSPLUS_XATTR_FINDER_INFO_NAME, |
28 | sizeof(HFSPLUS_XATTR_FINDER_INFO_NAME)); |
29 | } |
30 | return -1; |
31 | } |
32 | |
33 | static int strcmp_xattr_acl(const char *name) |
34 | { |
35 | if (name) { |
36 | return strncmp(name, HFSPLUS_XATTR_ACL_NAME, |
37 | sizeof(HFSPLUS_XATTR_ACL_NAME)); |
38 | } |
39 | return -1; |
40 | } |
41 | |
42 | static bool is_known_namespace(const char *name) |
43 | { |
44 | if (strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) && |
45 | strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) && |
46 | strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) && |
47 | strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN)) |
48 | return false; |
49 | |
50 | return true; |
51 | } |
52 | |
53 | static void (struct inode *attr_file, |
54 | u32 clump_size, |
55 | char *buf, u16 node_size) |
56 | { |
57 | struct hfs_bnode_desc *desc; |
58 | struct hfs_btree_header_rec *head; |
59 | u16 offset; |
60 | __be16 *rec_offsets; |
61 | u32 hdr_node_map_rec_bits; |
62 | char *bmp; |
63 | u32 used_nodes; |
64 | u32 used_bmp_bytes; |
65 | u64 tmp; |
66 | |
67 | hfs_dbg(ATTR_MOD, "init_hdr_attr_file: clump %u, node_size %u\n" , |
68 | clump_size, node_size); |
69 | |
70 | /* The end of the node contains list of record offsets */ |
71 | rec_offsets = (__be16 *)(buf + node_size); |
72 | |
73 | desc = (struct hfs_bnode_desc *)buf; |
74 | desc->type = HFS_NODE_HEADER; |
75 | desc->num_recs = cpu_to_be16(HFSPLUS_BTREE_HDR_NODE_RECS_COUNT); |
76 | offset = sizeof(struct hfs_bnode_desc); |
77 | *--rec_offsets = cpu_to_be16(offset); |
78 | |
79 | head = (struct hfs_btree_header_rec *)(buf + offset); |
80 | head->node_size = cpu_to_be16(node_size); |
81 | tmp = i_size_read(inode: attr_file); |
82 | do_div(tmp, node_size); |
83 | head->node_count = cpu_to_be32(tmp); |
84 | head->free_nodes = cpu_to_be32(be32_to_cpu(head->node_count) - 1); |
85 | head->clump_size = cpu_to_be32(clump_size); |
86 | head->attributes |= cpu_to_be32(HFS_TREE_BIGKEYS | HFS_TREE_VARIDXKEYS); |
87 | head->max_key_len = cpu_to_be16(HFSPLUS_ATTR_KEYLEN - sizeof(u16)); |
88 | offset += sizeof(struct hfs_btree_header_rec); |
89 | *--rec_offsets = cpu_to_be16(offset); |
90 | offset += HFSPLUS_BTREE_HDR_USER_BYTES; |
91 | *--rec_offsets = cpu_to_be16(offset); |
92 | |
93 | hdr_node_map_rec_bits = 8 * (node_size - offset - (4 * sizeof(u16))); |
94 | if (be32_to_cpu(head->node_count) > hdr_node_map_rec_bits) { |
95 | u32 map_node_bits; |
96 | u32 map_nodes; |
97 | |
98 | desc->next = cpu_to_be32(be32_to_cpu(head->leaf_tail) + 1); |
99 | map_node_bits = 8 * (node_size - sizeof(struct hfs_bnode_desc) - |
100 | (2 * sizeof(u16)) - 2); |
101 | map_nodes = (be32_to_cpu(head->node_count) - |
102 | hdr_node_map_rec_bits + |
103 | (map_node_bits - 1)) / map_node_bits; |
104 | be32_add_cpu(var: &head->free_nodes, val: 0 - map_nodes); |
105 | } |
106 | |
107 | bmp = buf + offset; |
108 | used_nodes = |
109 | be32_to_cpu(head->node_count) - be32_to_cpu(head->free_nodes); |
110 | used_bmp_bytes = used_nodes / 8; |
111 | if (used_bmp_bytes) { |
112 | memset(bmp, 0xFF, used_bmp_bytes); |
113 | bmp += used_bmp_bytes; |
114 | used_nodes %= 8; |
115 | } |
116 | *bmp = ~(0xFF >> used_nodes); |
117 | offset += hdr_node_map_rec_bits / 8; |
118 | *--rec_offsets = cpu_to_be16(offset); |
119 | } |
120 | |
121 | static int hfsplus_create_attributes_file(struct super_block *sb) |
122 | { |
123 | int err = 0; |
124 | struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); |
125 | struct inode *attr_file; |
126 | struct hfsplus_inode_info *hip; |
127 | u32 clump_size; |
128 | u16 node_size = HFSPLUS_ATTR_TREE_NODE_SIZE; |
129 | char *buf; |
130 | int index, written; |
131 | struct address_space *mapping; |
132 | struct page *page; |
133 | int old_state = HFSPLUS_EMPTY_ATTR_TREE; |
134 | |
135 | hfs_dbg(ATTR_MOD, "create_attr_file: ino %d\n" , HFSPLUS_ATTR_CNID); |
136 | |
137 | check_attr_tree_state_again: |
138 | switch (atomic_read(v: &sbi->attr_tree_state)) { |
139 | case HFSPLUS_EMPTY_ATTR_TREE: |
140 | if (old_state != atomic_cmpxchg(v: &sbi->attr_tree_state, |
141 | old: old_state, |
142 | HFSPLUS_CREATING_ATTR_TREE)) |
143 | goto check_attr_tree_state_again; |
144 | break; |
145 | case HFSPLUS_CREATING_ATTR_TREE: |
146 | /* |
147 | * This state means that another thread is in process |
148 | * of AttributesFile creation. Theoretically, it is |
149 | * possible to be here. But really __setxattr() method |
150 | * first of all calls hfs_find_init() for lookup in |
151 | * B-tree of CatalogFile. This method locks mutex of |
152 | * CatalogFile's B-tree. As a result, if some thread |
153 | * is inside AttributedFile creation operation then |
154 | * another threads will be waiting unlocking of |
155 | * CatalogFile's B-tree's mutex. However, if code will |
156 | * change then we will return error code (-EAGAIN) from |
157 | * here. Really, it means that first try to set of xattr |
158 | * fails with error but second attempt will have success. |
159 | */ |
160 | return -EAGAIN; |
161 | case HFSPLUS_VALID_ATTR_TREE: |
162 | return 0; |
163 | case HFSPLUS_FAILED_ATTR_TREE: |
164 | return -EOPNOTSUPP; |
165 | default: |
166 | BUG(); |
167 | } |
168 | |
169 | attr_file = hfsplus_iget(sb, HFSPLUS_ATTR_CNID); |
170 | if (IS_ERR(ptr: attr_file)) { |
171 | pr_err("failed to load attributes file\n" ); |
172 | return PTR_ERR(ptr: attr_file); |
173 | } |
174 | |
175 | BUG_ON(i_size_read(attr_file) != 0); |
176 | |
177 | hip = HFSPLUS_I(inode: attr_file); |
178 | |
179 | clump_size = hfsplus_calc_btree_clump_size(block_size: sb->s_blocksize, |
180 | node_size, |
181 | sectors: sbi->sect_count, |
182 | HFSPLUS_ATTR_CNID); |
183 | |
184 | mutex_lock(&hip->extents_lock); |
185 | hip->clump_blocks = clump_size >> sbi->alloc_blksz_shift; |
186 | mutex_unlock(lock: &hip->extents_lock); |
187 | |
188 | if (sbi->free_blocks <= (hip->clump_blocks << 1)) { |
189 | err = -ENOSPC; |
190 | goto end_attr_file_creation; |
191 | } |
192 | |
193 | while (hip->alloc_blocks < hip->clump_blocks) { |
194 | err = hfsplus_file_extend(inode: attr_file, zeroout: false); |
195 | if (unlikely(err)) { |
196 | pr_err("failed to extend attributes file\n" ); |
197 | goto end_attr_file_creation; |
198 | } |
199 | hip->phys_size = attr_file->i_size = |
200 | (loff_t)hip->alloc_blocks << sbi->alloc_blksz_shift; |
201 | hip->fs_blocks = hip->alloc_blocks << sbi->fs_shift; |
202 | inode_set_bytes(inode: attr_file, bytes: attr_file->i_size); |
203 | } |
204 | |
205 | buf = kzalloc(size: node_size, GFP_NOFS); |
206 | if (!buf) { |
207 | err = -ENOMEM; |
208 | goto end_attr_file_creation; |
209 | } |
210 | |
211 | hfsplus_init_header_node(attr_file, clump_size, buf, node_size); |
212 | |
213 | mapping = attr_file->i_mapping; |
214 | |
215 | index = 0; |
216 | written = 0; |
217 | for (; written < node_size; index++, written += PAGE_SIZE) { |
218 | void *kaddr; |
219 | |
220 | page = read_mapping_page(mapping, index, NULL); |
221 | if (IS_ERR(ptr: page)) { |
222 | err = PTR_ERR(ptr: page); |
223 | goto failed_header_node_init; |
224 | } |
225 | |
226 | kaddr = kmap_atomic(page); |
227 | memcpy(kaddr, buf + written, |
228 | min_t(size_t, PAGE_SIZE, node_size - written)); |
229 | kunmap_atomic(kaddr); |
230 | |
231 | set_page_dirty(page); |
232 | put_page(page); |
233 | } |
234 | |
235 | hfsplus_mark_inode_dirty(inode: attr_file, HFSPLUS_I_ATTR_DIRTY); |
236 | |
237 | sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID); |
238 | if (!sbi->attr_tree) |
239 | pr_err("failed to load attributes file\n" ); |
240 | |
241 | : |
242 | kfree(objp: buf); |
243 | |
244 | end_attr_file_creation: |
245 | iput(attr_file); |
246 | |
247 | if (!err) |
248 | atomic_set(v: &sbi->attr_tree_state, HFSPLUS_VALID_ATTR_TREE); |
249 | else if (err == -ENOSPC) |
250 | atomic_set(v: &sbi->attr_tree_state, HFSPLUS_EMPTY_ATTR_TREE); |
251 | else |
252 | atomic_set(v: &sbi->attr_tree_state, HFSPLUS_FAILED_ATTR_TREE); |
253 | |
254 | return err; |
255 | } |
256 | |
257 | int __hfsplus_setxattr(struct inode *inode, const char *name, |
258 | const void *value, size_t size, int flags) |
259 | { |
260 | int err; |
261 | struct hfs_find_data cat_fd; |
262 | hfsplus_cat_entry entry; |
263 | u16 cat_entry_flags, cat_entry_type; |
264 | u16 folder_finderinfo_len = sizeof(struct DInfo) + |
265 | sizeof(struct DXInfo); |
266 | u16 file_finderinfo_len = sizeof(struct FInfo) + |
267 | sizeof(struct FXInfo); |
268 | |
269 | if ((!S_ISREG(inode->i_mode) && |
270 | !S_ISDIR(inode->i_mode)) || |
271 | HFSPLUS_IS_RSRC(inode)) |
272 | return -EOPNOTSUPP; |
273 | |
274 | if (value == NULL) |
275 | return hfsplus_removexattr(inode, name); |
276 | |
277 | err = hfs_find_init(tree: HFSPLUS_SB(sb: inode->i_sb)->cat_tree, fd: &cat_fd); |
278 | if (err) { |
279 | pr_err("can't init xattr find struct\n" ); |
280 | return err; |
281 | } |
282 | |
283 | err = hfsplus_find_cat(sb: inode->i_sb, cnid: inode->i_ino, fd: &cat_fd); |
284 | if (err) { |
285 | pr_err("catalog searching failed\n" ); |
286 | goto end_setxattr; |
287 | } |
288 | |
289 | if (!strcmp_xattr_finder_info(name)) { |
290 | if (flags & XATTR_CREATE) { |
291 | pr_err("xattr exists yet\n" ); |
292 | err = -EOPNOTSUPP; |
293 | goto end_setxattr; |
294 | } |
295 | hfs_bnode_read(node: cat_fd.bnode, buf: &entry, off: cat_fd.entryoffset, |
296 | len: sizeof(hfsplus_cat_entry)); |
297 | if (be16_to_cpu(entry.type) == HFSPLUS_FOLDER) { |
298 | if (size == folder_finderinfo_len) { |
299 | memcpy(&entry.folder.info, value, |
300 | folder_finderinfo_len); |
301 | hfs_bnode_write(node: cat_fd.bnode, buf: &entry, |
302 | off: cat_fd.entryoffset, |
303 | len: sizeof(struct hfsplus_cat_folder)); |
304 | hfsplus_mark_inode_dirty(inode, |
305 | HFSPLUS_I_CAT_DIRTY); |
306 | } else { |
307 | err = -ERANGE; |
308 | goto end_setxattr; |
309 | } |
310 | } else if (be16_to_cpu(entry.type) == HFSPLUS_FILE) { |
311 | if (size == file_finderinfo_len) { |
312 | memcpy(&entry.file.info, value, |
313 | file_finderinfo_len); |
314 | hfs_bnode_write(node: cat_fd.bnode, buf: &entry, |
315 | off: cat_fd.entryoffset, |
316 | len: sizeof(struct hfsplus_cat_file)); |
317 | hfsplus_mark_inode_dirty(inode, |
318 | HFSPLUS_I_CAT_DIRTY); |
319 | } else { |
320 | err = -ERANGE; |
321 | goto end_setxattr; |
322 | } |
323 | } else { |
324 | err = -EOPNOTSUPP; |
325 | goto end_setxattr; |
326 | } |
327 | goto end_setxattr; |
328 | } |
329 | |
330 | if (!HFSPLUS_SB(sb: inode->i_sb)->attr_tree) { |
331 | err = hfsplus_create_attributes_file(sb: inode->i_sb); |
332 | if (unlikely(err)) |
333 | goto end_setxattr; |
334 | } |
335 | |
336 | if (hfsplus_attr_exists(inode, name)) { |
337 | if (flags & XATTR_CREATE) { |
338 | pr_err("xattr exists yet\n" ); |
339 | err = -EOPNOTSUPP; |
340 | goto end_setxattr; |
341 | } |
342 | err = hfsplus_delete_attr(inode, name); |
343 | if (err) |
344 | goto end_setxattr; |
345 | err = hfsplus_create_attr(inode, name, value, size); |
346 | if (err) |
347 | goto end_setxattr; |
348 | } else { |
349 | if (flags & XATTR_REPLACE) { |
350 | pr_err("cannot replace xattr\n" ); |
351 | err = -EOPNOTSUPP; |
352 | goto end_setxattr; |
353 | } |
354 | err = hfsplus_create_attr(inode, name, value, size); |
355 | if (err) |
356 | goto end_setxattr; |
357 | } |
358 | |
359 | cat_entry_type = hfs_bnode_read_u16(node: cat_fd.bnode, off: cat_fd.entryoffset); |
360 | if (cat_entry_type == HFSPLUS_FOLDER) { |
361 | cat_entry_flags = hfs_bnode_read_u16(node: cat_fd.bnode, |
362 | off: cat_fd.entryoffset + |
363 | offsetof(struct hfsplus_cat_folder, flags)); |
364 | cat_entry_flags |= HFSPLUS_XATTR_EXISTS; |
365 | if (!strcmp_xattr_acl(name)) |
366 | cat_entry_flags |= HFSPLUS_ACL_EXISTS; |
367 | hfs_bnode_write_u16(node: cat_fd.bnode, off: cat_fd.entryoffset + |
368 | offsetof(struct hfsplus_cat_folder, flags), |
369 | data: cat_entry_flags); |
370 | hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY); |
371 | } else if (cat_entry_type == HFSPLUS_FILE) { |
372 | cat_entry_flags = hfs_bnode_read_u16(node: cat_fd.bnode, |
373 | off: cat_fd.entryoffset + |
374 | offsetof(struct hfsplus_cat_file, flags)); |
375 | cat_entry_flags |= HFSPLUS_XATTR_EXISTS; |
376 | if (!strcmp_xattr_acl(name)) |
377 | cat_entry_flags |= HFSPLUS_ACL_EXISTS; |
378 | hfs_bnode_write_u16(node: cat_fd.bnode, off: cat_fd.entryoffset + |
379 | offsetof(struct hfsplus_cat_file, flags), |
380 | data: cat_entry_flags); |
381 | hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY); |
382 | } else { |
383 | pr_err("invalid catalog entry type\n" ); |
384 | err = -EIO; |
385 | goto end_setxattr; |
386 | } |
387 | |
388 | end_setxattr: |
389 | hfs_find_exit(fd: &cat_fd); |
390 | return err; |
391 | } |
392 | |
393 | static int name_len(const char *xattr_name, int xattr_name_len) |
394 | { |
395 | int len = xattr_name_len + 1; |
396 | |
397 | if (!is_known_namespace(name: xattr_name)) |
398 | len += XATTR_MAC_OSX_PREFIX_LEN; |
399 | |
400 | return len; |
401 | } |
402 | |
403 | static int copy_name(char *buffer, const char *xattr_name, int name_len) |
404 | { |
405 | int len = name_len; |
406 | int offset = 0; |
407 | |
408 | if (!is_known_namespace(name: xattr_name)) { |
409 | memcpy(buffer, XATTR_MAC_OSX_PREFIX, XATTR_MAC_OSX_PREFIX_LEN); |
410 | offset += XATTR_MAC_OSX_PREFIX_LEN; |
411 | len += XATTR_MAC_OSX_PREFIX_LEN; |
412 | } |
413 | |
414 | strncpy(p: buffer + offset, q: xattr_name, size: name_len); |
415 | memset(buffer + offset + name_len, 0, 1); |
416 | len += 1; |
417 | |
418 | return len; |
419 | } |
420 | |
421 | int hfsplus_setxattr(struct inode *inode, const char *name, |
422 | const void *value, size_t size, int flags, |
423 | const char *prefix, size_t prefixlen) |
424 | { |
425 | char *xattr_name; |
426 | int res; |
427 | |
428 | xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, |
429 | GFP_KERNEL); |
430 | if (!xattr_name) |
431 | return -ENOMEM; |
432 | strcpy(p: xattr_name, q: prefix); |
433 | strcpy(p: xattr_name + prefixlen, q: name); |
434 | res = __hfsplus_setxattr(inode, name: xattr_name, value, size, flags); |
435 | kfree(objp: xattr_name); |
436 | return res; |
437 | } |
438 | |
439 | static ssize_t hfsplus_getxattr_finder_info(struct inode *inode, |
440 | void *value, size_t size) |
441 | { |
442 | ssize_t res = 0; |
443 | struct hfs_find_data fd; |
444 | u16 entry_type; |
445 | u16 folder_rec_len = sizeof(struct DInfo) + sizeof(struct DXInfo); |
446 | u16 file_rec_len = sizeof(struct FInfo) + sizeof(struct FXInfo); |
447 | u16 record_len = max(folder_rec_len, file_rec_len); |
448 | u8 folder_finder_info[sizeof(struct DInfo) + sizeof(struct DXInfo)]; |
449 | u8 file_finder_info[sizeof(struct FInfo) + sizeof(struct FXInfo)]; |
450 | |
451 | if (size >= record_len) { |
452 | res = hfs_find_init(tree: HFSPLUS_SB(sb: inode->i_sb)->cat_tree, fd: &fd); |
453 | if (res) { |
454 | pr_err("can't init xattr find struct\n" ); |
455 | return res; |
456 | } |
457 | res = hfsplus_find_cat(sb: inode->i_sb, cnid: inode->i_ino, fd: &fd); |
458 | if (res) |
459 | goto end_getxattr_finder_info; |
460 | entry_type = hfs_bnode_read_u16(node: fd.bnode, off: fd.entryoffset); |
461 | |
462 | if (entry_type == HFSPLUS_FOLDER) { |
463 | hfs_bnode_read(node: fd.bnode, buf: folder_finder_info, |
464 | off: fd.entryoffset + |
465 | offsetof(struct hfsplus_cat_folder, user_info), |
466 | len: folder_rec_len); |
467 | memcpy(value, folder_finder_info, folder_rec_len); |
468 | res = folder_rec_len; |
469 | } else if (entry_type == HFSPLUS_FILE) { |
470 | hfs_bnode_read(node: fd.bnode, buf: file_finder_info, |
471 | off: fd.entryoffset + |
472 | offsetof(struct hfsplus_cat_file, user_info), |
473 | len: file_rec_len); |
474 | memcpy(value, file_finder_info, file_rec_len); |
475 | res = file_rec_len; |
476 | } else { |
477 | res = -EOPNOTSUPP; |
478 | goto end_getxattr_finder_info; |
479 | } |
480 | } else |
481 | res = size ? -ERANGE : record_len; |
482 | |
483 | end_getxattr_finder_info: |
484 | if (size >= record_len) |
485 | hfs_find_exit(fd: &fd); |
486 | return res; |
487 | } |
488 | |
489 | ssize_t __hfsplus_getxattr(struct inode *inode, const char *name, |
490 | void *value, size_t size) |
491 | { |
492 | struct hfs_find_data fd; |
493 | hfsplus_attr_entry *entry; |
494 | __be32 xattr_record_type; |
495 | u32 record_type; |
496 | u16 record_length = 0; |
497 | ssize_t res; |
498 | |
499 | if ((!S_ISREG(inode->i_mode) && |
500 | !S_ISDIR(inode->i_mode)) || |
501 | HFSPLUS_IS_RSRC(inode)) |
502 | return -EOPNOTSUPP; |
503 | |
504 | if (!strcmp_xattr_finder_info(name)) |
505 | return hfsplus_getxattr_finder_info(inode, value, size); |
506 | |
507 | if (!HFSPLUS_SB(sb: inode->i_sb)->attr_tree) |
508 | return -EOPNOTSUPP; |
509 | |
510 | entry = hfsplus_alloc_attr_entry(); |
511 | if (!entry) { |
512 | pr_err("can't allocate xattr entry\n" ); |
513 | return -ENOMEM; |
514 | } |
515 | |
516 | res = hfs_find_init(tree: HFSPLUS_SB(sb: inode->i_sb)->attr_tree, fd: &fd); |
517 | if (res) { |
518 | pr_err("can't init xattr find struct\n" ); |
519 | goto failed_getxattr_init; |
520 | } |
521 | |
522 | res = hfsplus_find_attr(sb: inode->i_sb, cnid: inode->i_ino, name, fd: &fd); |
523 | if (res) { |
524 | if (res == -ENOENT) |
525 | res = -ENODATA; |
526 | else |
527 | pr_err("xattr searching failed\n" ); |
528 | goto out; |
529 | } |
530 | |
531 | hfs_bnode_read(node: fd.bnode, buf: &xattr_record_type, |
532 | off: fd.entryoffset, len: sizeof(xattr_record_type)); |
533 | record_type = be32_to_cpu(xattr_record_type); |
534 | if (record_type == HFSPLUS_ATTR_INLINE_DATA) { |
535 | record_length = hfs_bnode_read_u16(node: fd.bnode, |
536 | off: fd.entryoffset + |
537 | offsetof(struct hfsplus_attr_inline_data, |
538 | length)); |
539 | if (record_length > HFSPLUS_MAX_INLINE_DATA_SIZE) { |
540 | pr_err("invalid xattr record size\n" ); |
541 | res = -EIO; |
542 | goto out; |
543 | } |
544 | } else if (record_type == HFSPLUS_ATTR_FORK_DATA || |
545 | record_type == HFSPLUS_ATTR_EXTENTS) { |
546 | pr_err("only inline data xattr are supported\n" ); |
547 | res = -EOPNOTSUPP; |
548 | goto out; |
549 | } else { |
550 | pr_err("invalid xattr record\n" ); |
551 | res = -EIO; |
552 | goto out; |
553 | } |
554 | |
555 | if (size) { |
556 | hfs_bnode_read(node: fd.bnode, buf: entry, off: fd.entryoffset, |
557 | offsetof(struct hfsplus_attr_inline_data, |
558 | raw_bytes) + record_length); |
559 | } |
560 | |
561 | if (size >= record_length) { |
562 | memcpy(value, entry->inline_data.raw_bytes, record_length); |
563 | res = record_length; |
564 | } else |
565 | res = size ? -ERANGE : record_length; |
566 | |
567 | out: |
568 | hfs_find_exit(fd: &fd); |
569 | |
570 | failed_getxattr_init: |
571 | hfsplus_destroy_attr_entry(entry); |
572 | return res; |
573 | } |
574 | |
575 | ssize_t hfsplus_getxattr(struct inode *inode, const char *name, |
576 | void *value, size_t size, |
577 | const char *prefix, size_t prefixlen) |
578 | { |
579 | int res; |
580 | char *xattr_name; |
581 | |
582 | xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, |
583 | GFP_KERNEL); |
584 | if (!xattr_name) |
585 | return -ENOMEM; |
586 | |
587 | strcpy(p: xattr_name, q: prefix); |
588 | strcpy(p: xattr_name + prefixlen, q: name); |
589 | |
590 | res = __hfsplus_getxattr(inode, name: xattr_name, value, size); |
591 | kfree(objp: xattr_name); |
592 | return res; |
593 | |
594 | } |
595 | |
596 | static inline int can_list(const char *xattr_name) |
597 | { |
598 | if (!xattr_name) |
599 | return 0; |
600 | |
601 | return strncmp(xattr_name, XATTR_TRUSTED_PREFIX, |
602 | XATTR_TRUSTED_PREFIX_LEN) || |
603 | capable(CAP_SYS_ADMIN); |
604 | } |
605 | |
606 | static ssize_t hfsplus_listxattr_finder_info(struct dentry *dentry, |
607 | char *buffer, size_t size) |
608 | { |
609 | ssize_t res; |
610 | struct inode *inode = d_inode(dentry); |
611 | struct hfs_find_data fd; |
612 | u16 entry_type; |
613 | u8 folder_finder_info[sizeof(struct DInfo) + sizeof(struct DXInfo)]; |
614 | u8 file_finder_info[sizeof(struct FInfo) + sizeof(struct FXInfo)]; |
615 | unsigned long len, found_bit; |
616 | int xattr_name_len, symbols_count; |
617 | |
618 | res = hfs_find_init(tree: HFSPLUS_SB(sb: inode->i_sb)->cat_tree, fd: &fd); |
619 | if (res) { |
620 | pr_err("can't init xattr find struct\n" ); |
621 | return res; |
622 | } |
623 | |
624 | res = hfsplus_find_cat(sb: inode->i_sb, cnid: inode->i_ino, fd: &fd); |
625 | if (res) |
626 | goto end_listxattr_finder_info; |
627 | |
628 | entry_type = hfs_bnode_read_u16(node: fd.bnode, off: fd.entryoffset); |
629 | if (entry_type == HFSPLUS_FOLDER) { |
630 | len = sizeof(struct DInfo) + sizeof(struct DXInfo); |
631 | hfs_bnode_read(node: fd.bnode, buf: folder_finder_info, |
632 | off: fd.entryoffset + |
633 | offsetof(struct hfsplus_cat_folder, user_info), |
634 | len); |
635 | found_bit = find_first_bit(addr: (void *)folder_finder_info, size: len*8); |
636 | } else if (entry_type == HFSPLUS_FILE) { |
637 | len = sizeof(struct FInfo) + sizeof(struct FXInfo); |
638 | hfs_bnode_read(node: fd.bnode, buf: file_finder_info, |
639 | off: fd.entryoffset + |
640 | offsetof(struct hfsplus_cat_file, user_info), |
641 | len); |
642 | found_bit = find_first_bit(addr: (void *)file_finder_info, size: len*8); |
643 | } else { |
644 | res = -EOPNOTSUPP; |
645 | goto end_listxattr_finder_info; |
646 | } |
647 | |
648 | if (found_bit >= (len*8)) |
649 | res = 0; |
650 | else { |
651 | symbols_count = sizeof(HFSPLUS_XATTR_FINDER_INFO_NAME) - 1; |
652 | xattr_name_len = |
653 | name_len(HFSPLUS_XATTR_FINDER_INFO_NAME, xattr_name_len: symbols_count); |
654 | if (!buffer || !size) { |
655 | if (can_list(HFSPLUS_XATTR_FINDER_INFO_NAME)) |
656 | res = xattr_name_len; |
657 | } else if (can_list(HFSPLUS_XATTR_FINDER_INFO_NAME)) { |
658 | if (size < xattr_name_len) |
659 | res = -ERANGE; |
660 | else { |
661 | res = copy_name(buffer, |
662 | HFSPLUS_XATTR_FINDER_INFO_NAME, |
663 | name_len: symbols_count); |
664 | } |
665 | } |
666 | } |
667 | |
668 | end_listxattr_finder_info: |
669 | hfs_find_exit(fd: &fd); |
670 | |
671 | return res; |
672 | } |
673 | |
674 | ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size) |
675 | { |
676 | ssize_t err; |
677 | ssize_t res; |
678 | struct inode *inode = d_inode(dentry); |
679 | struct hfs_find_data fd; |
680 | struct hfsplus_attr_key attr_key; |
681 | char *strbuf; |
682 | int xattr_name_len; |
683 | |
684 | if ((!S_ISREG(inode->i_mode) && |
685 | !S_ISDIR(inode->i_mode)) || |
686 | HFSPLUS_IS_RSRC(inode)) |
687 | return -EOPNOTSUPP; |
688 | |
689 | res = hfsplus_listxattr_finder_info(dentry, buffer, size); |
690 | if (res < 0) |
691 | return res; |
692 | else if (!HFSPLUS_SB(sb: inode->i_sb)->attr_tree) |
693 | return (res == 0) ? -EOPNOTSUPP : res; |
694 | |
695 | err = hfs_find_init(tree: HFSPLUS_SB(sb: inode->i_sb)->attr_tree, fd: &fd); |
696 | if (err) { |
697 | pr_err("can't init xattr find struct\n" ); |
698 | return err; |
699 | } |
700 | |
701 | strbuf = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + |
702 | XATTR_MAC_OSX_PREFIX_LEN + 1, GFP_KERNEL); |
703 | if (!strbuf) { |
704 | res = -ENOMEM; |
705 | goto out; |
706 | } |
707 | |
708 | err = hfsplus_find_attr(sb: inode->i_sb, cnid: inode->i_ino, NULL, fd: &fd); |
709 | if (err) { |
710 | if (err == -ENOENT) { |
711 | if (res == 0) |
712 | res = -ENODATA; |
713 | goto end_listxattr; |
714 | } else { |
715 | res = err; |
716 | goto end_listxattr; |
717 | } |
718 | } |
719 | |
720 | for (;;) { |
721 | u16 key_len = hfs_bnode_read_u16(node: fd.bnode, off: fd.keyoffset); |
722 | |
723 | if (key_len == 0 || key_len > fd.tree->max_key_len) { |
724 | pr_err("invalid xattr key length: %d\n" , key_len); |
725 | res = -EIO; |
726 | goto end_listxattr; |
727 | } |
728 | |
729 | hfs_bnode_read(node: fd.bnode, buf: &attr_key, |
730 | off: fd.keyoffset, len: key_len + sizeof(key_len)); |
731 | |
732 | if (be32_to_cpu(attr_key.cnid) != inode->i_ino) |
733 | goto end_listxattr; |
734 | |
735 | xattr_name_len = NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN; |
736 | if (hfsplus_uni2asc(sb: inode->i_sb, |
737 | ustr: (const struct hfsplus_unistr *)&fd.key->attr.key_name, |
738 | astr: strbuf, len_p: &xattr_name_len)) { |
739 | pr_err("unicode conversion failed\n" ); |
740 | res = -EIO; |
741 | goto end_listxattr; |
742 | } |
743 | |
744 | if (!buffer || !size) { |
745 | if (can_list(xattr_name: strbuf)) |
746 | res += name_len(xattr_name: strbuf, xattr_name_len); |
747 | } else if (can_list(xattr_name: strbuf)) { |
748 | if (size < (res + name_len(xattr_name: strbuf, xattr_name_len))) { |
749 | res = -ERANGE; |
750 | goto end_listxattr; |
751 | } else |
752 | res += copy_name(buffer: buffer + res, |
753 | xattr_name: strbuf, name_len: xattr_name_len); |
754 | } |
755 | |
756 | if (hfs_brec_goto(fd: &fd, cnt: 1)) |
757 | goto end_listxattr; |
758 | } |
759 | |
760 | end_listxattr: |
761 | kfree(objp: strbuf); |
762 | out: |
763 | hfs_find_exit(fd: &fd); |
764 | return res; |
765 | } |
766 | |
767 | static int hfsplus_removexattr(struct inode *inode, const char *name) |
768 | { |
769 | int err; |
770 | struct hfs_find_data cat_fd; |
771 | u16 flags; |
772 | u16 cat_entry_type; |
773 | int is_xattr_acl_deleted; |
774 | int is_all_xattrs_deleted; |
775 | |
776 | if (!HFSPLUS_SB(sb: inode->i_sb)->attr_tree) |
777 | return -EOPNOTSUPP; |
778 | |
779 | if (!strcmp_xattr_finder_info(name)) |
780 | return -EOPNOTSUPP; |
781 | |
782 | err = hfs_find_init(tree: HFSPLUS_SB(sb: inode->i_sb)->cat_tree, fd: &cat_fd); |
783 | if (err) { |
784 | pr_err("can't init xattr find struct\n" ); |
785 | return err; |
786 | } |
787 | |
788 | err = hfsplus_find_cat(sb: inode->i_sb, cnid: inode->i_ino, fd: &cat_fd); |
789 | if (err) { |
790 | pr_err("catalog searching failed\n" ); |
791 | goto end_removexattr; |
792 | } |
793 | |
794 | err = hfsplus_delete_attr(inode, name); |
795 | if (err) |
796 | goto end_removexattr; |
797 | |
798 | is_xattr_acl_deleted = !strcmp_xattr_acl(name); |
799 | is_all_xattrs_deleted = !hfsplus_attr_exists(inode, NULL); |
800 | |
801 | if (!is_xattr_acl_deleted && !is_all_xattrs_deleted) |
802 | goto end_removexattr; |
803 | |
804 | cat_entry_type = hfs_bnode_read_u16(node: cat_fd.bnode, off: cat_fd.entryoffset); |
805 | |
806 | if (cat_entry_type == HFSPLUS_FOLDER) { |
807 | flags = hfs_bnode_read_u16(node: cat_fd.bnode, off: cat_fd.entryoffset + |
808 | offsetof(struct hfsplus_cat_folder, flags)); |
809 | if (is_xattr_acl_deleted) |
810 | flags &= ~HFSPLUS_ACL_EXISTS; |
811 | if (is_all_xattrs_deleted) |
812 | flags &= ~HFSPLUS_XATTR_EXISTS; |
813 | hfs_bnode_write_u16(node: cat_fd.bnode, off: cat_fd.entryoffset + |
814 | offsetof(struct hfsplus_cat_folder, flags), |
815 | data: flags); |
816 | hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY); |
817 | } else if (cat_entry_type == HFSPLUS_FILE) { |
818 | flags = hfs_bnode_read_u16(node: cat_fd.bnode, off: cat_fd.entryoffset + |
819 | offsetof(struct hfsplus_cat_file, flags)); |
820 | if (is_xattr_acl_deleted) |
821 | flags &= ~HFSPLUS_ACL_EXISTS; |
822 | if (is_all_xattrs_deleted) |
823 | flags &= ~HFSPLUS_XATTR_EXISTS; |
824 | hfs_bnode_write_u16(node: cat_fd.bnode, off: cat_fd.entryoffset + |
825 | offsetof(struct hfsplus_cat_file, flags), |
826 | data: flags); |
827 | hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY); |
828 | } else { |
829 | pr_err("invalid catalog entry type\n" ); |
830 | err = -EIO; |
831 | goto end_removexattr; |
832 | } |
833 | |
834 | end_removexattr: |
835 | hfs_find_exit(fd: &cat_fd); |
836 | return err; |
837 | } |
838 | |
839 | static int hfsplus_osx_getxattr(const struct xattr_handler *handler, |
840 | struct dentry *unused, struct inode *inode, |
841 | const char *name, void *buffer, size_t size) |
842 | { |
843 | /* |
844 | * Don't allow retrieving properly prefixed attributes |
845 | * by prepending them with "osx." |
846 | */ |
847 | if (is_known_namespace(name)) |
848 | return -EOPNOTSUPP; |
849 | |
850 | /* |
851 | * osx is the namespace we use to indicate an unprefixed |
852 | * attribute on the filesystem (like the ones that OS X |
853 | * creates), so we pass the name through unmodified (after |
854 | * ensuring it doesn't conflict with another namespace). |
855 | */ |
856 | return __hfsplus_getxattr(inode, name, value: buffer, size); |
857 | } |
858 | |
859 | static int hfsplus_osx_setxattr(const struct xattr_handler *handler, |
860 | struct mnt_idmap *idmap, |
861 | struct dentry *unused, struct inode *inode, |
862 | const char *name, const void *buffer, |
863 | size_t size, int flags) |
864 | { |
865 | /* |
866 | * Don't allow setting properly prefixed attributes |
867 | * by prepending them with "osx." |
868 | */ |
869 | if (is_known_namespace(name)) |
870 | return -EOPNOTSUPP; |
871 | |
872 | /* |
873 | * osx is the namespace we use to indicate an unprefixed |
874 | * attribute on the filesystem (like the ones that OS X |
875 | * creates), so we pass the name through unmodified (after |
876 | * ensuring it doesn't conflict with another namespace). |
877 | */ |
878 | return __hfsplus_setxattr(inode, name, value: buffer, size, flags); |
879 | } |
880 | |
881 | const struct xattr_handler hfsplus_xattr_osx_handler = { |
882 | .prefix = XATTR_MAC_OSX_PREFIX, |
883 | .get = hfsplus_osx_getxattr, |
884 | .set = hfsplus_osx_setxattr, |
885 | }; |
886 | |