xref: /openbmc/linux/fs/hfsplus/attributes.c (revision c4c11dd1)
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 			pr_err("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 	hfs_dbg(ATTR_MOD, "find_attr: %s,%d\n", name ? name : NULL, cnid);
170 
171 	if (!HFSPLUS_SB(sb)->attr_tree) {
172 		pr_err("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 	hfs_dbg(ATTR_MOD, "create_attr: %s,%ld\n",
232 		name ? name : NULL, inode->i_ino);
233 
234 	if (!HFSPLUS_SB(sb)->attr_tree) {
235 		pr_err("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 		pr_err("only inline data xattr are supported\n");
311 		return -EOPNOTSUPP;
312 	default:
313 		pr_err("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 	hfs_dbg(ATTR_MOD, "delete_attr: %s,%ld\n",
332 		name ? name : NULL, inode->i_ino);
333 
334 	if (!HFSPLUS_SB(sb)->attr_tree) {
335 		pr_err("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 		pr_err("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 	hfs_dbg(ATTR_MOD, "delete_all_attrs: %d\n", cnid);
373 
374 	if (!HFSPLUS_SB(dir->i_sb)->attr_tree) {
375 		pr_err("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 				pr_err("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 }
400