Commit | Line | Data |
---|---|---|
3e05ca20 VD |
1 | /* |
2 | * linux/fs/hfsplus/attributes.c | |
3 | * | |
4 | * Vyacheslav Dubeyko <slava@dubeyko.com> | |
5 | * | |
6 | * Handling of records in attributes tree | |
7 | */ | |
8 | ||
9 | #include "hfsplus_fs.h" | |
10 | #include "hfsplus_raw.h" | |
11 | ||
12 | static struct kmem_cache *hfsplus_attr_tree_cachep; | |
13 | ||
14 | int hfsplus_create_attr_tree_cache(void) | |
15 | { | |
16 | if (hfsplus_attr_tree_cachep) | |
17 | return -EEXIST; | |
18 | ||
19 | hfsplus_attr_tree_cachep = | |
20 | kmem_cache_create("hfsplus_attr_cache", | |
21 | sizeof(hfsplus_attr_entry), 0, | |
22 | SLAB_HWCACHE_ALIGN, NULL); | |
23 | if (!hfsplus_attr_tree_cachep) | |
24 | return -ENOMEM; | |
25 | ||
26 | return 0; | |
27 | } | |
28 | ||
29 | void hfsplus_destroy_attr_tree_cache(void) | |
30 | { | |
31 | kmem_cache_destroy(hfsplus_attr_tree_cachep); | |
32 | } | |
33 | ||
34 | int hfsplus_attr_bin_cmp_key(const hfsplus_btree_key *k1, | |
35 | const hfsplus_btree_key *k2) | |
36 | { | |
37 | __be32 k1_cnid, k2_cnid; | |
38 | ||
39 | k1_cnid = k1->attr.cnid; | |
40 | k2_cnid = k2->attr.cnid; | |
41 | if (k1_cnid != k2_cnid) | |
42 | return be32_to_cpu(k1_cnid) < be32_to_cpu(k2_cnid) ? -1 : 1; | |
43 | ||
44 | return hfsplus_strcmp( | |
45 | (const struct hfsplus_unistr *)&k1->attr.key_name, | |
46 | (const struct hfsplus_unistr *)&k2->attr.key_name); | |
47 | } | |
48 | ||
49 | int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key, | |
50 | u32 cnid, const char *name) | |
51 | { | |
52 | int len; | |
53 | ||
54 | memset(key, 0, sizeof(struct hfsplus_attr_key)); | |
55 | key->attr.cnid = cpu_to_be32(cnid); | |
56 | if (name) { | |
57 | len = strlen(name); | |
58 | if (len > HFSPLUS_ATTR_MAX_STRLEN) { | |
59 | printk(KERN_ERR "hfs: invalid xattr name's length\n"); | |
60 | return -EINVAL; | |
61 | } | |
62 | hfsplus_asc2uni(sb, | |
63 | (struct hfsplus_unistr *)&key->attr.key_name, | |
64 | HFSPLUS_ATTR_MAX_STRLEN, name, len); | |
65 | len = be16_to_cpu(key->attr.key_name.length); | |
66 | } else { | |
67 | key->attr.key_name.length = 0; | |
68 | len = 0; | |
69 | } | |
70 | ||
71 | /* The length of the key, as stored in key_len field, does not include | |
72 | * the size of the key_len field itself. | |
73 | * So, offsetof(hfsplus_attr_key, key_name) is a trick because | |
74 | * it takes into consideration key_len field (__be16) of | |
75 | * hfsplus_attr_key structure instead of length field (__be16) of | |
76 | * hfsplus_attr_unistr structure. | |
77 | */ | |
78 | key->key_len = | |
79 | cpu_to_be16(offsetof(struct hfsplus_attr_key, key_name) + | |
80 | 2 * len); | |
81 | ||
82 | return 0; | |
83 | } | |
84 | ||
85 | void hfsplus_attr_build_key_uni(hfsplus_btree_key *key, | |
86 | u32 cnid, | |
87 | struct hfsplus_attr_unistr *name) | |
88 | { | |
89 | int ustrlen; | |
90 | ||
91 | memset(key, 0, sizeof(struct hfsplus_attr_key)); | |
92 | ustrlen = be16_to_cpu(name->length); | |
93 | key->attr.cnid = cpu_to_be32(cnid); | |
94 | key->attr.key_name.length = cpu_to_be16(ustrlen); | |
95 | ustrlen *= 2; | |
96 | memcpy(key->attr.key_name.unicode, name->unicode, ustrlen); | |
97 | ||
98 | /* The length of the key, as stored in key_len field, does not include | |
99 | * the size of the key_len field itself. | |
100 | * So, offsetof(hfsplus_attr_key, key_name) is a trick because | |
101 | * it takes into consideration key_len field (__be16) of | |
102 | * hfsplus_attr_key structure instead of length field (__be16) of | |
103 | * hfsplus_attr_unistr structure. | |
104 | */ | |
105 | key->key_len = | |
106 | cpu_to_be16(offsetof(struct hfsplus_attr_key, key_name) + | |
107 | ustrlen); | |
108 | } | |
109 | ||
110 | hfsplus_attr_entry *hfsplus_alloc_attr_entry(void) | |
111 | { | |
112 | return kmem_cache_alloc(hfsplus_attr_tree_cachep, GFP_KERNEL); | |
113 | } | |
114 | ||
115 | void hfsplus_destroy_attr_entry(hfsplus_attr_entry *entry) | |
116 | { | |
117 | if (entry) | |
118 | kmem_cache_free(hfsplus_attr_tree_cachep, entry); | |
119 | } | |
120 | ||
121 | #define HFSPLUS_INVALID_ATTR_RECORD -1 | |
122 | ||
123 | static int hfsplus_attr_build_record(hfsplus_attr_entry *entry, int record_type, | |
124 | u32 cnid, const void *value, size_t size) | |
125 | { | |
126 | if (record_type == HFSPLUS_ATTR_FORK_DATA) { | |
127 | /* | |
128 | * Mac OS X supports only inline data attributes. | |
129 | * Do nothing | |
130 | */ | |
131 | memset(entry, 0, sizeof(*entry)); | |
132 | return sizeof(struct hfsplus_attr_fork_data); | |
133 | } else if (record_type == HFSPLUS_ATTR_EXTENTS) { | |
134 | /* | |
135 | * Mac OS X supports only inline data attributes. | |
136 | * Do nothing. | |
137 | */ | |
138 | memset(entry, 0, sizeof(*entry)); | |
139 | return sizeof(struct hfsplus_attr_extents); | |
140 | } else if (record_type == HFSPLUS_ATTR_INLINE_DATA) { | |
141 | u16 len; | |
142 | ||
143 | memset(entry, 0, sizeof(struct hfsplus_attr_inline_data)); | |
144 | entry->inline_data.record_type = cpu_to_be32(record_type); | |
145 | if (size <= HFSPLUS_MAX_INLINE_DATA_SIZE) | |
146 | len = size; | |
147 | else | |
148 | return HFSPLUS_INVALID_ATTR_RECORD; | |
149 | entry->inline_data.length = cpu_to_be16(len); | |
150 | memcpy(entry->inline_data.raw_bytes, value, len); | |
151 | /* | |
152 | * Align len on two-byte boundary. | |
153 | * It needs to add pad byte if we have odd len. | |
154 | */ | |
155 | len = round_up(len, 2); | |
156 | return offsetof(struct hfsplus_attr_inline_data, raw_bytes) + | |
157 | len; | |
158 | } else /* invalid input */ | |
159 | memset(entry, 0, sizeof(*entry)); | |
160 | ||
161 | return HFSPLUS_INVALID_ATTR_RECORD; | |
162 | } | |
163 | ||
164 | int hfsplus_find_attr(struct super_block *sb, u32 cnid, | |
165 | const char *name, struct hfs_find_data *fd) | |
166 | { | |
167 | int err = 0; | |
168 | ||
c2b3e1f7 | 169 | hfs_dbg(ATTR_MOD, "find_attr: %s,%d\n", name ? name : NULL, cnid); |
3e05ca20 VD |
170 | |
171 | if (!HFSPLUS_SB(sb)->attr_tree) { | |
172 | printk(KERN_ERR "hfs: attributes file doesn't exist\n"); | |
173 | return -EINVAL; | |
174 | } | |
175 | ||
176 | if (name) { | |
177 | err = hfsplus_attr_build_key(sb, fd->search_key, cnid, name); | |
178 | if (err) | |
179 | goto failed_find_attr; | |
180 | err = hfs_brec_find(fd, hfs_find_rec_by_key); | |
181 | if (err) | |
182 | goto failed_find_attr; | |
183 | } else { | |
184 | err = hfsplus_attr_build_key(sb, fd->search_key, cnid, NULL); | |
185 | if (err) | |
186 | goto failed_find_attr; | |
187 | err = hfs_brec_find(fd, hfs_find_1st_rec_by_cnid); | |
188 | if (err) | |
189 | goto failed_find_attr; | |
190 | } | |
191 | ||
192 | failed_find_attr: | |
193 | return err; | |
194 | } | |
195 | ||
196 | int hfsplus_attr_exists(struct inode *inode, const char *name) | |
197 | { | |
198 | int err = 0; | |
199 | struct super_block *sb = inode->i_sb; | |
200 | struct hfs_find_data fd; | |
201 | ||
202 | if (!HFSPLUS_SB(sb)->attr_tree) | |
203 | return 0; | |
204 | ||
205 | err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd); | |
206 | if (err) | |
207 | return 0; | |
208 | ||
209 | err = hfsplus_find_attr(sb, inode->i_ino, name, &fd); | |
210 | if (err) | |
211 | goto attr_not_found; | |
212 | ||
213 | hfs_find_exit(&fd); | |
214 | return 1; | |
215 | ||
216 | attr_not_found: | |
217 | hfs_find_exit(&fd); | |
218 | return 0; | |
219 | } | |
220 | ||
221 | int hfsplus_create_attr(struct inode *inode, | |
222 | const char *name, | |
223 | const void *value, size_t size) | |
224 | { | |
225 | struct super_block *sb = inode->i_sb; | |
226 | struct hfs_find_data fd; | |
227 | hfsplus_attr_entry *entry_ptr; | |
228 | int entry_size; | |
229 | int err; | |
230 | ||
c2b3e1f7 | 231 | hfs_dbg(ATTR_MOD, "create_attr: %s,%ld\n", |
3e05ca20 VD |
232 | name ? name : NULL, inode->i_ino); |
233 | ||
234 | if (!HFSPLUS_SB(sb)->attr_tree) { | |
235 | printk(KERN_ERR "hfs: attributes file doesn't exist\n"); | |
236 | return -EINVAL; | |
237 | } | |
238 | ||
239 | entry_ptr = hfsplus_alloc_attr_entry(); | |
240 | if (!entry_ptr) | |
241 | return -ENOMEM; | |
242 | ||
243 | err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd); | |
244 | if (err) | |
245 | goto failed_init_create_attr; | |
246 | ||
247 | if (name) { | |
248 | err = hfsplus_attr_build_key(sb, fd.search_key, | |
249 | inode->i_ino, name); | |
250 | if (err) | |
251 | goto failed_create_attr; | |
252 | } else { | |
253 | err = -EINVAL; | |
254 | goto failed_create_attr; | |
255 | } | |
256 | ||
257 | /* Mac OS X supports only inline data attributes. */ | |
258 | entry_size = hfsplus_attr_build_record(entry_ptr, | |
259 | HFSPLUS_ATTR_INLINE_DATA, | |
260 | inode->i_ino, | |
261 | value, size); | |
262 | if (entry_size == HFSPLUS_INVALID_ATTR_RECORD) { | |
263 | err = -EINVAL; | |
264 | goto failed_create_attr; | |
265 | } | |
266 | ||
267 | err = hfs_brec_find(&fd, hfs_find_rec_by_key); | |
268 | if (err != -ENOENT) { | |
269 | if (!err) | |
270 | err = -EEXIST; | |
271 | goto failed_create_attr; | |
272 | } | |
273 | ||
274 | err = hfs_brec_insert(&fd, entry_ptr, entry_size); | |
275 | if (err) | |
276 | goto failed_create_attr; | |
277 | ||
278 | hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY); | |
279 | ||
280 | failed_create_attr: | |
281 | hfs_find_exit(&fd); | |
282 | ||
283 | failed_init_create_attr: | |
284 | hfsplus_destroy_attr_entry(entry_ptr); | |
285 | return err; | |
286 | } | |
287 | ||
288 | static int __hfsplus_delete_attr(struct inode *inode, u32 cnid, | |
289 | struct hfs_find_data *fd) | |
290 | { | |
291 | int err = 0; | |
292 | __be32 found_cnid, record_type; | |
293 | ||
294 | hfs_bnode_read(fd->bnode, &found_cnid, | |
295 | fd->keyoffset + | |
296 | offsetof(struct hfsplus_attr_key, cnid), | |
297 | sizeof(__be32)); | |
298 | if (cnid != be32_to_cpu(found_cnid)) | |
299 | return -ENOENT; | |
300 | ||
301 | hfs_bnode_read(fd->bnode, &record_type, | |
302 | fd->entryoffset, sizeof(record_type)); | |
303 | ||
304 | switch (be32_to_cpu(record_type)) { | |
305 | case HFSPLUS_ATTR_INLINE_DATA: | |
306 | /* All is OK. Do nothing. */ | |
307 | break; | |
308 | case HFSPLUS_ATTR_FORK_DATA: | |
309 | case HFSPLUS_ATTR_EXTENTS: | |
310 | printk(KERN_ERR "hfs: only inline data xattr are supported\n"); | |
311 | return -EOPNOTSUPP; | |
312 | default: | |
313 | printk(KERN_ERR "hfs: invalid extended attribute record\n"); | |
314 | return -ENOENT; | |
315 | } | |
316 | ||
317 | err = hfs_brec_remove(fd); | |
318 | if (err) | |
319 | return err; | |
320 | ||
321 | hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY); | |
322 | return err; | |
323 | } | |
324 | ||
325 | int hfsplus_delete_attr(struct inode *inode, const char *name) | |
326 | { | |
327 | int err = 0; | |
328 | struct super_block *sb = inode->i_sb; | |
329 | struct hfs_find_data fd; | |
330 | ||
c2b3e1f7 | 331 | hfs_dbg(ATTR_MOD, "delete_attr: %s,%ld\n", |
3e05ca20 VD |
332 | name ? name : NULL, inode->i_ino); |
333 | ||
334 | if (!HFSPLUS_SB(sb)->attr_tree) { | |
335 | printk(KERN_ERR "hfs: attributes file doesn't exist\n"); | |
336 | return -EINVAL; | |
337 | } | |
338 | ||
339 | err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd); | |
340 | if (err) | |
341 | return err; | |
342 | ||
343 | if (name) { | |
344 | err = hfsplus_attr_build_key(sb, fd.search_key, | |
345 | inode->i_ino, name); | |
346 | if (err) | |
347 | goto out; | |
348 | } else { | |
349 | printk(KERN_ERR "hfs: invalid extended attribute name\n"); | |
350 | err = -EINVAL; | |
351 | goto out; | |
352 | } | |
353 | ||
354 | err = hfs_brec_find(&fd, hfs_find_rec_by_key); | |
355 | if (err) | |
356 | goto out; | |
357 | ||
358 | err = __hfsplus_delete_attr(inode, inode->i_ino, &fd); | |
359 | if (err) | |
360 | goto out; | |
361 | ||
362 | out: | |
363 | hfs_find_exit(&fd); | |
364 | return err; | |
365 | } | |
366 | ||
367 | int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid) | |
368 | { | |
369 | int err = 0; | |
370 | struct hfs_find_data fd; | |
371 | ||
c2b3e1f7 | 372 | hfs_dbg(ATTR_MOD, "delete_all_attrs: %d\n", cnid); |
3e05ca20 VD |
373 | |
374 | if (!HFSPLUS_SB(dir->i_sb)->attr_tree) { | |
375 | printk(KERN_ERR "hfs: attributes file doesn't exist\n"); | |
376 | return -EINVAL; | |
377 | } | |
378 | ||
379 | err = hfs_find_init(HFSPLUS_SB(dir->i_sb)->attr_tree, &fd); | |
380 | if (err) | |
381 | return err; | |
382 | ||
383 | for (;;) { | |
384 | err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd); | |
385 | if (err) { | |
386 | if (err != -ENOENT) | |
387 | printk(KERN_ERR "hfs: xattr search failed.\n"); | |
388 | goto end_delete_all; | |
389 | } | |
390 | ||
391 | err = __hfsplus_delete_attr(dir, cnid, &fd); | |
392 | if (err) | |
393 | goto end_delete_all; | |
394 | } | |
395 | ||
396 | end_delete_all: | |
397 | hfs_find_exit(&fd); | |
398 | return err; | |
399 | } |