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