1 | /* |
2 | * Copyright (c) 2017 Grzegorz Kostka (kostka.grzegorz@gmail.com) |
3 | * Copyright (c) 2017 Kaho Ng (ngkaho1234@gmail.com) |
4 | * |
5 | * This program is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU General Public License |
7 | * as published by the Free Software Foundation; either version 2 |
8 | * of the License, or (at your option) any later version. |
9 | */ |
10 | |
11 | /** @addtogroup lwext4 |
12 | * @{ |
13 | */ |
14 | /** |
15 | * @file ext4_xattr.c |
16 | * @brief Extended Attribute manipulation. |
17 | */ |
18 | |
19 | #include <ext4_config.h> |
20 | #include <ext4_debug.h> |
21 | #include <ext4_errno.h> |
22 | #include <ext4_misc.h> |
23 | #include <ext4_types.h> |
24 | |
25 | #include <ext4_balloc.h> |
26 | #include <ext4_block_group.h> |
27 | #include <ext4_blockdev.h> |
28 | #include <ext4_crc32.h> |
29 | #include <ext4_fs.h> |
30 | #include <ext4_inode.h> |
31 | #include <ext4_super.h> |
32 | #include <ext4_trans.h> |
33 | #include <ext4_xattr.h> |
34 | |
35 | #include <stdlib.h> |
36 | #include <string.h> |
37 | |
38 | #if CONFIG_XATTR_ENABLE |
39 | |
40 | /** |
41 | * @file ext4_xattr.c |
42 | * @brief Extended Attribute Manipulation |
43 | */ |
44 | |
45 | /* Extended Attribute(EA) */ |
46 | |
47 | /* Magic value in attribute blocks */ |
48 | #define EXT4_XATTR_MAGIC 0xEA020000 |
49 | |
50 | /* Maximum number of references to one attribute block */ |
51 | #define EXT4_XATTR_REFCOUNT_MAX 1024 |
52 | |
53 | /* Name indexes */ |
54 | #define EXT4_XATTR_INDEX_USER 1 |
55 | #define EXT4_XATTR_INDEX_POSIX_ACL_ACCESS 2 |
56 | #define EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT 3 |
57 | #define EXT4_XATTR_INDEX_TRUSTED 4 |
58 | #define EXT4_XATTR_INDEX_LUSTRE 5 |
59 | #define EXT4_XATTR_INDEX_SECURITY 6 |
60 | #define EXT4_XATTR_INDEX_SYSTEM 7 |
61 | #define EXT4_XATTR_INDEX_RICHACL 8 |
62 | #define EXT4_XATTR_INDEX_ENCRYPTION 9 |
63 | |
64 | #define EXT4_XATTR_PAD_BITS 2 |
65 | #define EXT4_XATTR_PAD (1 << EXT4_XATTR_PAD_BITS) |
66 | #define EXT4_XATTR_ROUND (EXT4_XATTR_PAD - 1) |
67 | #define EXT4_XATTR_LEN(name_len) \ |
68 | (((name_len) + EXT4_XATTR_ROUND + sizeof(struct ext4_xattr_entry)) & \ |
69 | ~EXT4_XATTR_ROUND) |
70 | #define EXT4_XATTR_NEXT(entry) \ |
71 | ((struct ext4_xattr_entry *)((char *)(entry) + \ |
72 | EXT4_XATTR_LEN((entry)->e_name_len))) |
73 | #define EXT4_XATTR_SIZE(size) (((size) + EXT4_XATTR_ROUND) & ~EXT4_XATTR_ROUND) |
74 | #define EXT4_XATTR_NAME(entry) ((char *)((entry) + 1)) |
75 | |
76 | #define EXT4_XATTR_IHDR(sb, raw_inode) \ |
77 | ((struct ext4_xattr_ibody_header *)((char *)raw_inode + \ |
78 | EXT4_GOOD_OLD_INODE_SIZE + \ |
79 | ext4_inode_get_extra_isize( \ |
80 | sb, raw_inode))) |
81 | #define EXT4_XATTR_IFIRST(hdr) ((struct ext4_xattr_entry *)((hdr) + 1)) |
82 | |
83 | #define EXT4_XATTR_BHDR(block) ((struct ext4_xattr_header *)((block)->data)) |
84 | #define EXT4_XATTR_ENTRY(ptr) ((struct ext4_xattr_entry *)(ptr)) |
85 | #define EXT4_XATTR_BFIRST(block) EXT4_XATTR_ENTRY(EXT4_XATTR_BHDR(block) + 1) |
86 | #define EXT4_XATTR_IS_LAST_ENTRY(entry) (*(uint32_t *)(entry) == 0) |
87 | |
88 | #define EXT4_ZERO_XATTR_VALUE ((void *)-1) |
89 | |
90 | #pragma pack(push, 1) |
91 | |
92 | struct { |
93 | uint32_t ; /* magic number for identification */ |
94 | uint32_t ; /* reference count */ |
95 | uint32_t ; /* number of disk blocks used */ |
96 | uint32_t ; /* hash value of all attributes */ |
97 | uint32_t ; /* crc32c(uuid+id+xattrblock) */ |
98 | /* id = inum if refcount=1, blknum otherwise */ |
99 | uint32_t [3]; /* zero right now */ |
100 | }; |
101 | |
102 | struct ext4_xattr_ibody_header { |
103 | uint32_t h_magic; /* magic number for identification */ |
104 | }; |
105 | |
106 | struct ext4_xattr_entry { |
107 | uint8_t e_name_len; /* length of name */ |
108 | uint8_t e_name_index; /* attribute name index */ |
109 | uint16_t e_value_offs; /* offset in disk block of value */ |
110 | uint32_t e_value_block; /* disk block attribute is stored on (n/i) */ |
111 | uint32_t e_value_size; /* size of attribute value */ |
112 | uint32_t e_hash; /* hash value of name and value */ |
113 | }; |
114 | |
115 | #pragma pack(pop) |
116 | |
117 | |
118 | #define NAME_HASH_SHIFT 5 |
119 | #define VALUE_HASH_SHIFT 16 |
120 | |
121 | static inline void ext4_xattr_compute_hash(struct ext4_xattr_header *, |
122 | struct ext4_xattr_entry *entry) |
123 | { |
124 | uint32_t hash = 0; |
125 | char *name = EXT4_XATTR_NAME(entry); |
126 | int n; |
127 | |
128 | for (n = 0; n < entry->e_name_len; n++) { |
129 | hash = (hash << NAME_HASH_SHIFT) ^ |
130 | (hash >> (8 * sizeof(hash) - NAME_HASH_SHIFT)) ^ *name++; |
131 | } |
132 | |
133 | if (entry->e_value_block == 0 && entry->e_value_size != 0) { |
134 | uint32_t *value = |
135 | (uint32_t *)((char *)header + to_le16(entry->e_value_offs)); |
136 | for (n = (to_le32(entry->e_value_size) + EXT4_XATTR_ROUND) >> |
137 | EXT4_XATTR_PAD_BITS; |
138 | n; n--) { |
139 | hash = (hash << VALUE_HASH_SHIFT) ^ |
140 | (hash >> (8 * sizeof(hash) - VALUE_HASH_SHIFT)) ^ |
141 | to_le32(*value++); |
142 | } |
143 | } |
144 | entry->e_hash = to_le32(hash); |
145 | } |
146 | |
147 | #define BLOCK_HASH_SHIFT 16 |
148 | |
149 | /* |
150 | * ext4_xattr_rehash() |
151 | * |
152 | * Re-compute the extended attribute hash value after an entry has changed. |
153 | */ |
154 | static void ext4_xattr_rehash(struct ext4_xattr_header *, |
155 | struct ext4_xattr_entry *entry) |
156 | { |
157 | struct ext4_xattr_entry *here; |
158 | uint32_t hash = 0; |
159 | |
160 | ext4_xattr_compute_hash(header, entry); |
161 | here = EXT4_XATTR_ENTRY(header + 1); |
162 | while (!EXT4_XATTR_IS_LAST_ENTRY(here)) { |
163 | if (!here->e_hash) { |
164 | /* Block is not shared if an entry's hash value == 0 */ |
165 | hash = 0; |
166 | break; |
167 | } |
168 | hash = (hash << BLOCK_HASH_SHIFT) ^ |
169 | (hash >> (8 * sizeof(hash) - BLOCK_HASH_SHIFT)) ^ |
170 | to_le32(here->e_hash); |
171 | here = EXT4_XATTR_NEXT(here); |
172 | } |
173 | header->h_hash = to_le32(hash); |
174 | } |
175 | |
176 | #if CONFIG_META_CSUM_ENABLE |
177 | static uint32_t ext4_xattr_block_checksum(struct ext4_inode_ref *inode_ref, |
178 | ext4_fsblk_t blocknr, |
179 | struct ext4_xattr_header *) |
180 | { |
181 | uint32_t checksum = 0; |
182 | uint64_t le64_blocknr = blocknr; |
183 | struct ext4_sblock *sb = &inode_ref->fs->sb; |
184 | |
185 | if (ext4_sb_feature_ro_com(s: sb, EXT4_FRO_COM_METADATA_CSUM)) { |
186 | uint32_t orig_checksum; |
187 | |
188 | /* Preparation: temporarily set bg checksum to 0 */ |
189 | orig_checksum = header->h_checksum; |
190 | header->h_checksum = 0; |
191 | /* First calculate crc32 checksum against fs uuid */ |
192 | checksum = |
193 | ext4_crc32c(EXT4_CRC32_INIT, buf: sb->uuid, size: sizeof(sb->uuid)); |
194 | /* Then calculate crc32 checksum block number */ |
195 | checksum = |
196 | ext4_crc32c(crc: checksum, buf: &le64_blocknr, size: sizeof(le64_blocknr)); |
197 | /* Finally calculate crc32 checksum against |
198 | * the entire xattr block */ |
199 | checksum = |
200 | ext4_crc32c(crc: checksum, buf: header, size: ext4_sb_get_block_size(s: sb)); |
201 | header->h_checksum = orig_checksum; |
202 | } |
203 | return checksum; |
204 | } |
205 | #else |
206 | #define ext4_xattr_block_checksum(...) 0 |
207 | #endif |
208 | |
209 | static void ext4_xattr_set_block_checksum(struct ext4_inode_ref *inode_ref, |
210 | ext4_fsblk_t blocknr __unused, |
211 | struct ext4_xattr_header *) |
212 | { |
213 | struct ext4_sblock *sb = &inode_ref->fs->sb; |
214 | if (!ext4_sb_feature_ro_com(s: sb, EXT4_FRO_COM_METADATA_CSUM)) |
215 | return; |
216 | |
217 | header->h_checksum = |
218 | ext4_xattr_block_checksum(inode_ref, blocknr, header); |
219 | } |
220 | |
221 | struct xattr_prefix { |
222 | const char *prefix; |
223 | uint8_t name_index; |
224 | }; |
225 | |
226 | static const struct xattr_prefix prefix_tbl[] = { |
227 | {"user." , EXT4_XATTR_INDEX_USER}, |
228 | {"system.posix_acl_access" , EXT4_XATTR_INDEX_POSIX_ACL_ACCESS}, |
229 | {"system.posix_acl_default" , EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT}, |
230 | {"trusted." , EXT4_XATTR_INDEX_TRUSTED}, |
231 | {"security." , EXT4_XATTR_INDEX_SECURITY}, |
232 | {"system." , EXT4_XATTR_INDEX_SYSTEM}, |
233 | {"system.richacl" , EXT4_XATTR_INDEX_RICHACL}, |
234 | {NULL, 0}, |
235 | }; |
236 | |
237 | const char *(const char *full_name, size_t full_name_len, |
238 | uint8_t *name_index, size_t *name_len, |
239 | bool *found) |
240 | { |
241 | int i; |
242 | ext4_assert(name_index); |
243 | ext4_assert(found); |
244 | |
245 | *found = false; |
246 | |
247 | if (!full_name_len) { |
248 | if (name_len) |
249 | *name_len = 0; |
250 | |
251 | return NULL; |
252 | } |
253 | |
254 | for (i = 0; prefix_tbl[i].prefix; i++) { |
255 | size_t prefix_len = strlen(s: prefix_tbl[i].prefix); |
256 | if (full_name_len >= prefix_len && |
257 | !memcmp(a: full_name, b: prefix_tbl[i].prefix, size: prefix_len)) { |
258 | bool require_name = |
259 | prefix_tbl[i].prefix[prefix_len - 1] == '.'; |
260 | *name_index = prefix_tbl[i].name_index; |
261 | if (name_len) |
262 | *name_len = full_name_len - prefix_len; |
263 | |
264 | if (!(full_name_len - prefix_len) && require_name) |
265 | return NULL; |
266 | |
267 | *found = true; |
268 | if (require_name) |
269 | return full_name + prefix_len; |
270 | |
271 | return NULL; |
272 | } |
273 | } |
274 | if (name_len) |
275 | *name_len = 0; |
276 | |
277 | return NULL; |
278 | } |
279 | |
280 | const char *ext4_get_xattr_name_prefix(uint8_t name_index, |
281 | size_t *ret_prefix_len) |
282 | { |
283 | int i; |
284 | |
285 | for (i = 0; prefix_tbl[i].prefix; i++) { |
286 | size_t prefix_len = strlen(s: prefix_tbl[i].prefix); |
287 | if (prefix_tbl[i].name_index == name_index) { |
288 | if (ret_prefix_len) |
289 | *ret_prefix_len = prefix_len; |
290 | |
291 | return prefix_tbl[i].prefix; |
292 | } |
293 | } |
294 | if (ret_prefix_len) |
295 | *ret_prefix_len = 0; |
296 | |
297 | return NULL; |
298 | } |
299 | |
300 | static const char ext4_xattr_empty_value; |
301 | |
302 | /** |
303 | * @brief Insert/Remove/Modify the given entry |
304 | * |
305 | * @param i The information of the given EA entry |
306 | * @param s Search context block |
307 | * @param dry_run Do not modify the content of the buffer |
308 | * |
309 | * @return Return EOK when finished, ENOSPC when there is no enough space |
310 | */ |
311 | static int ext4_xattr_set_entry(struct ext4_xattr_info *i, |
312 | struct ext4_xattr_search *s, bool dry_run) |
313 | { |
314 | struct ext4_xattr_entry *last; |
315 | size_t free, min_offs = (char *)s->end - (char *)s->base, |
316 | name_len = i->name_len; |
317 | |
318 | /* |
319 | * If the entry is going to be removed but not found, return 0 to |
320 | * indicate success. |
321 | */ |
322 | if (!i->value && s->not_found) |
323 | return EOK; |
324 | |
325 | /* Compute min_offs and last. */ |
326 | last = s->first; |
327 | for (; !EXT4_XATTR_IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) { |
328 | if (last->e_value_size) { |
329 | size_t offs = to_le16(last->e_value_offs); |
330 | if (offs < min_offs) |
331 | min_offs = offs; |
332 | } |
333 | } |
334 | |
335 | /* Calculate free space in the block. */ |
336 | free = min_offs - ((char *)last - (char *)s->base) - sizeof(uint32_t); |
337 | if (!s->not_found) |
338 | free += EXT4_XATTR_SIZE(s->here->e_value_size) + |
339 | EXT4_XATTR_LEN(s->here->e_name_len); |
340 | |
341 | if (i->value) { |
342 | /* See whether there is enough space to hold new entry */ |
343 | if (free < |
344 | EXT4_XATTR_SIZE(i->value_len) + EXT4_XATTR_LEN(name_len)) |
345 | return ENOSPC; |
346 | } |
347 | |
348 | /* Return EOK now if we do not intend to modify the content. */ |
349 | if (dry_run) |
350 | return EOK; |
351 | |
352 | /* First remove the old entry's data part */ |
353 | if (!s->not_found) { |
354 | size_t value_offs = to_le16(s->here->e_value_offs); |
355 | void *value = (char *)s->base + value_offs; |
356 | void *first_value = (char *)s->base + min_offs; |
357 | size_t value_size = |
358 | EXT4_XATTR_SIZE(to_le32(s->here->e_value_size)); |
359 | |
360 | if (value_offs) { |
361 | /* Remove the data part. */ |
362 | memmove(dest: (char *)first_value + value_size, src: first_value, |
363 | size: (char *)value - (char *)first_value); |
364 | |
365 | /* Zero the gap created */ |
366 | memset(dest: first_value, c: 0, size: value_size); |
367 | |
368 | /* |
369 | * Calculate the new min_offs after removal of the old |
370 | * entry's data part |
371 | */ |
372 | min_offs += value_size; |
373 | } |
374 | |
375 | /* |
376 | * Adjust the value offset of entries which has value offset |
377 | * prior to the s->here. The offset of these entries won't be |
378 | * shifted if the size of the entry we removed is zero. |
379 | */ |
380 | for (last = s->first; !EXT4_XATTR_IS_LAST_ENTRY(last); |
381 | last = EXT4_XATTR_NEXT(last)) { |
382 | size_t offs = to_le16(last->e_value_offs); |
383 | |
384 | /* For zero-value-length entry, offs will be zero. */ |
385 | if (offs < value_offs) |
386 | last->e_value_offs = to_le16(offs + value_size); |
387 | } |
388 | } |
389 | |
390 | /* If caller wants us to insert... */ |
391 | if (i->value) { |
392 | size_t value_offs; |
393 | if (i->value_len) |
394 | value_offs = min_offs - EXT4_XATTR_SIZE(i->value_len); |
395 | else |
396 | value_offs = 0; |
397 | |
398 | if (!s->not_found) { |
399 | struct ext4_xattr_entry *here = s->here; |
400 | |
401 | /* Reuse the current entry we have got */ |
402 | here->e_value_offs = to_le16(value_offs); |
403 | here->e_value_size = to_le32(i->value_len); |
404 | } else { |
405 | /* Insert a new entry */ |
406 | last->e_name_len = (uint8_t)name_len; |
407 | last->e_name_index = i->name_index; |
408 | last->e_value_offs = to_le16(value_offs); |
409 | last->e_value_block = 0; |
410 | last->e_value_size = to_le32(i->value_len); |
411 | memcpy(EXT4_XATTR_NAME(last), src: i->name, size: name_len); |
412 | |
413 | /* Set valid last entry indicator */ |
414 | *(uint32_t *)EXT4_XATTR_NEXT(last) = 0; |
415 | |
416 | s->here = last; |
417 | } |
418 | |
419 | /* Insert the value's part */ |
420 | if (value_offs) { |
421 | memcpy(dest: (char *)s->base + value_offs, src: i->value, |
422 | size: i->value_len); |
423 | |
424 | /* Clear the padding bytes if there is */ |
425 | if (EXT4_XATTR_SIZE(i->value_len) != i->value_len) |
426 | memset(dest: (char *)s->base + value_offs + |
427 | i->value_len, |
428 | c: 0, EXT4_XATTR_SIZE(i->value_len) - |
429 | i->value_len); |
430 | } |
431 | } else { |
432 | size_t shift_offs; |
433 | |
434 | /* Remove the whole entry */ |
435 | shift_offs = (char *)EXT4_XATTR_NEXT(s->here) - (char *)s->here; |
436 | memmove(dest: s->here, EXT4_XATTR_NEXT(s->here), |
437 | size: (char *)last + sizeof(uint32_t) - |
438 | (char *)EXT4_XATTR_NEXT(s->here)); |
439 | |
440 | /* Zero the gap created */ |
441 | memset(dest: (char *)last - shift_offs + sizeof(uint32_t), c: 0, |
442 | size: shift_offs); |
443 | |
444 | s->here = NULL; |
445 | } |
446 | |
447 | return EOK; |
448 | } |
449 | |
450 | static inline bool ext4_xattr_is_empty(struct ext4_xattr_search *s) |
451 | { |
452 | if (!EXT4_XATTR_IS_LAST_ENTRY(s->first)) |
453 | return false; |
454 | |
455 | return true; |
456 | } |
457 | |
458 | /** |
459 | * @brief Find the entry according to given information |
460 | * |
461 | * @param i The information of the EA entry to be found, |
462 | * including name_index, name and the length of name |
463 | * @param s Search context block |
464 | */ |
465 | static void ext4_xattr_find_entry(struct ext4_xattr_info *i, |
466 | struct ext4_xattr_search *s) |
467 | { |
468 | struct ext4_xattr_entry *entry = NULL; |
469 | |
470 | s->not_found = true; |
471 | s->here = NULL; |
472 | |
473 | /* |
474 | * Find the wanted EA entry by simply comparing the namespace, |
475 | * name and the length of name. |
476 | */ |
477 | for (entry = s->first; !EXT4_XATTR_IS_LAST_ENTRY(entry); |
478 | entry = EXT4_XATTR_NEXT(entry)) { |
479 | size_t name_len = entry->e_name_len; |
480 | const char *name = EXT4_XATTR_NAME(entry); |
481 | if (name_len == i->name_len && |
482 | entry->e_name_index == i->name_index && |
483 | !memcmp(a: name, b: i->name, size: name_len)) { |
484 | s->here = entry; |
485 | s->not_found = false; |
486 | i->value_len = to_le32(entry->e_value_size); |
487 | if (i->value_len) |
488 | i->value = (char *)s->base + |
489 | to_le16(entry->e_value_offs); |
490 | else |
491 | i->value = NULL; |
492 | |
493 | return; |
494 | } |
495 | } |
496 | } |
497 | |
498 | /** |
499 | * @brief Check whether the xattr block's content is valid |
500 | * |
501 | * @param inode_ref Inode reference |
502 | * @param block The block buffer to be validated |
503 | * |
504 | * @return true if @block is valid, false otherwise. |
505 | */ |
506 | static bool ext4_xattr_is_block_valid(struct ext4_inode_ref *inode_ref, |
507 | struct ext4_block *block) |
508 | { |
509 | |
510 | void *base = block->data, |
511 | *end = block->data + ext4_sb_get_block_size(s: &inode_ref->fs->sb); |
512 | size_t min_offs = (char *)end - (char *)base; |
513 | struct ext4_xattr_header * = EXT4_XATTR_BHDR(block); |
514 | struct ext4_xattr_entry *entry = EXT4_XATTR_BFIRST(block); |
515 | |
516 | /* |
517 | * Check whether the magic number in the header is correct. |
518 | */ |
519 | if (header->h_magic != to_le32(EXT4_XATTR_MAGIC)) |
520 | return false; |
521 | |
522 | /* |
523 | * The in-kernel filesystem driver only supports 1 block currently. |
524 | */ |
525 | if (header->h_blocks != to_le32(1)) |
526 | return false; |
527 | |
528 | /* |
529 | * Check if those entries are maliciously corrupted to inflict harm |
530 | * upon us. |
531 | */ |
532 | for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); |
533 | entry = EXT4_XATTR_NEXT(entry)) { |
534 | if (!to_le32(entry->e_value_size) && |
535 | to_le16(entry->e_value_offs)) |
536 | return false; |
537 | |
538 | if ((char *)base + to_le16(entry->e_value_offs) + |
539 | to_le32(entry->e_value_size) > |
540 | (char *)end) |
541 | return false; |
542 | |
543 | /* |
544 | * The name length field should also be correct, |
545 | * also there should be an 4-byte zero entry at the |
546 | * end. |
547 | */ |
548 | if ((char *)EXT4_XATTR_NEXT(entry) + sizeof(uint32_t) > |
549 | (char *)end) |
550 | return false; |
551 | |
552 | if (to_le32(entry->e_value_size)) { |
553 | size_t offs = to_le16(entry->e_value_offs); |
554 | if (offs < min_offs) |
555 | min_offs = offs; |
556 | } |
557 | } |
558 | /* |
559 | * Entry field and data field do not override each other. |
560 | */ |
561 | if ((char *)base + min_offs < (char *)entry + sizeof(uint32_t)) |
562 | return false; |
563 | |
564 | return true; |
565 | } |
566 | |
567 | /** |
568 | * @brief Check whether the inode buffer's content is valid |
569 | * |
570 | * @param inode_ref Inode reference |
571 | * |
572 | * @return true if the inode buffer is valid, false otherwise. |
573 | */ |
574 | static bool ext4_xattr_is_ibody_valid(struct ext4_inode_ref *inode_ref) |
575 | { |
576 | size_t min_offs; |
577 | void *base, *end; |
578 | struct ext4_fs *fs = inode_ref->fs; |
579 | struct ext4_xattr_ibody_header *; |
580 | struct ext4_xattr_entry *entry; |
581 | size_t inode_size = ext4_get16(&fs->sb, inode_size); |
582 | |
583 | iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); |
584 | entry = EXT4_XATTR_IFIRST(iheader); |
585 | base = iheader; |
586 | end = (char *)inode_ref->inode + inode_size; |
587 | min_offs = (char *)end - (char *)base; |
588 | |
589 | /* |
590 | * Check whether the magic number in the header is correct. |
591 | */ |
592 | if (iheader->h_magic != to_le32(EXT4_XATTR_MAGIC)) |
593 | return false; |
594 | |
595 | /* |
596 | * Check if those entries are maliciously corrupted to inflict harm |
597 | * upon us. |
598 | */ |
599 | for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); |
600 | entry = EXT4_XATTR_NEXT(entry)) { |
601 | if (!to_le32(entry->e_value_size) && |
602 | to_le16(entry->e_value_offs)) |
603 | return false; |
604 | |
605 | if ((char *)base + to_le16(entry->e_value_offs) + |
606 | to_le32(entry->e_value_size) > |
607 | (char *)end) |
608 | return false; |
609 | |
610 | /* |
611 | * The name length field should also be correct, |
612 | * also there should be an 4-byte zero entry at the |
613 | * end. |
614 | */ |
615 | if ((char *)EXT4_XATTR_NEXT(entry) + sizeof(uint32_t) > |
616 | (char *)end) |
617 | return false; |
618 | |
619 | if (to_le32(entry->e_value_size)) { |
620 | size_t offs = to_le16(entry->e_value_offs); |
621 | if (offs < min_offs) |
622 | min_offs = offs; |
623 | } |
624 | } |
625 | /* |
626 | * Entry field and data field do not override each other. |
627 | */ |
628 | if ((char *)base + min_offs < (char *)entry + sizeof(uint32_t)) |
629 | return false; |
630 | |
631 | return true; |
632 | } |
633 | |
634 | /** |
635 | * @brief An EA entry finder for inode buffer |
636 | */ |
637 | struct ext4_xattr_finder { |
638 | /** |
639 | * @brief The information of the EA entry to be find |
640 | */ |
641 | struct ext4_xattr_info i; |
642 | |
643 | /** |
644 | * @brief Search context block of the current search |
645 | */ |
646 | struct ext4_xattr_search s; |
647 | |
648 | /** |
649 | * @brief Inode reference to the corresponding inode |
650 | */ |
651 | struct ext4_inode_ref *inode_ref; |
652 | }; |
653 | |
654 | static void ext4_xattr_ibody_initialize(struct ext4_inode_ref *inode_ref) |
655 | { |
656 | struct ext4_xattr_ibody_header *; |
657 | struct ext4_fs *fs = inode_ref->fs; |
658 | size_t = |
659 | ext4_inode_get_extra_isize(sb: &fs->sb, inode: inode_ref->inode); |
660 | size_t inode_size = ext4_get16(&fs->sb, inode_size); |
661 | if (!extra_isize) |
662 | return; |
663 | |
664 | header = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); |
665 | memset(dest: header, c: 0, size: inode_size - EXT4_GOOD_OLD_INODE_SIZE - extra_isize); |
666 | header->h_magic = to_le32(EXT4_XATTR_MAGIC); |
667 | inode_ref->dirty = true; |
668 | } |
669 | |
670 | /** |
671 | * @brief Initialize a given xattr block |
672 | * |
673 | * @param inode_ref Inode reference |
674 | * @param block xattr block buffer |
675 | */ |
676 | static void ext4_xattr_block_initialize(struct ext4_inode_ref *inode_ref, |
677 | struct ext4_block *block) |
678 | { |
679 | struct ext4_xattr_header *; |
680 | struct ext4_fs *fs = inode_ref->fs; |
681 | |
682 | memset(dest: block->data, c: 0, size: ext4_sb_get_block_size(s: &fs->sb)); |
683 | |
684 | header = EXT4_XATTR_BHDR(block); |
685 | header->h_magic = to_le32(EXT4_XATTR_MAGIC); |
686 | header->h_refcount = to_le32(1); |
687 | header->h_blocks = to_le32(1); |
688 | |
689 | ext4_trans_set_block_dirty(buf: block->buf); |
690 | } |
691 | |
692 | static void ext4_xattr_block_init_search(struct ext4_inode_ref *inode_ref, |
693 | struct ext4_xattr_search *s, |
694 | struct ext4_block *block) |
695 | { |
696 | s->base = block->data; |
697 | s->end = block->data + ext4_sb_get_block_size(s: &inode_ref->fs->sb); |
698 | s->first = EXT4_XATTR_BFIRST(block); |
699 | s->here = NULL; |
700 | s->not_found = true; |
701 | } |
702 | |
703 | /** |
704 | * @brief Find an EA entry inside a xattr block |
705 | * |
706 | * @param inode_ref Inode reference |
707 | * @param finder The caller-provided finder block with |
708 | * information filled |
709 | * @param block The block buffer to be looked into |
710 | * |
711 | * @return Return EOK no matter the entry is found or not. |
712 | * If the IO operation or the buffer validation failed, |
713 | * return other value. |
714 | */ |
715 | static int ext4_xattr_block_find_entry(struct ext4_inode_ref *inode_ref, |
716 | struct ext4_xattr_finder *finder, |
717 | struct ext4_block *block) |
718 | { |
719 | int ret = EOK; |
720 | |
721 | /* Initialize the caller-given finder */ |
722 | finder->inode_ref = inode_ref; |
723 | memset(dest: &finder->s, c: 0, size: sizeof(finder->s)); |
724 | |
725 | if (ret != EOK) |
726 | return ret; |
727 | |
728 | /* Check the validity of the buffer */ |
729 | if (!ext4_xattr_is_block_valid(inode_ref, block)) |
730 | return EIO; |
731 | |
732 | ext4_xattr_block_init_search(inode_ref, s: &finder->s, block); |
733 | ext4_xattr_find_entry(i: &finder->i, s: &finder->s); |
734 | return EOK; |
735 | } |
736 | |
737 | /** |
738 | * @brief Find an EA entry inside an inode's extra space |
739 | * |
740 | * @param inode_ref Inode reference |
741 | * @param finder The caller-provided finder block with |
742 | * information filled |
743 | * |
744 | * @return Return EOK no matter the entry is found or not. |
745 | * If the IO operation or the buffer validation failed, |
746 | * return other value. |
747 | */ |
748 | static int ext4_xattr_ibody_find_entry(struct ext4_inode_ref *inode_ref, |
749 | struct ext4_xattr_finder *finder) |
750 | { |
751 | struct ext4_fs *fs = inode_ref->fs; |
752 | struct ext4_xattr_ibody_header *; |
753 | size_t = |
754 | ext4_inode_get_extra_isize(sb: &fs->sb, inode: inode_ref->inode); |
755 | size_t inode_size = ext4_get16(&fs->sb, inode_size); |
756 | |
757 | /* Initialize the caller-given finder */ |
758 | finder->inode_ref = inode_ref; |
759 | memset(dest: &finder->s, c: 0, size: sizeof(finder->s)); |
760 | |
761 | /* |
762 | * If there is no extra inode space |
763 | * set ext4_xattr_ibody_finder::s::not_found to true and return EOK |
764 | */ |
765 | if (!extra_isize) { |
766 | finder->s.not_found = true; |
767 | return EOK; |
768 | } |
769 | |
770 | /* Check the validity of the buffer */ |
771 | if (!ext4_xattr_is_ibody_valid(inode_ref)) |
772 | return EIO; |
773 | |
774 | iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); |
775 | finder->s.base = EXT4_XATTR_IFIRST(iheader); |
776 | finder->s.end = (char *)inode_ref->inode + inode_size; |
777 | finder->s.first = EXT4_XATTR_IFIRST(iheader); |
778 | ext4_xattr_find_entry(i: &finder->i, s: &finder->s); |
779 | return EOK; |
780 | } |
781 | |
782 | /** |
783 | * @brief Try to allocate a block holding EA entries. |
784 | * |
785 | * @param inode_ref Inode reference |
786 | * |
787 | * @return Error code |
788 | */ |
789 | static int ext4_xattr_try_alloc_block(struct ext4_inode_ref *inode_ref) |
790 | { |
791 | int ret = EOK; |
792 | |
793 | ext4_fsblk_t xattr_block = 0; |
794 | xattr_block = |
795 | ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &inode_ref->fs->sb); |
796 | |
797 | /* |
798 | * Only allocate a xattr block when there is no xattr block |
799 | * used by the inode. |
800 | */ |
801 | if (!xattr_block) { |
802 | ext4_fsblk_t goal = ext4_fs_inode_to_goal_block(inode_ref); |
803 | |
804 | ret = ext4_balloc_alloc_block(inode_ref, goal, baddr: &xattr_block); |
805 | if (ret != EOK) |
806 | goto Finish; |
807 | |
808 | ext4_inode_set_file_acl(inode: inode_ref->inode, sb: &inode_ref->fs->sb, |
809 | acl: xattr_block); |
810 | } |
811 | |
812 | Finish: |
813 | return ret; |
814 | } |
815 | |
816 | /** |
817 | * @brief Try to free a block holding EA entries. |
818 | * |
819 | * @param inode_ref Inode reference |
820 | * |
821 | * @return Error code |
822 | */ |
823 | static void ext4_xattr_try_free_block(struct ext4_inode_ref *inode_ref) |
824 | { |
825 | ext4_fsblk_t xattr_block; |
826 | xattr_block = |
827 | ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &inode_ref->fs->sb); |
828 | /* |
829 | * Free the xattr block used by the inode when there is one. |
830 | */ |
831 | if (xattr_block) { |
832 | ext4_inode_set_file_acl(inode: inode_ref->inode, sb: &inode_ref->fs->sb, |
833 | acl: 0); |
834 | ext4_balloc_free_block(inode_ref, baddr: xattr_block); |
835 | inode_ref->dirty = true; |
836 | } |
837 | } |
838 | |
839 | /** |
840 | * @brief Put a list of EA entries into a caller-provided buffer |
841 | * In order to make sure that @list buffer can fit in the data, |
842 | * the routine should be called twice. |
843 | * |
844 | * @param inode_ref Inode reference |
845 | * @param list A caller-provided buffer to hold a list of EA entries. |
846 | * If list == NULL, list_len will contain the size of |
847 | * the buffer required to hold these entries |
848 | * @param list_len The length of the data written to @list |
849 | * @return Error code |
850 | */ |
851 | int ext4_xattr_list(struct ext4_inode_ref *inode_ref, |
852 | struct ext4_xattr_list_entry *list, size_t *list_len) |
853 | { |
854 | int ret = EOK; |
855 | size_t buf_len = 0; |
856 | struct ext4_fs *fs = inode_ref->fs; |
857 | struct ext4_xattr_ibody_header *; |
858 | size_t = |
859 | ext4_inode_get_extra_isize(sb: &fs->sb, inode: inode_ref->inode); |
860 | struct ext4_block block; |
861 | bool block_loaded = false; |
862 | ext4_fsblk_t xattr_block = 0; |
863 | struct ext4_xattr_entry *entry; |
864 | struct ext4_xattr_list_entry *list_prev = NULL; |
865 | xattr_block = ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &fs->sb); |
866 | |
867 | /* |
868 | * If there is extra inode space and the xattr buffer in the |
869 | * inode is valid. |
870 | */ |
871 | if (extra_isize && ext4_xattr_is_ibody_valid(inode_ref)) { |
872 | iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); |
873 | entry = EXT4_XATTR_IFIRST(iheader); |
874 | |
875 | /* |
876 | * The format of the list should be like this: |
877 | * |
878 | * name_len indicates the length in bytes of the name |
879 | * of the EA entry. The string is null-terminated. |
880 | * |
881 | * list->name => (char *)(list + 1); |
882 | * list->next => (void *)((char *)(list + 1) + name_len + 1); |
883 | */ |
884 | for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); |
885 | entry = EXT4_XATTR_NEXT(entry)) { |
886 | size_t name_len = entry->e_name_len; |
887 | if (list) { |
888 | list->name_index = entry->e_name_index; |
889 | list->name_len = name_len; |
890 | list->name = (char *)(list + 1); |
891 | memcpy(dest: list->name, EXT4_XATTR_NAME(entry), |
892 | size: list->name_len); |
893 | |
894 | if (list_prev) |
895 | list_prev->next = list; |
896 | |
897 | list_prev = list; |
898 | list = (struct ext4_xattr_list_entry |
899 | *)(list->name + name_len + 1); |
900 | } |
901 | |
902 | /* |
903 | * Size calculation by pointer arithmetics. |
904 | */ |
905 | buf_len += |
906 | (char *)((struct ext4_xattr_list_entry *)0 + 1) + |
907 | name_len + 1 - |
908 | (char *)(struct ext4_xattr_list_entry *)0; |
909 | } |
910 | } |
911 | |
912 | /* |
913 | * If there is a xattr block used by the inode |
914 | */ |
915 | if (xattr_block) { |
916 | ret = ext4_trans_block_get(bdev: fs->bdev, b: &block, lba: xattr_block); |
917 | if (ret != EOK) |
918 | goto out; |
919 | |
920 | block_loaded = true; |
921 | |
922 | /* |
923 | * As we don't allow the content in the block being invalid, |
924 | * bail out. |
925 | */ |
926 | if (!ext4_xattr_is_block_valid(inode_ref, block: &block)) { |
927 | ret = EIO; |
928 | goto out; |
929 | } |
930 | |
931 | entry = EXT4_XATTR_BFIRST(&block); |
932 | |
933 | /* |
934 | * The format of the list should be like this: |
935 | * |
936 | * name_len indicates the length in bytes of the name |
937 | * of the EA entry. The string is null-terminated. |
938 | * |
939 | * list->name => (char *)(list + 1); |
940 | * list->next => (void *)((char *)(list + 1) + name_len + 1); |
941 | * |
942 | * Same as above actually. |
943 | */ |
944 | for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); |
945 | entry = EXT4_XATTR_NEXT(entry)) { |
946 | size_t name_len = entry->e_name_len; |
947 | if (list) { |
948 | list->name_index = entry->e_name_index; |
949 | list->name_len = name_len; |
950 | list->name = (char *)(list + 1); |
951 | memcpy(dest: list->name, EXT4_XATTR_NAME(entry), |
952 | size: list->name_len); |
953 | |
954 | if (list_prev) |
955 | list_prev->next = list; |
956 | |
957 | list_prev = list; |
958 | list = (struct ext4_xattr_list_entry |
959 | *)(list->name + name_len + 1); |
960 | } |
961 | |
962 | /* |
963 | * Size calculation by pointer arithmetics. |
964 | */ |
965 | buf_len += |
966 | (char *)((struct ext4_xattr_list_entry *)0 + 1) + |
967 | name_len + 1 - |
968 | (char *)(struct ext4_xattr_list_entry *)0; |
969 | } |
970 | } |
971 | if (list_prev) |
972 | list_prev->next = NULL; |
973 | out: |
974 | if (ret == EOK && list_len) |
975 | *list_len = buf_len; |
976 | |
977 | if (block_loaded) |
978 | ext4_block_set(bdev: fs->bdev, b: &block); |
979 | |
980 | return ret; |
981 | } |
982 | |
983 | /** |
984 | * @brief Query EA entry's value with given name-index and name |
985 | * |
986 | * @param inode_ref Inode reference |
987 | * @param name_index Name-index |
988 | * @param name Name of the EA entry to be queried |
989 | * @param name_len Length of name in bytes |
990 | * @param buf Output buffer to hold content |
991 | * @param buf_len Output buffer's length |
992 | * @param data_len The length of data of the EA entry found |
993 | * |
994 | * @return Error code |
995 | */ |
996 | int ext4_xattr_get(struct ext4_inode_ref *inode_ref, uint8_t name_index, |
997 | const char *name, size_t name_len, void *buf, size_t buf_len, |
998 | size_t *data_len) |
999 | { |
1000 | int ret = EOK; |
1001 | struct ext4_xattr_finder ibody_finder; |
1002 | struct ext4_xattr_finder block_finder; |
1003 | struct ext4_xattr_info i; |
1004 | size_t value_len = 0; |
1005 | size_t value_offs = 0; |
1006 | struct ext4_fs *fs = inode_ref->fs; |
1007 | ext4_fsblk_t xattr_block; |
1008 | xattr_block = ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &fs->sb); |
1009 | |
1010 | i.name_index = name_index; |
1011 | i.name = name; |
1012 | i.name_len = name_len; |
1013 | i.value = 0; |
1014 | i.value_len = 0; |
1015 | if (data_len) |
1016 | *data_len = 0; |
1017 | |
1018 | ibody_finder.i = i; |
1019 | ret = ext4_xattr_ibody_find_entry(inode_ref, finder: &ibody_finder); |
1020 | if (ret != EOK) |
1021 | goto out; |
1022 | |
1023 | if (!ibody_finder.s.not_found) { |
1024 | value_len = to_le32(ibody_finder.s.here->e_value_size); |
1025 | value_offs = to_le32(ibody_finder.s.here->e_value_offs); |
1026 | if (buf_len && buf) { |
1027 | void *data_loc = |
1028 | (char *)ibody_finder.s.base + value_offs; |
1029 | memcpy(dest: buf, src: data_loc, |
1030 | size: (buf_len < value_len) ? buf_len : value_len); |
1031 | } |
1032 | } else { |
1033 | struct ext4_block block; |
1034 | |
1035 | /* Return ENODATA if there is no EA block */ |
1036 | if (!xattr_block) { |
1037 | ret = ENODATA; |
1038 | goto out; |
1039 | } |
1040 | |
1041 | block_finder.i = i; |
1042 | ret = ext4_trans_block_get(bdev: fs->bdev, b: &block, lba: xattr_block); |
1043 | if (ret != EOK) |
1044 | goto out; |
1045 | |
1046 | ret = ext4_xattr_block_find_entry(inode_ref, finder: &block_finder, |
1047 | block: &block); |
1048 | if (ret != EOK) { |
1049 | ext4_block_set(bdev: fs->bdev, b: &block); |
1050 | goto out; |
1051 | } |
1052 | |
1053 | /* Return ENODATA if entry is not found */ |
1054 | if (block_finder.s.not_found) { |
1055 | ext4_block_set(bdev: fs->bdev, b: &block); |
1056 | ret = ENODATA; |
1057 | goto out; |
1058 | } |
1059 | |
1060 | value_len = to_le32(block_finder.s.here->e_value_size); |
1061 | value_offs = to_le32(block_finder.s.here->e_value_offs); |
1062 | if (buf_len && buf) { |
1063 | void *data_loc = |
1064 | (char *)block_finder.s.base + value_offs; |
1065 | memcpy(dest: buf, src: data_loc, |
1066 | size: (buf_len < value_len) ? buf_len : value_len); |
1067 | } |
1068 | |
1069 | /* |
1070 | * Free the xattr block buffer returned by |
1071 | * ext4_xattr_block_find_entry. |
1072 | */ |
1073 | ext4_block_set(bdev: fs->bdev, b: &block); |
1074 | } |
1075 | |
1076 | out: |
1077 | if (ret == EOK && data_len) |
1078 | *data_len = value_len; |
1079 | |
1080 | return ret; |
1081 | } |
1082 | |
1083 | /** |
1084 | * @brief Try to copy the content of an xattr block to a newly-allocated |
1085 | * block. If the operation fails, the block buffer provided by |
1086 | * caller will be freed |
1087 | * |
1088 | * @param inode_ref Inode reference |
1089 | * @param block The block buffer reference |
1090 | * @param new_block The newly-allocated block buffer reference |
1091 | * @param orig_block The block number of @block |
1092 | * @param allocated a new block is allocated |
1093 | * |
1094 | * @return Error code |
1095 | */ |
1096 | static int ext4_xattr_copy_new_block(struct ext4_inode_ref *inode_ref, |
1097 | struct ext4_block *block, |
1098 | struct ext4_block *new_block, |
1099 | ext4_fsblk_t *orig_block, bool *allocated) |
1100 | { |
1101 | int ret = EOK; |
1102 | ext4_fsblk_t xattr_block = 0; |
1103 | struct ext4_xattr_header *; |
1104 | struct ext4_fs *fs = inode_ref->fs; |
1105 | header = EXT4_XATTR_BHDR(block); |
1106 | |
1107 | if (orig_block) |
1108 | *orig_block = block->lb_id; |
1109 | |
1110 | if (allocated) |
1111 | *allocated = false; |
1112 | |
1113 | /* Only do copy when a block is referenced by more than one inode. */ |
1114 | if (to_le32(header->h_refcount) > 1) { |
1115 | ext4_fsblk_t goal = ext4_fs_inode_to_goal_block(inode_ref); |
1116 | |
1117 | /* Allocate a new block to be used by this inode */ |
1118 | ret = ext4_balloc_alloc_block(inode_ref, goal, baddr: &xattr_block); |
1119 | if (ret != EOK) |
1120 | goto out; |
1121 | |
1122 | ret = ext4_trans_block_get(bdev: fs->bdev, b: new_block, lba: xattr_block); |
1123 | if (ret != EOK) |
1124 | goto out; |
1125 | |
1126 | /* Copy the content of the whole block */ |
1127 | memcpy(dest: new_block->data, src: block->data, |
1128 | size: ext4_sb_get_block_size(s: &inode_ref->fs->sb)); |
1129 | |
1130 | /* |
1131 | * Decrement the reference count of the original xattr block |
1132 | * by one |
1133 | */ |
1134 | header->h_refcount = to_le32(to_le32(header->h_refcount) - 1); |
1135 | ext4_trans_set_block_dirty(buf: block->buf); |
1136 | ext4_trans_set_block_dirty(buf: new_block->buf); |
1137 | |
1138 | header = EXT4_XATTR_BHDR(new_block); |
1139 | header->h_refcount = to_le32(1); |
1140 | |
1141 | if (allocated) |
1142 | *allocated = true; |
1143 | } |
1144 | out: |
1145 | if (xattr_block) { |
1146 | if (ret != EOK) |
1147 | ext4_balloc_free_block(inode_ref, baddr: xattr_block); |
1148 | else { |
1149 | /* |
1150 | * Modify the in-inode pointer to point to the new xattr block |
1151 | */ |
1152 | ext4_inode_set_file_acl(inode: inode_ref->inode, sb: &fs->sb, acl: xattr_block); |
1153 | inode_ref->dirty = true; |
1154 | } |
1155 | } |
1156 | |
1157 | return ret; |
1158 | } |
1159 | |
1160 | /** |
1161 | * @brief Given an EA entry's name, remove the EA entry |
1162 | * |
1163 | * @param inode_ref Inode reference |
1164 | * @param name_index Name-index |
1165 | * @param name Name of the EA entry to be removed |
1166 | * @param name_len Length of name in bytes |
1167 | * |
1168 | * @return Error code |
1169 | */ |
1170 | int ext4_xattr_remove(struct ext4_inode_ref *inode_ref, uint8_t name_index, |
1171 | const char *name, size_t name_len) |
1172 | { |
1173 | int ret = EOK; |
1174 | struct ext4_block block; |
1175 | struct ext4_xattr_finder ibody_finder; |
1176 | struct ext4_xattr_finder block_finder; |
1177 | bool use_block = false; |
1178 | bool block_loaded = false; |
1179 | struct ext4_xattr_info i; |
1180 | struct ext4_fs *fs = inode_ref->fs; |
1181 | ext4_fsblk_t xattr_block; |
1182 | |
1183 | xattr_block = ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &fs->sb); |
1184 | |
1185 | i.name_index = name_index; |
1186 | i.name = name; |
1187 | i.name_len = name_len; |
1188 | i.value = NULL; |
1189 | i.value_len = 0; |
1190 | |
1191 | ibody_finder.i = i; |
1192 | block_finder.i = i; |
1193 | |
1194 | ret = ext4_xattr_ibody_find_entry(inode_ref, finder: &ibody_finder); |
1195 | if (ret != EOK) |
1196 | goto out; |
1197 | |
1198 | if (ibody_finder.s.not_found && xattr_block) { |
1199 | ret = ext4_trans_block_get(bdev: fs->bdev, b: &block, lba: xattr_block); |
1200 | if (ret != EOK) |
1201 | goto out; |
1202 | |
1203 | block_loaded = true; |
1204 | block_finder.i = i; |
1205 | ret = ext4_xattr_block_find_entry(inode_ref, finder: &block_finder, |
1206 | block: &block); |
1207 | if (ret != EOK) |
1208 | goto out; |
1209 | |
1210 | /* Return ENODATA if entry is not found */ |
1211 | if (block_finder.s.not_found) { |
1212 | ret = ENODATA; |
1213 | goto out; |
1214 | } |
1215 | use_block = true; |
1216 | } |
1217 | |
1218 | if (use_block) { |
1219 | bool allocated = false; |
1220 | struct ext4_block new_block; |
1221 | |
1222 | /* |
1223 | * There will be no effect when the xattr block is only referenced |
1224 | * once. |
1225 | */ |
1226 | ret = ext4_xattr_copy_new_block(inode_ref, block: &block, new_block: &new_block, |
1227 | orig_block: &xattr_block, allocated: &allocated); |
1228 | if (ret != EOK) |
1229 | goto out; |
1230 | |
1231 | if (!allocated) { |
1232 | /* Prevent double-freeing */ |
1233 | block_loaded = false; |
1234 | new_block = block; |
1235 | } |
1236 | |
1237 | ret = ext4_xattr_block_find_entry(inode_ref, finder: &block_finder, |
1238 | block: &new_block); |
1239 | if (ret != EOK) |
1240 | goto out; |
1241 | |
1242 | /* Now remove the entry */ |
1243 | ext4_xattr_set_entry(i: &i, s: &block_finder.s, dry_run: false); |
1244 | |
1245 | if (ext4_xattr_is_empty(s: &block_finder.s)) { |
1246 | ext4_block_set(bdev: fs->bdev, b: &new_block); |
1247 | ext4_xattr_try_free_block(inode_ref); |
1248 | } else { |
1249 | struct ext4_xattr_header * = |
1250 | EXT4_XATTR_BHDR(&new_block); |
1251 | header = EXT4_XATTR_BHDR(&new_block); |
1252 | ext4_assert(block_finder.s.first); |
1253 | ext4_xattr_rehash(header, entry: block_finder.s.first); |
1254 | ext4_xattr_set_block_checksum(inode_ref, |
1255 | blocknr: block.lb_id, |
1256 | header); |
1257 | |
1258 | ext4_trans_set_block_dirty(buf: new_block.buf); |
1259 | ext4_block_set(bdev: fs->bdev, b: &new_block); |
1260 | } |
1261 | |
1262 | } else { |
1263 | /* Now remove the entry */ |
1264 | ext4_xattr_set_entry(i: &i, s: &block_finder.s, dry_run: false); |
1265 | inode_ref->dirty = true; |
1266 | } |
1267 | out: |
1268 | if (block_loaded) |
1269 | ext4_block_set(bdev: fs->bdev, b: &block); |
1270 | |
1271 | return ret; |
1272 | } |
1273 | |
1274 | /** |
1275 | * @brief Insert/overwrite an EA entry into/in a xattr block |
1276 | * |
1277 | * @param inode_ref Inode reference |
1278 | * @param i The information of the given EA entry |
1279 | * |
1280 | * @return Error code |
1281 | */ |
1282 | static int ext4_xattr_block_set(struct ext4_inode_ref *inode_ref, |
1283 | struct ext4_xattr_info *i, |
1284 | bool no_insert) |
1285 | { |
1286 | int ret = EOK; |
1287 | bool allocated = false; |
1288 | struct ext4_fs *fs = inode_ref->fs; |
1289 | struct ext4_block block, new_block; |
1290 | ext4_fsblk_t orig_xattr_block; |
1291 | |
1292 | orig_xattr_block = ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &fs->sb); |
1293 | |
1294 | ext4_assert(i->value); |
1295 | if (!orig_xattr_block) { |
1296 | struct ext4_xattr_search s; |
1297 | struct ext4_xattr_header *; |
1298 | |
1299 | /* If insertion of new entry is not allowed... */ |
1300 | if (no_insert) { |
1301 | ret = ENODATA; |
1302 | goto out; |
1303 | } |
1304 | |
1305 | ret = ext4_xattr_try_alloc_block(inode_ref); |
1306 | if (ret != EOK) |
1307 | goto out; |
1308 | |
1309 | orig_xattr_block = |
1310 | ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &fs->sb); |
1311 | ret = ext4_trans_block_get(bdev: fs->bdev, b: &block, lba: orig_xattr_block); |
1312 | if (ret != EOK) { |
1313 | ext4_xattr_try_free_block(inode_ref); |
1314 | goto out; |
1315 | } |
1316 | |
1317 | ext4_xattr_block_initialize(inode_ref, block: &block); |
1318 | ext4_xattr_block_init_search(inode_ref, s: &s, block: &block); |
1319 | |
1320 | ret = ext4_xattr_set_entry(i, s: &s, dry_run: false); |
1321 | if (ret == EOK) { |
1322 | header = EXT4_XATTR_BHDR(&block); |
1323 | |
1324 | ext4_assert(s.here); |
1325 | ext4_assert(s.first); |
1326 | ext4_xattr_compute_hash(header, entry: s.here); |
1327 | ext4_xattr_rehash(header, entry: s.first); |
1328 | ext4_xattr_set_block_checksum(inode_ref, |
1329 | blocknr: block.lb_id, |
1330 | header); |
1331 | ext4_trans_set_block_dirty(buf: block.buf); |
1332 | } |
1333 | ext4_block_set(bdev: fs->bdev, b: &block); |
1334 | if (ret != EOK) |
1335 | ext4_xattr_try_free_block(inode_ref); |
1336 | |
1337 | } else { |
1338 | struct ext4_xattr_finder finder; |
1339 | struct ext4_xattr_header *; |
1340 | finder.i = *i; |
1341 | ret = ext4_trans_block_get(bdev: fs->bdev, b: &block, lba: orig_xattr_block); |
1342 | if (ret != EOK) |
1343 | goto out; |
1344 | |
1345 | header = EXT4_XATTR_BHDR(&block); |
1346 | |
1347 | /* |
1348 | * Consider the following case when insertion of new |
1349 | * entry is not allowed |
1350 | */ |
1351 | if (to_le32(header->h_refcount) > 1 && no_insert) { |
1352 | /* |
1353 | * There are other people referencing the |
1354 | * same xattr block |
1355 | */ |
1356 | ret = ext4_xattr_block_find_entry(inode_ref, finder: &finder, block: &block); |
1357 | if (ret != EOK) { |
1358 | ext4_block_set(bdev: fs->bdev, b: &block); |
1359 | goto out; |
1360 | } |
1361 | if (finder.s.not_found) { |
1362 | ext4_block_set(bdev: fs->bdev, b: &block); |
1363 | ret = ENODATA; |
1364 | goto out; |
1365 | } |
1366 | } |
1367 | |
1368 | /* |
1369 | * There will be no effect when the xattr block is only referenced |
1370 | * once. |
1371 | */ |
1372 | ret = ext4_xattr_copy_new_block(inode_ref, block: &block, new_block: &new_block, |
1373 | orig_block: &orig_xattr_block, allocated: &allocated); |
1374 | if (ret != EOK) { |
1375 | ext4_block_set(bdev: fs->bdev, b: &block); |
1376 | goto out; |
1377 | } |
1378 | |
1379 | if (allocated) { |
1380 | ext4_block_set(bdev: fs->bdev, b: &block); |
1381 | new_block = block; |
1382 | } |
1383 | |
1384 | ret = ext4_xattr_block_find_entry(inode_ref, finder: &finder, block: &block); |
1385 | if (ret != EOK) { |
1386 | ext4_block_set(bdev: fs->bdev, b: &block); |
1387 | goto out; |
1388 | } |
1389 | |
1390 | ret = ext4_xattr_set_entry(i, s: &finder.s, dry_run: false); |
1391 | if (ret == EOK) { |
1392 | header = EXT4_XATTR_BHDR(&block); |
1393 | |
1394 | ext4_assert(finder.s.here); |
1395 | ext4_assert(finder.s.first); |
1396 | ext4_xattr_compute_hash(header, entry: finder.s.here); |
1397 | ext4_xattr_rehash(header, entry: finder.s.first); |
1398 | ext4_xattr_set_block_checksum(inode_ref, |
1399 | blocknr: block.lb_id, |
1400 | header); |
1401 | ext4_trans_set_block_dirty(buf: block.buf); |
1402 | } |
1403 | ext4_block_set(bdev: fs->bdev, b: &block); |
1404 | } |
1405 | out: |
1406 | return ret; |
1407 | } |
1408 | |
1409 | /** |
1410 | * @brief Remove an EA entry from a xattr block |
1411 | * |
1412 | * @param inode_ref Inode reference |
1413 | * @param i The information of the given EA entry |
1414 | * |
1415 | * @return Error code |
1416 | */ |
1417 | static int ext4_xattr_block_remove(struct ext4_inode_ref *inode_ref, |
1418 | struct ext4_xattr_info *i) |
1419 | { |
1420 | int ret = EOK; |
1421 | bool allocated = false; |
1422 | const void *value = i->value; |
1423 | struct ext4_fs *fs = inode_ref->fs; |
1424 | struct ext4_xattr_finder finder; |
1425 | struct ext4_block block, new_block; |
1426 | struct ext4_xattr_header *; |
1427 | ext4_fsblk_t orig_xattr_block; |
1428 | orig_xattr_block = ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &fs->sb); |
1429 | |
1430 | ext4_assert(orig_xattr_block); |
1431 | ret = ext4_trans_block_get(bdev: fs->bdev, b: &block, lba: orig_xattr_block); |
1432 | if (ret != EOK) |
1433 | goto out; |
1434 | |
1435 | /* |
1436 | * There will be no effect when the xattr block is only referenced |
1437 | * once. |
1438 | */ |
1439 | ret = ext4_xattr_copy_new_block(inode_ref, block: &block, new_block: &new_block, |
1440 | orig_block: &orig_xattr_block, allocated: &allocated); |
1441 | if (ret != EOK) { |
1442 | ext4_block_set(bdev: fs->bdev, b: &block); |
1443 | goto out; |
1444 | } |
1445 | |
1446 | if (allocated) { |
1447 | ext4_block_set(bdev: fs->bdev, b: &block); |
1448 | block = new_block; |
1449 | } |
1450 | |
1451 | ext4_xattr_block_find_entry(inode_ref, finder: &finder, block: &block); |
1452 | |
1453 | if (!finder.s.not_found) { |
1454 | i->value = NULL; |
1455 | ret = ext4_xattr_set_entry(i, s: &finder.s, dry_run: false); |
1456 | i->value = value; |
1457 | |
1458 | header = EXT4_XATTR_BHDR(&block); |
1459 | ext4_assert(finder.s.first); |
1460 | ext4_xattr_rehash(header, entry: finder.s.first); |
1461 | ext4_xattr_set_block_checksum(inode_ref, |
1462 | blocknr: block.lb_id, |
1463 | header); |
1464 | ext4_trans_set_block_dirty(buf: block.buf); |
1465 | } |
1466 | |
1467 | ext4_block_set(bdev: fs->bdev, b: &block); |
1468 | out: |
1469 | return ret; |
1470 | } |
1471 | |
1472 | /** |
1473 | * @brief Insert an EA entry into a given inode reference |
1474 | * |
1475 | * @param inode_ref Inode reference |
1476 | * @param name_index Name-index |
1477 | * @param name Name of the EA entry to be inserted |
1478 | * @param name_len Length of name in bytes |
1479 | * @param value Input buffer to hold content |
1480 | * @param value_len Length of input content |
1481 | * |
1482 | * @return Error code |
1483 | */ |
1484 | int ext4_xattr_set(struct ext4_inode_ref *inode_ref, uint8_t name_index, |
1485 | const char *name, size_t name_len, const void *value, |
1486 | size_t value_len) |
1487 | { |
1488 | int ret = EOK; |
1489 | struct ext4_fs *fs = inode_ref->fs; |
1490 | struct ext4_xattr_finder ibody_finder; |
1491 | struct ext4_xattr_info i; |
1492 | bool block_found = false; |
1493 | ext4_fsblk_t orig_xattr_block; |
1494 | size_t = |
1495 | ext4_inode_get_extra_isize(sb: &fs->sb, inode: inode_ref->inode); |
1496 | |
1497 | i.name_index = name_index; |
1498 | i.name = name; |
1499 | i.name_len = name_len; |
1500 | i.value = (value_len) ? value : &ext4_xattr_empty_value; |
1501 | i.value_len = value_len; |
1502 | |
1503 | ibody_finder.i = i; |
1504 | |
1505 | orig_xattr_block = ext4_inode_get_file_acl(inode: inode_ref->inode, sb: &fs->sb); |
1506 | |
1507 | /* |
1508 | * Even if entry is not found, search context block inside the |
1509 | * finder is still valid and can be used to insert entry. |
1510 | */ |
1511 | ret = ext4_xattr_ibody_find_entry(inode_ref, finder: &ibody_finder); |
1512 | if (ret != EOK) { |
1513 | ext4_xattr_ibody_initialize(inode_ref); |
1514 | ext4_xattr_ibody_find_entry(inode_ref, finder: &ibody_finder); |
1515 | } |
1516 | |
1517 | if (ibody_finder.s.not_found) { |
1518 | if (orig_xattr_block) { |
1519 | block_found = true; |
1520 | ret = ext4_xattr_block_set(inode_ref, i: &i, no_insert: true); |
1521 | if (ret == ENOSPC) |
1522 | goto try_insert; |
1523 | else if (ret == ENODATA) |
1524 | goto try_insert; |
1525 | else if (ret != EOK) |
1526 | goto out; |
1527 | |
1528 | } else |
1529 | goto try_insert; |
1530 | |
1531 | } else { |
1532 | try_insert: |
1533 | /* Only try to set entry in ibody if inode is sufficiently large */ |
1534 | if (extra_isize) |
1535 | ret = ext4_xattr_set_entry(i: &i, s: &ibody_finder.s, dry_run: false); |
1536 | else |
1537 | ret = ENOSPC; |
1538 | |
1539 | if (ret == ENOSPC) { |
1540 | if (!block_found) { |
1541 | ret = ext4_xattr_block_set(inode_ref, i: &i, no_insert: false); |
1542 | ibody_finder.i.value = NULL; |
1543 | ext4_xattr_set_entry(i: &ibody_finder.i, |
1544 | s: &ibody_finder.s, dry_run: false); |
1545 | inode_ref->dirty = true; |
1546 | } |
1547 | |
1548 | } else if (ret == EOK) { |
1549 | if (block_found) |
1550 | ret = ext4_xattr_block_remove(inode_ref, i: &i); |
1551 | |
1552 | inode_ref->dirty = true; |
1553 | } |
1554 | } |
1555 | |
1556 | out: |
1557 | return ret; |
1558 | } |
1559 | |
1560 | #endif |
1561 | |
1562 | /** |
1563 | * @} |
1564 | */ |
1565 | |