diff options
author | Vyacheslav Dubeyko <slava@dubeyko.com> | 2013-02-27 20:03:01 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-02-27 22:10:10 -0500 |
commit | 3e05ca20fb570b456bd9841b5ff489d865e8c563 (patch) | |
tree | 3fe4c11cebeec039e55a4aa8d4add4d59b005cd3 /fs/hfsplus | |
parent | 9ed083d8ccc6186448c3558c6f40b40ba0b1568a (diff) |
hfsplus: add functionality of manipulating by records in attributes tree
Add functionality of manipulating by records in attributes tree.
Signed-off-by: Vyacheslav Dubeyko <slava@dubeyko.com>
Reported-by: Hin-Tak Leung <htl10@users.sourceforge.net>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Jan Kara <jack@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/hfsplus')
-rw-r--r-- | fs/hfsplus/attributes.c | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/fs/hfsplus/attributes.c b/fs/hfsplus/attributes.c new file mode 100644 index 000000000000..8d691f124714 --- /dev/null +++ b/fs/hfsplus/attributes.c | |||
@@ -0,0 +1,399 @@ | |||
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 | |||
169 | dprint(DBG_ATTR_MOD, "find_attr: %s,%d\n", name ? name : NULL, cnid); | ||
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 | |||
231 | dprint(DBG_ATTR_MOD, "create_attr: %s,%ld\n", | ||
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 | |||
331 | dprint(DBG_ATTR_MOD, "delete_attr: %s,%ld\n", | ||
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 | |||
372 | dprint(DBG_ATTR_MOD, "delete_all_attrs: %d\n", cnid); | ||
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 | } | ||