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 | |
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(name: "hfsplus_attr_cache" , |
22 | size: sizeof(hfsplus_attr_entry), align: 0, |
23 | SLAB_HWCACHE_ALIGN, NULL); |
24 | if (!hfsplus_attr_tree_cachep) |
25 | return -ENOMEM; |
26 | |
27 | return 0; |
28 | } |
29 | |
30 | void hfsplus_destroy_attr_tree_cache(void) |
31 | { |
32 | kmem_cache_destroy(s: hfsplus_attr_tree_cachep); |
33 | } |
34 | |
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 | s1: (const struct hfsplus_unistr *)&k1->attr.key_name, |
47 | s2: (const struct hfsplus_unistr *)&k2->attr.key_name); |
48 | } |
49 | |
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 | ustr: (struct hfsplus_unistr *)&key->attr.key_name, |
60 | HFSPLUS_ATTR_MAX_STRLEN, astr: 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 | |
83 | hfsplus_attr_entry *hfsplus_alloc_attr_entry(void) |
84 | { |
85 | return kmem_cache_alloc(cachep: hfsplus_attr_tree_cachep, GFP_KERNEL); |
86 | } |
87 | |
88 | void hfsplus_destroy_attr_entry(hfsplus_attr_entry *entry) |
89 | { |
90 | if (entry) |
91 | kmem_cache_free(s: hfsplus_attr_tree_cachep, objp: entry); |
92 | } |
93 | |
94 | #define HFSPLUS_INVALID_ATTR_RECORD -1 |
95 | |
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 | 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 | |
137 | int 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, key: fd->search_key, cnid, name); |
151 | if (err) |
152 | goto failed_find_attr; |
153 | err = hfs_brec_find(fd, do_key_compare: hfs_find_rec_by_key); |
154 | if (err) |
155 | goto failed_find_attr; |
156 | } else { |
157 | err = hfsplus_attr_build_key(sb, key: fd->search_key, cnid, NULL); |
158 | if (err) |
159 | goto failed_find_attr; |
160 | err = hfs_brec_find(fd, do_key_compare: hfs_find_1st_rec_by_cnid); |
161 | if (err) |
162 | goto failed_find_attr; |
163 | } |
164 | |
165 | failed_find_attr: |
166 | return err; |
167 | } |
168 | |
169 | int 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(tree: HFSPLUS_SB(sb)->attr_tree, fd: &fd); |
179 | if (err) |
180 | return 0; |
181 | |
182 | err = hfsplus_find_attr(sb, cnid: inode->i_ino, name, fd: &fd); |
183 | if (err) |
184 | goto attr_not_found; |
185 | |
186 | hfs_find_exit(fd: &fd); |
187 | return 1; |
188 | |
189 | attr_not_found: |
190 | hfs_find_exit(fd: &fd); |
191 | return 0; |
192 | } |
193 | |
194 | int 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(tree: HFSPLUS_SB(sb)->attr_tree, fd: &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(tree: fd.tree, rsvd_nodes: fd.tree->depth + 1); |
222 | if (err) |
223 | goto failed_create_attr; |
224 | |
225 | if (name) { |
226 | err = hfsplus_attr_build_key(sb, key: fd.search_key, |
227 | cnid: 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: entry_ptr, |
237 | HFSPLUS_ATTR_INLINE_DATA, |
238 | cnid: 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: &fd, do_key_compare: 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: &fd, entry: entry_ptr, entry_len: entry_size); |
253 | if (err) |
254 | goto failed_create_attr; |
255 | |
256 | hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY); |
257 | |
258 | failed_create_attr: |
259 | hfs_find_exit(fd: &fd); |
260 | |
261 | failed_init_create_attr: |
262 | hfsplus_destroy_attr_entry(entry: entry_ptr); |
263 | return err; |
264 | } |
265 | |
266 | static 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(node: fd->bnode, buf: &found_cnid, |
273 | off: fd->keyoffset + |
274 | offsetof(struct hfsplus_attr_key, cnid), |
275 | len: sizeof(__be32)); |
276 | if (cnid != be32_to_cpu(found_cnid)) |
277 | return -ENOENT; |
278 | |
279 | hfs_bnode_read(node: fd->bnode, buf: &record_type, |
280 | off: fd->entryoffset, len: 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(node: fd->bnode, buf: fd->search_key, |
297 | off: fd->keyoffset, len: 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 | |
307 | int 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(tree: HFSPLUS_SB(sb)->attr_tree, fd: &fd); |
322 | if (err) |
323 | return err; |
324 | |
325 | /* Fail early and avoid ENOSPC during the btree operation */ |
326 | err = hfs_bmap_reserve(tree: fd.tree, rsvd_nodes: fd.tree->depth); |
327 | if (err) |
328 | goto out; |
329 | |
330 | if (name) { |
331 | err = hfsplus_attr_build_key(sb, key: fd.search_key, |
332 | cnid: 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: &fd, do_key_compare: hfs_find_rec_by_key); |
342 | if (err) |
343 | goto out; |
344 | |
345 | err = __hfsplus_delete_attr(inode, cnid: inode->i_ino, fd: &fd); |
346 | if (err) |
347 | goto out; |
348 | |
349 | out: |
350 | hfs_find_exit(fd: &fd); |
351 | return err; |
352 | } |
353 | |
354 | int 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(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(tree: HFSPLUS_SB(sb: dir->i_sb)->attr_tree, fd: &fd); |
367 | if (err) |
368 | return err; |
369 | |
370 | for (;;) { |
371 | err = hfsplus_find_attr(sb: dir->i_sb, cnid, NULL, fd: &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(inode: dir, cnid, fd: &fd); |
379 | if (err) |
380 | goto end_delete_all; |
381 | } |
382 | |
383 | end_delete_all: |
384 | hfs_find_exit(fd: &fd); |
385 | return err; |
386 | } |
387 | |