]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
3e05ca20 VD |
2 | /* |
3 | * linux/fs/hfsplus/attributes.c | |
4 | * | |
5 | * Vyacheslav Dubeyko <[email protected]> | |
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 | ||
c11e614d | 15 | int __init hfsplus_create_attr_tree_cache(void) |
3e05ca20 VD |
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 | ||
30 | void hfsplus_destroy_attr_tree_cache(void) | |
31 | { | |
32 | kmem_cache_destroy(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 | (const struct hfsplus_unistr *)&k1->attr.key_name, | |
47 | (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) { | |
bf29e886 | 58 | int res = hfsplus_asc2uni(sb, |
3e05ca20 | 59 | (struct hfsplus_unistr *)&key->attr.key_name, |
bf29e886 HTL |
60 | HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name)); |
61 | if (res) | |
62 | return res; | |
3e05ca20 VD |
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 | ||
3e05ca20 VD |
83 | hfsplus_attr_entry *hfsplus_alloc_attr_entry(void) |
84 | { | |
85 | return kmem_cache_alloc(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(hfsplus_attr_tree_cachep, 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 | ||
c2b3e1f7 | 142 | hfs_dbg(ATTR_MOD, "find_attr: %s,%d\n", name ? name : NULL, cnid); |
3e05ca20 VD |
143 | |
144 | if (!HFSPLUS_SB(sb)->attr_tree) { | |
d6142673 | 145 | pr_err("attributes file doesn't exist\n"); |
3e05ca20 VD |
146 | return -EINVAL; |
147 | } | |
148 | ||
149 | if (name) { | |
150 | err = hfsplus_attr_build_key(sb, fd->search_key, cnid, name); | |
151 | if (err) | |
152 | goto failed_find_attr; | |
153 | err = hfs_brec_find(fd, hfs_find_rec_by_key); | |
154 | if (err) | |
155 | goto failed_find_attr; | |
156 | } else { | |
157 | err = hfsplus_attr_build_key(sb, fd->search_key, cnid, NULL); | |
158 | if (err) | |
159 | goto failed_find_attr; | |
160 | err = hfs_brec_find(fd, 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(HFSPLUS_SB(sb)->attr_tree, &fd); | |
179 | if (err) | |
180 | return 0; | |
181 | ||
182 | err = hfsplus_find_attr(sb, inode->i_ino, name, &fd); | |
183 | if (err) | |
184 | goto attr_not_found; | |
185 | ||
186 | hfs_find_exit(&fd); | |
187 | return 1; | |
188 | ||
189 | attr_not_found: | |
190 | hfs_find_exit(&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 | ||
c2b3e1f7 | 204 | hfs_dbg(ATTR_MOD, "create_attr: %s,%ld\n", |
3e05ca20 VD |
205 | name ? name : NULL, inode->i_ino); |
206 | ||
207 | if (!HFSPLUS_SB(sb)->attr_tree) { | |
d6142673 | 208 | pr_err("attributes file doesn't exist\n"); |
3e05ca20 VD |
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(HFSPLUS_SB(sb)->attr_tree, &fd); | |
217 | if (err) | |
218 | goto failed_init_create_attr; | |
219 | ||
d92915c3 EF |
220 | /* Fail early and avoid ENOSPC during the btree operation */ |
221 | err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1); | |
222 | if (err) | |
223 | goto failed_create_attr; | |
224 | ||
3e05ca20 VD |
225 | if (name) { |
226 | err = hfsplus_attr_build_key(sb, fd.search_key, | |
227 | 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_ptr, | |
237 | HFSPLUS_ATTR_INLINE_DATA, | |
238 | 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, 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, entry_ptr, 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); | |
260 | ||
261 | failed_init_create_attr: | |
262 | hfsplus_destroy_attr_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(fd->bnode, &found_cnid, | |
273 | fd->keyoffset + | |
274 | offsetof(struct hfsplus_attr_key, cnid), | |
275 | sizeof(__be32)); | |
276 | if (cnid != be32_to_cpu(found_cnid)) | |
277 | return -ENOENT; | |
278 | ||
279 | hfs_bnode_read(fd->bnode, &record_type, | |
280 | fd->entryoffset, 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: | |
d6142673 | 288 | pr_err("only inline data xattr are supported\n"); |
3e05ca20 VD |
289 | return -EOPNOTSUPP; |
290 | default: | |
d6142673 | 291 | pr_err("invalid extended attribute record\n"); |
3e05ca20 VD |
292 | return -ENOENT; |
293 | } | |
294 | ||
295 | err = hfs_brec_remove(fd); | |
296 | if (err) | |
297 | return err; | |
298 | ||
299 | hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY); | |
300 | return err; | |
301 | } | |
302 | ||
303 | int hfsplus_delete_attr(struct inode *inode, const char *name) | |
304 | { | |
305 | int err = 0; | |
306 | struct super_block *sb = inode->i_sb; | |
307 | struct hfs_find_data fd; | |
308 | ||
c2b3e1f7 | 309 | hfs_dbg(ATTR_MOD, "delete_attr: %s,%ld\n", |
3e05ca20 VD |
310 | name ? name : NULL, inode->i_ino); |
311 | ||
312 | if (!HFSPLUS_SB(sb)->attr_tree) { | |
d6142673 | 313 | pr_err("attributes file doesn't exist\n"); |
3e05ca20 VD |
314 | return -EINVAL; |
315 | } | |
316 | ||
317 | err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd); | |
318 | if (err) | |
319 | return err; | |
320 | ||
d92915c3 EF |
321 | /* Fail early and avoid ENOSPC during the btree operation */ |
322 | err = hfs_bmap_reserve(fd.tree, fd.tree->depth); | |
323 | if (err) | |
324 | goto out; | |
325 | ||
3e05ca20 VD |
326 | if (name) { |
327 | err = hfsplus_attr_build_key(sb, fd.search_key, | |
328 | inode->i_ino, name); | |
329 | if (err) | |
330 | goto out; | |
331 | } else { | |
d6142673 | 332 | pr_err("invalid extended attribute name\n"); |
3e05ca20 VD |
333 | err = -EINVAL; |
334 | goto out; | |
335 | } | |
336 | ||
337 | err = hfs_brec_find(&fd, hfs_find_rec_by_key); | |
338 | if (err) | |
339 | goto out; | |
340 | ||
341 | err = __hfsplus_delete_attr(inode, inode->i_ino, &fd); | |
342 | if (err) | |
343 | goto out; | |
344 | ||
345 | out: | |
346 | hfs_find_exit(&fd); | |
347 | return err; | |
348 | } | |
349 | ||
350 | int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid) | |
351 | { | |
352 | int err = 0; | |
353 | struct hfs_find_data fd; | |
354 | ||
c2b3e1f7 | 355 | hfs_dbg(ATTR_MOD, "delete_all_attrs: %d\n", cnid); |
3e05ca20 VD |
356 | |
357 | if (!HFSPLUS_SB(dir->i_sb)->attr_tree) { | |
d6142673 | 358 | pr_err("attributes file doesn't exist\n"); |
3e05ca20 VD |
359 | return -EINVAL; |
360 | } | |
361 | ||
362 | err = hfs_find_init(HFSPLUS_SB(dir->i_sb)->attr_tree, &fd); | |
363 | if (err) | |
364 | return err; | |
365 | ||
366 | for (;;) { | |
367 | err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd); | |
368 | if (err) { | |
369 | if (err != -ENOENT) | |
d6142673 | 370 | pr_err("xattr search failed\n"); |
3e05ca20 VD |
371 | goto end_delete_all; |
372 | } | |
373 | ||
374 | err = __hfsplus_delete_attr(dir, cnid, &fd); | |
375 | if (err) | |
376 | goto end_delete_all; | |
377 | } | |
378 | ||
379 | end_delete_all: | |
380 | hfs_find_exit(&fd); | |
381 | return err; | |
382 | } |