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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since fcb0d76 was fcb0d76, checked in by Jiri Svoboda <jiri@…>, 8 years ago

Libext4 should try to include what it uses.

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