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
92struct ext4_xattr_header {
93 uint32_t h_magic; /* magic number for identification */
94 uint32_t h_refcount; /* reference count */
95 uint32_t h_blocks; /* number of disk blocks used */
96 uint32_t h_hash; /* hash value of all attributes */
97 uint32_t h_checksum; /* crc32c(uuid+id+xattrblock) */
98 /* id = inum if refcount=1, blknum otherwise */
99 uint32_t h_reserved[3]; /* zero right now */
100};
101
102struct ext4_xattr_ibody_header {
103 uint32_t h_magic; /* magic number for identification */
104};
105
106struct 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
121static inline void ext4_xattr_compute_hash(struct ext4_xattr_header *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 */
154static void ext4_xattr_rehash(struct ext4_xattr_header *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
177static uint32_t ext4_xattr_block_checksum(struct ext4_inode_ref *inode_ref,
178 ext4_fsblk_t blocknr,
179 struct ext4_xattr_header *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
209static void ext4_xattr_set_block_checksum(struct ext4_inode_ref *inode_ref,
210 ext4_fsblk_t blocknr __unused,
211 struct ext4_xattr_header *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
221struct xattr_prefix {
222 const char *prefix;
223 uint8_t name_index;
224};
225
226static 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
237const char *ext4_extract_xattr_name(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
280const 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
300static 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 */
311static 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
450static 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 */
465static 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 */
506static 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 *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 */
574static 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 *iheader;
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 */
637struct 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
654static void ext4_xattr_ibody_initialize(struct ext4_inode_ref *inode_ref)
655{
656 struct ext4_xattr_ibody_header *header;
657 struct ext4_fs *fs = inode_ref->fs;
658 size_t extra_isize =
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 */
676static void ext4_xattr_block_initialize(struct ext4_inode_ref *inode_ref,
677 struct ext4_block *block)
678{
679 struct ext4_xattr_header *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
692static 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 */
715static 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 */
748static 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 *iheader;
753 size_t extra_isize =
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 */
789static 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
812Finish:
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 */
823static 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 */
851int 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 *iheader;
858 size_t extra_isize =
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;
973out:
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 */
996int 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
1076out:
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 */
1096static 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 *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 }
1144out:
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 */
1170int 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 *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 }
1267out:
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 */
1282static 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 *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 *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 }
1405out:
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 */
1417static 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 *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);
1468out:
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 */
1484int 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 extra_isize =
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
1556out:
1557 return ret;
1558}
1559
1560#endif
1561
1562/**
1563 * @}
1564 */
1565