source: mainline/uspace/lib/ext4/src/directory_index.c@ a35b458

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a35b458 was a35b458, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 7 years ago

style: Remove trailing whitespace on _all_ lines, including empty ones, for particular file types.

Command used: tools/srepl '\s\+$' '' -- *.c *.h *.py *.sh *.s *.S *.ag

Currently, whitespace on empty lines is very inconsistent.
There are two basic choices: Either remove the whitespace, or keep empty lines
indented to the level of surrounding code. The former is AFAICT more common,
and also much easier to do automatically.

Alternatively, we could write script for automatic indentation, and use that
instead. However, if such a script exists, it's possible to use the indented
style locally, by having the editor apply relevant conversions on load/save,
without affecting remote repository. IMO, it makes more sense to adopt
the simpler rule.

  • Property mode set to 100644
File size: 31.3 KB
Line 
1/*
2 * Copyright (c) 2012 Frantisek Princ
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libext4
30 * @{
31 */
32/**
33 * @file directory_index.c
34 * @brief Ext4 directory index operations.
35 */
36
37#include <byteorder.h>
38#include <errno.h>
39#include <mem.h>
40#include <stdlib.h>
41#include <str.h>
42#include "ext4/directory.h"
43#include "ext4/directory_index.h"
44#include "ext4/filesystem.h"
45#include "ext4/hash.h"
46#include "ext4/inode.h"
47#include "ext4/superblock.h"
48
49/** Type entry to pass to sorting algorithm.
50 *
51 */
52typedef struct ext4_dx_sort_entry {
53 uint32_t hash;
54 uint32_t rec_len;
55 void *dentry;
56} ext4_dx_sort_entry_t;
57
58/** Get hash version used in directory index.
59 *
60 * @param root_info Pointer to root info structure of index
61 *
62 * @return Hash algorithm version
63 *
64 */
65uint8_t ext4_directory_dx_root_info_get_hash_version(
66 ext4_directory_dx_root_info_t *root_info)
67{
68 return root_info->hash_version;
69}
70
71/** Set hash version, that will be used in directory index.
72 *
73 * @param root_info Pointer to root info structure of index
74 * @param version Hash algorithm version
75 *
76 */
77void ext4_directory_dx_root_info_set_hash_version(
78 ext4_directory_dx_root_info_t *root_info, uint8_t version)
79{
80 root_info->hash_version = version;
81}
82
83/** Get length of root_info structure in bytes.
84 *
85 * @param root_info Pointer to root info structure of index
86 *
87 * @return Length of the structure
88 *
89 */
90uint8_t ext4_directory_dx_root_info_get_info_length(
91 ext4_directory_dx_root_info_t *root_info)
92{
93 return root_info->info_length;
94}
95
96/** Set length of root_info structure in bytes.
97 *
98 * @param root_info Pointer to root info structure of index
99 * @param info_length Length of the structure
100 *
101 */
102void ext4_directory_dx_root_info_set_info_length(
103 ext4_directory_dx_root_info_t *root_info, uint8_t info_length)
104{
105 root_info->info_length = info_length;
106}
107
108/** Get number of indirect levels of HTree.
109 *
110 * @param root_info Pointer to root info structure of index
111 *
112 * @return Height of HTree (actually only 0 or 1)
113 *
114 */
115uint8_t ext4_directory_dx_root_info_get_indirect_levels(
116 ext4_directory_dx_root_info_t *root_info)
117{
118 return root_info->indirect_levels;
119}
120
121/** Set number of indirect levels of HTree.
122 *
123 * @param root_info Pointer to root info structure of index
124 * @param levels Height of HTree (actually only 0 or 1)
125 *
126 */
127void ext4_directory_dx_root_info_set_indirect_levels(
128 ext4_directory_dx_root_info_t *root_info, uint8_t levels)
129{
130 root_info->indirect_levels = levels;
131}
132
133/** Get maximum number of index node entries.
134 *
135 * @param countlimit Pointer to counlimit structure
136 *
137 * @return Maximum of entries in node
138 *
139 */
140uint16_t ext4_directory_dx_countlimit_get_limit(
141 ext4_directory_dx_countlimit_t *countlimit)
142{
143 return uint16_t_le2host(countlimit->limit);
144}
145
146/** Set maximum number of index node entries.
147 *
148 * @param countlimit Pointer to counlimit structure
149 * @param limit Maximum of entries in node
150 *
151 */
152void ext4_directory_dx_countlimit_set_limit(
153 ext4_directory_dx_countlimit_t *countlimit, uint16_t limit)
154{
155 countlimit->limit = host2uint16_t_le(limit);
156}
157
158/** Get current number of index node entries.
159 *
160 * @param countlimit Pointer to counlimit structure
161 *
162 * @return Number of entries in node
163 *
164 */
165uint16_t ext4_directory_dx_countlimit_get_count(
166 ext4_directory_dx_countlimit_t *countlimit)
167{
168 return uint16_t_le2host(countlimit->count);
169}
170
171/** Set current number of index node entries.
172 *
173 * @param countlimit Pointer to counlimit structure
174 * @param count Number of entries in node
175 *
176 */
177void ext4_directory_dx_countlimit_set_count(
178 ext4_directory_dx_countlimit_t *countlimit, uint16_t count)
179{
180 countlimit->count = host2uint16_t_le(count);
181}
182
183/** Get hash value of index entry.
184 *
185 * @param entry Pointer to index entry
186 *
187 * @return Hash value
188 *
189 */
190uint32_t ext4_directory_dx_entry_get_hash(ext4_directory_dx_entry_t *entry)
191{
192 return uint32_t_le2host(entry->hash);
193}
194
195/** Set hash value of index entry.
196 *
197 * @param entry Pointer to index entry
198 * @param hash Hash value
199 *
200 */
201void ext4_directory_dx_entry_set_hash(ext4_directory_dx_entry_t *entry,
202 uint32_t hash)
203{
204 entry->hash = host2uint32_t_le(hash);
205}
206
207/** Get block address where child node is located.
208 *
209 * @param entry Pointer to index entry
210 *
211 * @return Block address of child node
212 *
213 */
214uint32_t ext4_directory_dx_entry_get_block(ext4_directory_dx_entry_t *entry)
215{
216 return uint32_t_le2host(entry->block);
217}
218
219/** Set block address where child node is located.
220 *
221 * @param entry Pointer to index entry
222 * @param block Block address of child node
223 *
224 */
225void ext4_directory_dx_entry_set_block(ext4_directory_dx_entry_t *entry,
226 uint32_t block)
227{
228 entry->block = host2uint32_t_le(block);
229}
230
231/** Initialize index structure of new directory.
232 *
233 * @param dir Pointer to directory i-node
234 *
235 * @return Error code
236 *
237 */
238errno_t ext4_directory_dx_init(ext4_inode_ref_t *dir)
239{
240 /* Load block 0, where will be index root located */
241 uint32_t fblock;
242 errno_t rc = ext4_filesystem_get_inode_data_block_index(dir, 0,
243 &fblock);
244 if (rc != EOK)
245 return rc;
246
247 block_t *block;
248 rc = block_get(&block, dir->fs->device, fblock, BLOCK_FLAGS_NONE);
249 if (rc != EOK)
250 return rc;
251
252 /* Initialize pointers to data structures */
253 ext4_directory_dx_root_t *root = block->data;
254 ext4_directory_dx_root_info_t *info = &(root->info);
255
256 /* Initialize root info structure */
257 uint8_t hash_version =
258 ext4_superblock_get_default_hash_version(dir->fs->superblock);
259
260 ext4_directory_dx_root_info_set_hash_version(info, hash_version);
261 ext4_directory_dx_root_info_set_indirect_levels(info, 0);
262 ext4_directory_dx_root_info_set_info_length(info, 8);
263
264 /* Set limit and current number of entries */
265 ext4_directory_dx_countlimit_t *countlimit =
266 (ext4_directory_dx_countlimit_t *) &root->entries;
267 ext4_directory_dx_countlimit_set_count(countlimit, 1);
268
269 uint32_t block_size =
270 ext4_superblock_get_block_size(dir->fs->superblock);
271 uint32_t entry_space =
272 block_size - 2 * sizeof(ext4_directory_dx_dot_entry_t) -
273 sizeof(ext4_directory_dx_root_info_t);
274 uint16_t root_limit = entry_space / sizeof(ext4_directory_dx_entry_t);
275 ext4_directory_dx_countlimit_set_limit(countlimit, root_limit);
276
277 /* Append new block, where will be new entries inserted in the future */
278 uint32_t iblock;
279 rc = ext4_filesystem_append_inode_block(dir, &fblock, &iblock);
280 if (rc != EOK) {
281 block_put(block);
282 return rc;
283 }
284
285 block_t *new_block;
286 rc = block_get(&new_block, dir->fs->device, fblock, BLOCK_FLAGS_NOREAD);
287 if (rc != EOK) {
288 block_put(block);
289 return rc;
290 }
291
292 /* Fill the whole block with empty entry */
293 ext4_directory_entry_ll_t *block_entry = new_block->data;
294 ext4_directory_entry_ll_set_entry_length(block_entry, block_size);
295 ext4_directory_entry_ll_set_inode(block_entry, 0);
296
297 new_block->dirty = true;
298 rc = block_put(new_block);
299 if (rc != EOK) {
300 block_put(block);
301 return rc;
302 }
303
304 /* Connect new block to the only entry in index */
305 ext4_directory_dx_entry_t *entry = root->entries;
306 ext4_directory_dx_entry_set_block(entry, iblock);
307
308 block->dirty = true;
309
310 return block_put(block);
311}
312
313/** Initialize hash info structure necessary for index operations.
314 *
315 * @param hinfo Pointer to hinfo to be initialized
316 * @param root_block Root block (number 0) of index
317 * @param sb Pointer to superblock
318 * @param name_len Length of name to be computed hash value from
319 * @param name Name to be computed hash value from
320 *
321 * @return Error code
322 *
323 */
324static errno_t ext4_directory_hinfo_init(ext4_hash_info_t *hinfo,
325 block_t *root_block, ext4_superblock_t *sb, size_t name_len,
326 const char *name)
327{
328 ext4_directory_dx_root_t *root =
329 (ext4_directory_dx_root_t *) root_block->data;
330
331 if ((root->info.hash_version != EXT4_HASH_VERSION_TEA) &&
332 (root->info.hash_version != EXT4_HASH_VERSION_HALF_MD4) &&
333 (root->info.hash_version != EXT4_HASH_VERSION_LEGACY))
334 return EXT4_ERR_BAD_DX_DIR;
335
336 /* Check unused flags */
337 if (root->info.unused_flags != 0)
338 return EXT4_ERR_BAD_DX_DIR;
339
340 /* Check indirect levels */
341 if (root->info.indirect_levels > 1)
342 return EXT4_ERR_BAD_DX_DIR;
343
344 /* Check if node limit is correct */
345 uint32_t block_size = ext4_superblock_get_block_size(sb);
346 uint32_t entry_space = block_size;
347 entry_space -= 2 * sizeof(ext4_directory_dx_dot_entry_t);
348 entry_space -= sizeof(ext4_directory_dx_root_info_t);
349 entry_space = entry_space / sizeof(ext4_directory_dx_entry_t);
350
351 uint16_t limit = ext4_directory_dx_countlimit_get_limit(
352 (ext4_directory_dx_countlimit_t *) &root->entries);
353 if (limit != entry_space)
354 return EXT4_ERR_BAD_DX_DIR;
355
356 /* Check hash version and modify if necessary */
357 hinfo->hash_version =
358 ext4_directory_dx_root_info_get_hash_version(&root->info);
359 if ((hinfo->hash_version <= EXT4_HASH_VERSION_TEA) &&
360 (ext4_superblock_has_flag(sb, EXT4_SUPERBLOCK_FLAGS_UNSIGNED_HASH))) {
361 /* 3 is magic from ext4 linux implementation */
362 hinfo->hash_version += 3;
363 }
364
365 /* Load hash seed from superblock */
366 hinfo->seed = ext4_superblock_get_hash_seed(sb);
367
368 /* Compute hash value of name */
369 if (name)
370 ext4_hash_string(hinfo, name_len, name);
371
372 return EOK;
373}
374
375/** Walk through index tree and load leaf with corresponding hash value.
376 *
377 * @param hinfo Initialized hash info structure
378 * @param inode_ref Current i-node
379 * @param root_block Root block (iblock 0), where is root node located
380 * @param dx_block Pointer to leaf node in dx_blocks array
381 * @param dx_blocks Array with the whole path from root to leaf
382 *
383 * @return Error code
384 *
385 */
386static errno_t ext4_directory_dx_get_leaf(ext4_hash_info_t *hinfo,
387 ext4_inode_ref_t *inode_ref, block_t *root_block,
388 ext4_directory_dx_block_t **dx_block, ext4_directory_dx_block_t *dx_blocks)
389{
390 ext4_directory_dx_block_t *tmp_dx_block = dx_blocks;
391 ext4_directory_dx_root_t *root =
392 (ext4_directory_dx_root_t *) root_block->data;
393 ext4_directory_dx_entry_t *entries =
394 (ext4_directory_dx_entry_t *) &root->entries;
395
396 uint16_t limit = ext4_directory_dx_countlimit_get_limit(
397 (ext4_directory_dx_countlimit_t *) entries);
398 uint8_t indirect_level =
399 ext4_directory_dx_root_info_get_indirect_levels(&root->info);
400
401 block_t *tmp_block = root_block;
402 ext4_directory_dx_entry_t *p;
403 ext4_directory_dx_entry_t *q;
404 ext4_directory_dx_entry_t *m;
405 ext4_directory_dx_entry_t *at;
406
407 /* Walk through the index tree */
408 while (true) {
409 uint16_t count = ext4_directory_dx_countlimit_get_count(
410 (ext4_directory_dx_countlimit_t *) entries);
411 if ((count == 0) || (count > limit))
412 return EXT4_ERR_BAD_DX_DIR;
413
414 /* Do binary search in every node */
415 p = entries + 1;
416 q = entries + count - 1;
417
418 while (p <= q) {
419 m = p + (q - p) / 2;
420 if (ext4_directory_dx_entry_get_hash(m) > hinfo->hash)
421 q = m - 1;
422 else
423 p = m + 1;
424 }
425
426 at = p - 1;
427
428 /* Write results */
429 tmp_dx_block->block = tmp_block;
430 tmp_dx_block->entries = entries;
431 tmp_dx_block->position = at;
432
433 /* Is algorithm in the leaf? */
434 if (indirect_level == 0) {
435 *dx_block = tmp_dx_block;
436 return EOK;
437 }
438
439 /* Goto child node */
440 uint32_t next_block = ext4_directory_dx_entry_get_block(at);
441
442 indirect_level--;
443
444 uint32_t fblock;
445 errno_t rc = ext4_filesystem_get_inode_data_block_index(inode_ref,
446 next_block, &fblock);
447 if (rc != EOK)
448 return rc;
449
450 rc = block_get(&tmp_block, inode_ref->fs->device, fblock,
451 BLOCK_FLAGS_NONE);
452 if (rc != EOK)
453 return rc;
454
455 entries = ((ext4_directory_dx_node_t *) tmp_block->data)->entries;
456 limit = ext4_directory_dx_countlimit_get_limit(
457 (ext4_directory_dx_countlimit_t *) entries);
458
459 uint16_t entry_space =
460 ext4_superblock_get_block_size(inode_ref->fs->superblock) -
461 sizeof(ext4_directory_dx_dot_entry_t);
462 entry_space = entry_space / sizeof(ext4_directory_dx_entry_t);
463
464 if (limit != entry_space) {
465 block_put(tmp_block);
466 return EXT4_ERR_BAD_DX_DIR;
467 }
468
469 ++tmp_dx_block;
470 }
471
472 /* Unreachable */
473 return EOK;
474}
475
476/** Check if the the next block would be checked during entry search.
477 *
478 * @param inode_ref Directory i-node
479 * @param hash Hash value to check
480 * @param dx_block Current block
481 * @param dx_blocks Array with path from root to leaf node
482 *
483 * @return Error code
484 *
485 */
486static errno_t ext4_directory_dx_next_block(ext4_inode_ref_t *inode_ref,
487 uint32_t hash, ext4_directory_dx_block_t *dx_block,
488 ext4_directory_dx_block_t *dx_blocks)
489{
490 uint32_t num_handles = 0;
491 ext4_directory_dx_block_t *p = dx_block;
492
493 /* Try to find data block with next bunch of entries */
494 while (true) {
495 p->position++;
496 uint16_t count = ext4_directory_dx_countlimit_get_count(
497 (ext4_directory_dx_countlimit_t *) p->entries);
498
499 if (p->position < p->entries + count)
500 break;
501
502 if (p == dx_blocks)
503 return EOK;
504
505 num_handles++;
506 p--;
507 }
508
509 /* Check hash collision (if not occured - no next block cannot be used) */
510 uint32_t current_hash = ext4_directory_dx_entry_get_hash(p->position);
511 if ((hash & 1) == 0) {
512 if ((current_hash & ~1) != hash)
513 return 0;
514 }
515
516 /* Fill new path */
517 while (num_handles--) {
518 uint32_t block_idx =
519 ext4_directory_dx_entry_get_block(p->position);
520 uint32_t block_addr;
521
522 errno_t rc = ext4_filesystem_get_inode_data_block_index(inode_ref,
523 block_idx, &block_addr);
524 if (rc != EOK)
525 return rc;
526
527 block_t *block;
528 rc = block_get(&block, inode_ref->fs->device, block_addr, BLOCK_FLAGS_NONE);
529 if (rc != EOK)
530 return rc;
531
532 p++;
533
534 /* Don't forget to put old block (prevent memory leak) */
535 rc = block_put(p->block);
536 if (rc != EOK)
537 return rc;
538
539 p->block = block;
540 p->entries = ((ext4_directory_dx_node_t *) block->data)->entries;
541 p->position = p->entries;
542 }
543
544 return ENOENT;
545}
546
547/** Try to find directory entry using directory index.
548 *
549 * @param result Output value - if entry will be found,
550 * than will be passed through this parameter
551 * @param inode_ref Directory i-node
552 * @param name_len Length of name to be found
553 * @param name Name to be found
554 *
555 * @return Error code
556 *
557 */
558errno_t ext4_directory_dx_find_entry(ext4_directory_search_result_t *result,
559 ext4_inode_ref_t *inode_ref, size_t name_len, const char *name)
560{
561 /* Load direct block 0 (index root) */
562 uint32_t root_block_addr;
563 errno_t rc2;
564 errno_t rc = ext4_filesystem_get_inode_data_block_index(inode_ref, 0,
565 &root_block_addr);
566 if (rc != EOK)
567 return rc;
568
569 ext4_filesystem_t *fs = inode_ref->fs;
570
571 block_t *root_block;
572 rc = block_get(&root_block, fs->device, root_block_addr,
573 BLOCK_FLAGS_NONE);
574 if (rc != EOK)
575 return rc;
576
577 /* Initialize hash info (compute hash value) */
578 ext4_hash_info_t hinfo;
579 rc = ext4_directory_hinfo_init(&hinfo, root_block, fs->superblock,
580 name_len, name);
581 if (rc != EOK) {
582 block_put(root_block);
583 return EXT4_ERR_BAD_DX_DIR;
584 }
585
586 /*
587 * Hardcoded number 2 means maximum height of index tree,
588 * specified in the Linux driver.
589 */
590 ext4_directory_dx_block_t dx_blocks[2];
591 ext4_directory_dx_block_t *dx_block;
592 ext4_directory_dx_block_t *tmp;
593
594 rc = ext4_directory_dx_get_leaf(&hinfo, inode_ref, root_block,
595 &dx_block, dx_blocks);
596 if (rc != EOK) {
597 block_put(root_block);
598 return EXT4_ERR_BAD_DX_DIR;
599 }
600
601 do {
602 /* Load leaf block */
603 uint32_t leaf_block_idx =
604 ext4_directory_dx_entry_get_block(dx_block->position);
605 uint32_t leaf_block_addr;
606
607 rc = ext4_filesystem_get_inode_data_block_index(inode_ref,
608 leaf_block_idx, &leaf_block_addr);
609 if (rc != EOK)
610 goto cleanup;
611
612 block_t *leaf_block;
613 rc = block_get(&leaf_block, fs->device, leaf_block_addr,
614 BLOCK_FLAGS_NONE);
615 if (rc != EOK)
616 goto cleanup;
617
618 /* Linear search inside block */
619 ext4_directory_entry_ll_t *res_dentry;
620 rc = ext4_directory_find_in_block(leaf_block, fs->superblock,
621 name_len, name, &res_dentry);
622
623 /* Found => return it */
624 if (rc == EOK) {
625 result->block = leaf_block;
626 result->dentry = res_dentry;
627 goto cleanup;
628 }
629
630 /* Not found, leave untouched */
631 rc2 = block_put(leaf_block);
632 if (rc2 != EOK)
633 goto cleanup;
634
635 if (rc != ENOENT)
636 goto cleanup;
637
638 /* check if the next block could be checked */
639 rc = ext4_directory_dx_next_block(inode_ref, hinfo.hash,
640 dx_block, &dx_blocks[0]);
641 if (rc != EOK)
642 goto cleanup;
643
644 } while (rc == ENOENT);
645
646 /* Entry not found */
647 rc = ENOENT;
648
649cleanup:
650 /* The whole path must be released (preventing memory leak) */
651 tmp = dx_blocks;
652
653 while (tmp <= dx_block) {
654 rc2 = block_put(tmp->block);
655 if (rc == EOK && rc2 != EOK)
656 rc = rc2;
657 ++tmp;
658 }
659
660 return rc;
661}
662
663/** Compare function used to pass in quicksort implementation.
664 *
665 * It can compare two entries by hash value.
666 *
667 * @param arg1 First entry
668 * @param arg2 Second entry
669 *
670 * @return Classic compare result
671 * (0: equal, -1: arg1 < arg2, 1: arg1 > arg2)
672 *
673 */
674static int ext4_directory_dx_entry_comparator(const void *arg1,
675 const void *arg2)
676{
677 ext4_dx_sort_entry_t const *entry1 = arg1;
678 ext4_dx_sort_entry_t const *entry2 = arg2;
679
680 if (entry1->hash == entry2->hash)
681 return 0;
682
683 if (entry1->hash < entry2->hash)
684 return -1;
685 else
686 return 1;
687}
688
689/** Insert new index entry to block.
690 *
691 * Note that space for new entry must be checked by caller.
692 *
693 * @param index_block Block where to insert new entry
694 * @param hash Hash value covered by child node
695 * @param iblock Logical number of child block
696 *
697 */
698static void ext4_directory_dx_insert_entry(
699 ext4_directory_dx_block_t *index_block, uint32_t hash, uint32_t iblock)
700{
701 ext4_directory_dx_entry_t *old_index_entry = index_block->position;
702 ext4_directory_dx_entry_t *new_index_entry = old_index_entry + 1;
703
704 ext4_directory_dx_countlimit_t *countlimit =
705 (ext4_directory_dx_countlimit_t *) index_block->entries;
706 uint32_t count = ext4_directory_dx_countlimit_get_count(countlimit);
707
708 ext4_directory_dx_entry_t *start_index = index_block->entries;
709 size_t bytes = (void *) (start_index + count) - (void *) (new_index_entry);
710
711 memmove(new_index_entry + 1, new_index_entry, bytes);
712
713 ext4_directory_dx_entry_set_block(new_index_entry, iblock);
714 ext4_directory_dx_entry_set_hash(new_index_entry, hash);
715
716 ext4_directory_dx_countlimit_set_count(countlimit, count + 1);
717
718 index_block->block->dirty = true;
719}
720
721/** Split directory entries to two parts preventing node overflow.
722 *
723 * @param inode_ref Directory i-node
724 * @param hinfo Hash info
725 * @param old_data_block Block with data to be split
726 * @param index_block Block where index entries are located
727 * @param new_data_block Output value for newly allocated data block
728 *
729 */
730static errno_t ext4_directory_dx_split_data(ext4_inode_ref_t *inode_ref,
731 ext4_hash_info_t *hinfo, block_t *old_data_block,
732 ext4_directory_dx_block_t *index_block, block_t **new_data_block)
733{
734 errno_t rc = EOK;
735
736 /* Allocate buffer for directory entries */
737 uint32_t block_size =
738 ext4_superblock_get_block_size(inode_ref->fs->superblock);
739 void *entry_buffer = malloc(block_size);
740 if (entry_buffer == NULL)
741 return ENOMEM;
742
743 /* dot entry has the smallest size available */
744 uint32_t max_entry_count =
745 block_size / sizeof(ext4_directory_dx_dot_entry_t);
746
747 /* Allocate sort entry */
748 ext4_dx_sort_entry_t *sort_array =
749 malloc(max_entry_count * sizeof(ext4_dx_sort_entry_t));
750 if (sort_array == NULL) {
751 free(entry_buffer);
752 return ENOMEM;
753 }
754
755 uint32_t idx = 0;
756 uint32_t real_size = 0;
757
758 /* Initialize hinfo */
759 ext4_hash_info_t tmp_hinfo;
760 memcpy(&tmp_hinfo, hinfo, sizeof(ext4_hash_info_t));
761
762 /* Load all valid entries to the buffer */
763 ext4_directory_entry_ll_t *dentry = old_data_block->data;
764 void *entry_buffer_ptr = entry_buffer;
765 while ((void *)dentry < old_data_block->data + block_size) {
766 /* Read only valid entries */
767 if (ext4_directory_entry_ll_get_inode(dentry) != 0) {
768 uint8_t len = ext4_directory_entry_ll_get_name_length(
769 inode_ref->fs->superblock, dentry);
770 ext4_hash_string(&tmp_hinfo, len, (char *) dentry->name);
771
772 uint32_t rec_len = 8 + len;
773
774 if ((rec_len % 4) != 0)
775 rec_len += 4 - (rec_len % 4);
776
777 memcpy(entry_buffer_ptr, dentry, rec_len);
778
779 sort_array[idx].dentry = entry_buffer_ptr;
780 sort_array[idx].rec_len = rec_len;
781 sort_array[idx].hash = tmp_hinfo.hash;
782
783 entry_buffer_ptr += rec_len;
784 real_size += rec_len;
785 idx++;
786 }
787
788 dentry = (void *) dentry +
789 ext4_directory_entry_ll_get_entry_length(dentry);
790 }
791
792 /* Sort all entries */
793 qsort(sort_array, idx, sizeof(ext4_dx_sort_entry_t),
794 ext4_directory_dx_entry_comparator);
795
796 /* Allocate new block for store the second part of entries */
797 uint32_t new_fblock;
798 uint32_t new_iblock;
799 rc = ext4_filesystem_append_inode_block(inode_ref, &new_fblock,
800 &new_iblock);
801 if (rc != EOK) {
802 free(sort_array);
803 free(entry_buffer);
804 return rc;
805 }
806
807 /* Load new block */
808 block_t *new_data_block_tmp;
809 rc = block_get(&new_data_block_tmp, inode_ref->fs->device,
810 new_fblock, BLOCK_FLAGS_NOREAD);
811 if (rc != EOK) {
812 free(sort_array);
813 free(entry_buffer);
814 return rc;
815 }
816
817 /*
818 * Distribute entries to two blocks (by size)
819 * - compute the half
820 */
821 uint32_t new_hash = 0;
822 uint32_t current_size = 0;
823 uint32_t mid = 0;
824 for (uint32_t i = 0; i < idx; ++i) {
825 if ((current_size + sort_array[i].rec_len) > (real_size / 2)) {
826 new_hash = sort_array[i].hash;
827 mid = i;
828 break;
829 }
830
831 current_size += sort_array[i].rec_len;
832 }
833
834 /* Check hash collision */
835 uint32_t continued = 0;
836 if (new_hash == sort_array[mid-1].hash)
837 continued = 1;
838
839 uint32_t offset = 0;
840 void *ptr;
841
842 /* First part - to the old block */
843 for (uint32_t i = 0; i < mid; ++i) {
844 ptr = old_data_block->data + offset;
845 memcpy(ptr, sort_array[i].dentry, sort_array[i].rec_len);
846
847 ext4_directory_entry_ll_t *tmp = ptr;
848 if (i < (mid - 1))
849 ext4_directory_entry_ll_set_entry_length(tmp,
850 sort_array[i].rec_len);
851 else
852 ext4_directory_entry_ll_set_entry_length(tmp,
853 block_size - offset);
854
855 offset += sort_array[i].rec_len;
856 }
857
858 /* Second part - to the new block */
859 offset = 0;
860 for (uint32_t i = mid; i < idx; ++i) {
861 ptr = new_data_block_tmp->data + offset;
862 memcpy(ptr, sort_array[i].dentry, sort_array[i].rec_len);
863
864 ext4_directory_entry_ll_t *tmp = ptr;
865 if (i < (idx - 1))
866 ext4_directory_entry_ll_set_entry_length(tmp,
867 sort_array[i].rec_len);
868 else
869 ext4_directory_entry_ll_set_entry_length(tmp,
870 block_size - offset);
871
872 offset += sort_array[i].rec_len;
873 }
874
875 /* Do some steps to finish operation */
876 old_data_block->dirty = true;
877 new_data_block_tmp->dirty = true;
878
879 free(sort_array);
880 free(entry_buffer);
881
882 ext4_directory_dx_insert_entry(index_block, new_hash + continued,
883 new_iblock);
884
885 *new_data_block = new_data_block_tmp;
886
887 return EOK;
888}
889
890/** Split index node and maybe some parent nodes in the tree hierarchy.
891 *
892 * @param inode_ref Directory i-node
893 * @param dx_blocks Array with path from root to leaf node
894 * @param dx_block Leaf block to be split if needed
895 *
896 * @return Error code
897 *
898 */
899static errno_t ext4_directory_dx_split_index(ext4_inode_ref_t *inode_ref,
900 ext4_directory_dx_block_t *dx_blocks, ext4_directory_dx_block_t *dx_block)
901{
902 ext4_directory_dx_entry_t *entries;
903 if (dx_block == dx_blocks)
904 entries =
905 ((ext4_directory_dx_root_t *) dx_block->block->data)->entries;
906 else
907 entries =
908 ((ext4_directory_dx_node_t *) dx_block->block->data)->entries;
909
910 ext4_directory_dx_countlimit_t *countlimit =
911 (ext4_directory_dx_countlimit_t *) entries;
912
913 uint16_t leaf_limit =
914 ext4_directory_dx_countlimit_get_limit(countlimit);
915 uint16_t leaf_count =
916 ext4_directory_dx_countlimit_get_count(countlimit);
917
918 /* Check if is necessary to split index block */
919 if (leaf_limit == leaf_count) {
920 size_t levels = dx_block - dx_blocks;
921
922 ext4_directory_dx_entry_t *root_entries =
923 ((ext4_directory_dx_root_t *) dx_blocks[0].block->data)->entries;
924
925 ext4_directory_dx_countlimit_t *root_countlimit =
926 (ext4_directory_dx_countlimit_t *) root_entries;
927 uint16_t root_limit =
928 ext4_directory_dx_countlimit_get_limit(root_countlimit);
929 uint16_t root_count =
930 ext4_directory_dx_countlimit_get_count(root_countlimit);
931
932 /* Linux limitation */
933 if ((levels > 0) && (root_limit == root_count))
934 return ENOSPC;
935
936 /* Add new block to directory */
937 uint32_t new_fblock;
938 uint32_t new_iblock;
939 errno_t rc = ext4_filesystem_append_inode_block(inode_ref,
940 &new_fblock, &new_iblock);
941 if (rc != EOK)
942 return rc;
943
944 /* load new block */
945 block_t *new_block;
946 rc = block_get(&new_block, inode_ref->fs->device,
947 new_fblock, BLOCK_FLAGS_NOREAD);
948 if (rc != EOK)
949 return rc;
950
951 ext4_directory_dx_node_t *new_node = new_block->data;
952 ext4_directory_dx_entry_t *new_entries = new_node->entries;
953
954 uint32_t block_size =
955 ext4_superblock_get_block_size(inode_ref->fs->superblock);
956
957 /* Split leaf node */
958 if (levels > 0) {
959 uint32_t count_left = leaf_count / 2;
960 uint32_t count_right = leaf_count - count_left;
961 uint32_t hash_right =
962 ext4_directory_dx_entry_get_hash(entries + count_left);
963
964 /* Copy data to new node */
965 memcpy((void *) new_entries, (void *) (entries + count_left),
966 count_right * sizeof(ext4_directory_dx_entry_t));
967
968 /* Initialize new node */
969 ext4_directory_dx_countlimit_t *left_countlimit =
970 (ext4_directory_dx_countlimit_t *) entries;
971 ext4_directory_dx_countlimit_t *right_countlimit =
972 (ext4_directory_dx_countlimit_t *) new_entries;
973
974 ext4_directory_dx_countlimit_set_count(left_countlimit, count_left);
975 ext4_directory_dx_countlimit_set_count(right_countlimit, count_right);
976
977 uint32_t entry_space =
978 block_size - sizeof(ext4_fake_directory_entry_t);
979 uint32_t node_limit =
980 entry_space / sizeof(ext4_directory_dx_entry_t);
981 ext4_directory_dx_countlimit_set_limit(right_countlimit, node_limit);
982
983 /* Which index block is target for new entry */
984 uint32_t position_index = (dx_block->position - dx_block->entries);
985 if (position_index >= count_left) {
986 dx_block->block->dirty = true;
987
988 block_t *block_tmp = dx_block->block;
989 dx_block->block = new_block;
990 dx_block->position =
991 new_entries + position_index - count_left;
992 dx_block->entries = new_entries;
993
994 new_block = block_tmp;
995 }
996
997 /* Finally insert new entry */
998 ext4_directory_dx_insert_entry(dx_blocks, hash_right, new_iblock);
999
1000 return block_put(new_block);
1001 } else {
1002 /* Create second level index */
1003
1004 /* Copy data from root to child block */
1005 memcpy((void *) new_entries, (void *) entries,
1006 leaf_count * sizeof(ext4_directory_dx_entry_t));
1007
1008 ext4_directory_dx_countlimit_t *new_countlimit =
1009 (ext4_directory_dx_countlimit_t *) new_entries;
1010
1011 uint32_t entry_space =
1012 block_size - sizeof(ext4_fake_directory_entry_t);
1013 uint32_t node_limit =
1014 entry_space / sizeof(ext4_directory_dx_entry_t);
1015 ext4_directory_dx_countlimit_set_limit(new_countlimit, node_limit);
1016
1017 /* Set values in root node */
1018 ext4_directory_dx_countlimit_t *new_root_countlimit =
1019 (ext4_directory_dx_countlimit_t *) entries;
1020
1021 ext4_directory_dx_countlimit_set_count(new_root_countlimit, 1);
1022 ext4_directory_dx_entry_set_block(entries, new_iblock);
1023
1024 ((ext4_directory_dx_root_t *)
1025 dx_blocks[0].block->data)->info.indirect_levels = 1;
1026
1027 /* Add new entry to the path */
1028 dx_block = dx_blocks + 1;
1029 dx_block->position = dx_block->position - entries + new_entries;
1030 dx_block->entries = new_entries;
1031 dx_block->block = new_block;
1032 }
1033 }
1034
1035 return EOK;
1036}
1037
1038/** Add new entry to indexed directory
1039 *
1040 * @param parent Directory i-node
1041 * @param child I-node to be referenced from directory entry
1042 * @param name Name of new directory entry
1043 *
1044 * @return Error code
1045 *
1046 */
1047errno_t ext4_directory_dx_add_entry(ext4_inode_ref_t *parent,
1048 ext4_inode_ref_t *child, const char *name)
1049{
1050 errno_t rc2 = EOK;
1051
1052 /* Get direct block 0 (index root) */
1053 uint32_t root_block_addr;
1054 errno_t rc = ext4_filesystem_get_inode_data_block_index(parent, 0,
1055 &root_block_addr);
1056 if (rc != EOK)
1057 return rc;
1058
1059 ext4_filesystem_t *fs = parent->fs;
1060
1061 block_t *root_block;
1062 rc = block_get(&root_block, fs->device, root_block_addr,
1063 BLOCK_FLAGS_NONE);
1064 if (rc != EOK)
1065 return rc;
1066
1067 /* Initialize hinfo structure (mainly compute hash) */
1068 uint32_t name_len = str_size(name);
1069 ext4_hash_info_t hinfo;
1070 rc = ext4_directory_hinfo_init(&hinfo, root_block, fs->superblock,
1071 name_len, name);
1072 if (rc != EOK) {
1073 block_put(root_block);
1074 return EXT4_ERR_BAD_DX_DIR;
1075 }
1076
1077 /*
1078 * Hardcoded number 2 means maximum height of index
1079 * tree defined in Linux.
1080 */
1081 ext4_directory_dx_block_t dx_blocks[2];
1082 ext4_directory_dx_block_t *dx_block;
1083 ext4_directory_dx_block_t *dx_it;
1084
1085 rc = ext4_directory_dx_get_leaf(&hinfo, parent, root_block,
1086 &dx_block, dx_blocks);
1087 if (rc != EOK) {
1088 rc = EXT4_ERR_BAD_DX_DIR;
1089 goto release_index;
1090 }
1091
1092 /* Try to insert to existing data block */
1093 uint32_t leaf_block_idx =
1094 ext4_directory_dx_entry_get_block(dx_block->position);
1095 uint32_t leaf_block_addr;
1096 rc = ext4_filesystem_get_inode_data_block_index(parent, leaf_block_idx,
1097 &leaf_block_addr);
1098 if (rc != EOK)
1099 goto release_index;
1100
1101 block_t *target_block;
1102 rc = block_get(&target_block, fs->device, leaf_block_addr,
1103 BLOCK_FLAGS_NONE);
1104 if (rc != EOK)
1105 goto release_index;
1106
1107 /* Check if insert operation passed */
1108 rc = ext4_directory_try_insert_entry(fs->superblock, target_block, child,
1109 name, name_len);
1110 if (rc == EOK)
1111 goto release_target_index;
1112
1113 /*
1114 * Check if there is needed to split index node
1115 * (and recursively also parent nodes)
1116 */
1117 rc = ext4_directory_dx_split_index(parent, dx_blocks, dx_block);
1118 if (rc != EOK)
1119 goto release_target_index;
1120
1121 /* Split entries to two blocks (includes sorting by hash value) */
1122 block_t *new_block = NULL;
1123 rc = ext4_directory_dx_split_data(parent, &hinfo, target_block,
1124 dx_block, &new_block);
1125 if (rc != EOK) {
1126 rc2 = rc;
1127 goto release_target_index;
1128 }
1129
1130 /* Where to save new entry */
1131 uint32_t new_block_hash =
1132 ext4_directory_dx_entry_get_hash(dx_block->position + 1);
1133 if (hinfo.hash >= new_block_hash)
1134 rc = ext4_directory_try_insert_entry(fs->superblock, new_block,
1135 child, name, name_len);
1136 else
1137 rc = ext4_directory_try_insert_entry(fs->superblock, target_block,
1138 child, name, name_len);
1139
1140 /* Cleanup */
1141 rc = block_put(new_block);
1142 if (rc != EOK)
1143 return rc;
1144
1145 /* Cleanup operations */
1146
1147release_target_index:
1148 rc2 = rc;
1149
1150 rc = block_put(target_block);
1151 if (rc != EOK)
1152 return rc;
1153
1154release_index:
1155 if (rc != EOK)
1156 rc2 = rc;
1157
1158 dx_it = dx_blocks;
1159
1160 while (dx_it <= dx_block) {
1161 rc = block_put(dx_it->block);
1162 if (rc != EOK)
1163 return rc;
1164
1165 dx_it++;
1166 }
1167
1168 return rc2;
1169}
1170
1171/**
1172 * @}
1173 */
Note: See TracBrowser for help on using the repository browser.