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