1 | /* |
2 | * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) |
3 | * |
4 | * |
5 | * HelenOS: |
6 | * Copyright (c) 2012 Martin Sucha |
7 | * Copyright (c) 2012 Frantisek Princ |
8 | * All rights reserved. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * |
14 | * - Redistributions of source code must retain the above copyright |
15 | * notice, this list of conditions and the following disclaimer. |
16 | * - Redistributions in binary form must reproduce the above copyright |
17 | * notice, this list of conditions and the following disclaimer in the |
18 | * documentation and/or other materials provided with the distribution. |
19 | * - The name of the author may not be used to endorse or promote products |
20 | * derived from this software without specific prior written permission. |
21 | * |
22 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
23 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
25 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
27 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
28 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
29 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
30 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
31 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
32 | */ |
33 | |
34 | /** @addtogroup lwext4 |
35 | * @{ |
36 | */ |
37 | /** |
38 | * @file ext4_dir.h |
39 | * @brief Directory handle procedures. |
40 | */ |
41 | |
42 | #include <ext4_config.h> |
43 | #include <ext4_types.h> |
44 | #include <ext4_misc.h> |
45 | #include <ext4_errno.h> |
46 | #include <ext4_debug.h> |
47 | |
48 | #include <ext4_trans.h> |
49 | #include <ext4_dir.h> |
50 | #include <ext4_dir_idx.h> |
51 | #include <ext4_crc32.h> |
52 | #include <ext4_inode.h> |
53 | #include <ext4_fs.h> |
54 | |
55 | #include <string.h> |
56 | |
57 | /****************************************************************************/ |
58 | |
59 | /* Walk through a dirent block to find a checksum "dirent" at the tail */ |
60 | static struct ext4_dir_entry_tail * |
61 | ext4_dir_get_tail(struct ext4_inode_ref *inode_ref, |
62 | struct ext4_dir_en *de) |
63 | { |
64 | struct ext4_dir_entry_tail *t; |
65 | struct ext4_sblock *sb = &inode_ref->fs->sb; |
66 | |
67 | t = EXT4_DIRENT_TAIL(de, ext4_sb_get_block_size(sb)); |
68 | |
69 | if (t->reserved_zero1 || t->reserved_zero2) |
70 | return NULL; |
71 | if (to_le16(t->rec_len) != sizeof(struct ext4_dir_entry_tail)) |
72 | return NULL; |
73 | if (t->reserved_ft != EXT4_DIRENTRY_DIR_CSUM) |
74 | return NULL; |
75 | |
76 | return t; |
77 | } |
78 | |
79 | #if CONFIG_META_CSUM_ENABLE |
80 | static uint32_t ext4_dir_csum(struct ext4_inode_ref *inode_ref, |
81 | struct ext4_dir_en *dirent, int size) |
82 | { |
83 | uint32_t csum; |
84 | struct ext4_sblock *sb = &inode_ref->fs->sb; |
85 | uint32_t ino_index = to_le32(inode_ref->index); |
86 | uint32_t ino_gen = to_le32(ext4_inode_get_generation(inode_ref->inode)); |
87 | |
88 | /* First calculate crc32 checksum against fs uuid */ |
89 | csum = ext4_crc32c(EXT4_CRC32_INIT, buf: sb->uuid, size: sizeof(sb->uuid)); |
90 | /* Then calculate crc32 checksum against inode number |
91 | * and inode generation */ |
92 | csum = ext4_crc32c(crc: csum, buf: &ino_index, size: sizeof(ino_index)); |
93 | csum = ext4_crc32c(crc: csum, buf: &ino_gen, size: sizeof(ino_gen)); |
94 | /* Finally calculate crc32 checksum against directory entries */ |
95 | csum = ext4_crc32c(crc: csum, buf: dirent, size); |
96 | return csum; |
97 | } |
98 | #else |
99 | #define ext4_dir_csum(...) 0 |
100 | #endif |
101 | |
102 | bool ext4_dir_csum_verify(struct ext4_inode_ref *inode_ref, |
103 | struct ext4_dir_en *dirent) |
104 | { |
105 | #ifdef CONFIG_META_CSUM_ENABLE |
106 | struct ext4_dir_entry_tail *t; |
107 | struct ext4_sblock *sb = &inode_ref->fs->sb; |
108 | |
109 | /* Compute the checksum only if the filesystem supports it */ |
110 | if (ext4_sb_feature_ro_com(s: sb, EXT4_FRO_COM_METADATA_CSUM)) { |
111 | t = ext4_dir_get_tail(inode_ref, de: dirent); |
112 | if (!t) { |
113 | /* There is no space to hold the checksum */ |
114 | return false; |
115 | } |
116 | |
117 | ptrdiff_t __unused diff = (char *)t - (char *)dirent; |
118 | uint32_t csum = ext4_dir_csum(inode_ref, dirent, size: diff); |
119 | if (t->checksum != to_le32(csum)) |
120 | return false; |
121 | |
122 | } |
123 | #endif |
124 | return true; |
125 | } |
126 | |
127 | void ext4_dir_init_entry_tail(struct ext4_dir_entry_tail *t) |
128 | { |
129 | memset(dest: t, c: 0, size: sizeof(struct ext4_dir_entry_tail)); |
130 | t->rec_len = to_le16(sizeof(struct ext4_dir_entry_tail)); |
131 | t->reserved_ft = EXT4_DIRENTRY_DIR_CSUM; |
132 | } |
133 | |
134 | void ext4_dir_set_csum(struct ext4_inode_ref *inode_ref, |
135 | struct ext4_dir_en *dirent) |
136 | { |
137 | struct ext4_dir_entry_tail *t; |
138 | struct ext4_sblock *sb = &inode_ref->fs->sb; |
139 | |
140 | /* Compute the checksum only if the filesystem supports it */ |
141 | if (ext4_sb_feature_ro_com(s: sb, EXT4_FRO_COM_METADATA_CSUM)) { |
142 | t = ext4_dir_get_tail(inode_ref, de: dirent); |
143 | if (!t) { |
144 | /* There is no space to hold the checksum */ |
145 | return; |
146 | } |
147 | |
148 | ptrdiff_t __unused diff = (char *)t - (char *)dirent; |
149 | uint32_t csum = ext4_dir_csum(inode_ref, dirent, size: diff); |
150 | t->checksum = to_le32(csum); |
151 | } |
152 | } |
153 | |
154 | /**@brief Do some checks before returning iterator. |
155 | * @param it Iterator to be checked |
156 | * @param block_size Size of data block |
157 | * @return Error code |
158 | */ |
159 | static int ext4_dir_iterator_set(struct ext4_dir_iter *it, |
160 | uint32_t block_size) |
161 | { |
162 | uint32_t off_in_block = it->curr_off % block_size; |
163 | struct ext4_sblock *sb = &it->inode_ref->fs->sb; |
164 | |
165 | it->curr = NULL; |
166 | |
167 | /* Ensure proper alignment */ |
168 | if ((off_in_block % 4) != 0) |
169 | return EIO; |
170 | |
171 | /* Ensure that the core of the entry does not overflow the block */ |
172 | if (off_in_block > block_size - 8) |
173 | return EIO; |
174 | |
175 | struct ext4_dir_en *en; |
176 | en = (void *)(it->curr_blk.data + off_in_block); |
177 | |
178 | /* Ensure that the whole entry does not overflow the block */ |
179 | uint16_t length = ext4_dir_en_get_entry_len(de: en); |
180 | if (off_in_block + length > block_size) |
181 | return EIO; |
182 | |
183 | /* Ensure the name length is not too large */ |
184 | if (ext4_dir_en_get_name_len(sb, de: en) > length - 8) |
185 | return EIO; |
186 | |
187 | /* Everything OK - "publish" the entry */ |
188 | it->curr = en; |
189 | return EOK; |
190 | } |
191 | |
192 | /**@brief Seek to next valid directory entry. |
193 | * Here can be jumped to the next data block. |
194 | * @param it Initialized iterator |
195 | * @param pos Position of the next entry |
196 | * @return Error code |
197 | */ |
198 | static int ext4_dir_iterator_seek(struct ext4_dir_iter *it, uint64_t pos) |
199 | { |
200 | struct ext4_sblock *sb = &it->inode_ref->fs->sb; |
201 | struct ext4_inode *inode = it->inode_ref->inode; |
202 | struct ext4_blockdev *bdev = it->inode_ref->fs->bdev; |
203 | uint64_t size = ext4_inode_get_size(sb, inode); |
204 | int r; |
205 | |
206 | /* The iterator is not valid until we seek to the desired position */ |
207 | it->curr = NULL; |
208 | |
209 | /* Are we at the end? */ |
210 | if (pos >= size) { |
211 | if (it->curr_blk.lb_id) { |
212 | |
213 | r = ext4_block_set(bdev, b: &it->curr_blk); |
214 | it->curr_blk.lb_id = 0; |
215 | if (r != EOK) |
216 | return r; |
217 | } |
218 | |
219 | it->curr_off = pos; |
220 | return EOK; |
221 | } |
222 | |
223 | /* Compute next block address */ |
224 | uint32_t block_size = ext4_sb_get_block_size(s: sb); |
225 | uint64_t current_blk_idx = it->curr_off / block_size; |
226 | uint32_t next_blk_idx = (uint32_t)(pos / block_size); |
227 | |
228 | /* |
229 | * If we don't have a block or are moving across block boundary, |
230 | * we need to get another block |
231 | */ |
232 | if ((it->curr_blk.lb_id == 0) || |
233 | (current_blk_idx != next_blk_idx)) { |
234 | if (it->curr_blk.lb_id) { |
235 | r = ext4_block_set(bdev, b: &it->curr_blk); |
236 | it->curr_blk.lb_id = 0; |
237 | |
238 | if (r != EOK) |
239 | return r; |
240 | } |
241 | |
242 | ext4_fsblk_t next_blk; |
243 | r = ext4_fs_get_inode_dblk_idx(inode_ref: it->inode_ref, iblock: next_blk_idx, |
244 | fblock: &next_blk, support_unwritten: false); |
245 | if (r != EOK) |
246 | return r; |
247 | |
248 | r = ext4_trans_block_get(bdev, b: &it->curr_blk, lba: next_blk); |
249 | if (r != EOK) { |
250 | it->curr_blk.lb_id = 0; |
251 | return r; |
252 | } |
253 | } |
254 | |
255 | it->curr_off = pos; |
256 | return ext4_dir_iterator_set(it, block_size); |
257 | } |
258 | |
259 | int ext4_dir_iterator_init(struct ext4_dir_iter *it, |
260 | struct ext4_inode_ref *inode_ref, uint64_t pos) |
261 | { |
262 | it->inode_ref = inode_ref; |
263 | it->curr = 0; |
264 | it->curr_off = 0; |
265 | it->curr_blk.lb_id = 0; |
266 | |
267 | return ext4_dir_iterator_seek(it, pos); |
268 | } |
269 | |
270 | int ext4_dir_iterator_next(struct ext4_dir_iter *it) |
271 | { |
272 | int r = EOK; |
273 | uint16_t skip; |
274 | |
275 | while (r == EOK) { |
276 | skip = ext4_dir_en_get_entry_len(de: it->curr); |
277 | r = ext4_dir_iterator_seek(it, pos: it->curr_off + skip); |
278 | |
279 | if (!it->curr) |
280 | break; |
281 | /*Skip NULL referenced entry*/ |
282 | if (ext4_dir_en_get_inode(de: it->curr) != 0) |
283 | break; |
284 | } |
285 | |
286 | return r; |
287 | } |
288 | |
289 | int ext4_dir_iterator_fini(struct ext4_dir_iter *it) |
290 | { |
291 | it->curr = 0; |
292 | |
293 | if (it->curr_blk.lb_id) |
294 | return ext4_block_set(bdev: it->inode_ref->fs->bdev, b: &it->curr_blk); |
295 | |
296 | return EOK; |
297 | } |
298 | |
299 | void ext4_dir_write_entry(struct ext4_sblock *sb, struct ext4_dir_en *en, |
300 | uint16_t entry_len, struct ext4_inode_ref *child, |
301 | const char *name, size_t name_len) |
302 | { |
303 | /* Check maximum entry length */ |
304 | ext4_assert(entry_len <= ext4_sb_get_block_size(sb)); |
305 | |
306 | /* Set type of entry */ |
307 | switch (ext4_inode_type(sb, inode: child->inode)) { |
308 | case EXT4_INODE_MODE_DIRECTORY: |
309 | ext4_dir_en_set_inode_type(sb, de: en, t: EXT4_DE_DIR); |
310 | break; |
311 | case EXT4_INODE_MODE_FILE: |
312 | ext4_dir_en_set_inode_type(sb, de: en, t: EXT4_DE_REG_FILE); |
313 | break; |
314 | case EXT4_INODE_MODE_SOFTLINK: |
315 | ext4_dir_en_set_inode_type(sb, de: en, t: EXT4_DE_SYMLINK); |
316 | break; |
317 | case EXT4_INODE_MODE_CHARDEV: |
318 | ext4_dir_en_set_inode_type(sb, de: en, t: EXT4_DE_CHRDEV); |
319 | break; |
320 | case EXT4_INODE_MODE_BLOCKDEV: |
321 | ext4_dir_en_set_inode_type(sb, de: en, t: EXT4_DE_BLKDEV); |
322 | break; |
323 | case EXT4_INODE_MODE_FIFO: |
324 | ext4_dir_en_set_inode_type(sb, de: en, t: EXT4_DE_FIFO); |
325 | break; |
326 | case EXT4_INODE_MODE_SOCKET: |
327 | ext4_dir_en_set_inode_type(sb, de: en, t: EXT4_DE_SOCK); |
328 | break; |
329 | default: |
330 | /* FIXME: unsupported filetype */ |
331 | ext4_dir_en_set_inode_type(sb, de: en, t: EXT4_DE_UNKNOWN); |
332 | } |
333 | |
334 | /* Set basic attributes */ |
335 | ext4_dir_en_set_inode(de: en, inode: child->index); |
336 | ext4_dir_en_set_entry_len(de: en, l: entry_len); |
337 | ext4_dir_en_set_name_len(sb, de: en, len: (uint16_t)name_len); |
338 | |
339 | /* Write name */ |
340 | memcpy(dest: en->name, src: name, size: name_len); |
341 | } |
342 | |
343 | int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name, |
344 | uint32_t name_len, struct ext4_inode_ref *child) |
345 | { |
346 | int r; |
347 | struct ext4_fs *fs = parent->fs; |
348 | struct ext4_sblock *sb = &parent->fs->sb; |
349 | |
350 | #if CONFIG_DIR_INDEX_ENABLE |
351 | /* Index adding (if allowed) */ |
352 | if ((ext4_sb_feature_com(s: sb, EXT4_FCOM_DIR_INDEX)) && |
353 | (ext4_inode_has_flag(inode: parent->inode, EXT4_INODE_FLAG_INDEX))) { |
354 | r = ext4_dir_dx_add_entry(parent, child, name, name_len); |
355 | |
356 | /* Check if index is not corrupted */ |
357 | if (r != EXT4_ERR_BAD_DX_DIR) { |
358 | if (r != EOK) |
359 | return r; |
360 | |
361 | return EOK; |
362 | } |
363 | |
364 | /* Needed to clear dir index flag if corrupted */ |
365 | ext4_inode_clear_flag(inode: parent->inode, EXT4_INODE_FLAG_INDEX); |
366 | parent->dirty = true; |
367 | } |
368 | #endif |
369 | |
370 | /* Linear algorithm */ |
371 | uint32_t iblock = 0; |
372 | ext4_fsblk_t fblock = 0; |
373 | uint32_t block_size = ext4_sb_get_block_size(s: sb); |
374 | uint64_t inode_size = ext4_inode_get_size(sb, inode: parent->inode); |
375 | uint32_t total_blocks = (uint32_t)(inode_size / block_size); |
376 | |
377 | /* Find block, where is space for new entry and try to add */ |
378 | bool success = false; |
379 | for (iblock = 0; iblock < total_blocks; ++iblock) { |
380 | r = ext4_fs_get_inode_dblk_idx(inode_ref: parent, iblock, fblock: &fblock, support_unwritten: false); |
381 | if (r != EOK) |
382 | return r; |
383 | |
384 | struct ext4_block block; |
385 | r = ext4_trans_block_get(bdev: fs->bdev, b: &block, lba: fblock); |
386 | if (r != EOK) |
387 | return r; |
388 | |
389 | if (!ext4_dir_csum_verify(inode_ref: parent, dirent: (void *)block.data)) { |
390 | ext4_dbg(DEBUG_DIR, |
391 | DBG_WARN "Leaf block checksum failed." |
392 | "Inode: %" PRIu32", " |
393 | "Block: %" PRIu32"\n" , |
394 | parent->index, |
395 | iblock); |
396 | } |
397 | |
398 | /* If adding is successful, function can finish */ |
399 | r = ext4_dir_try_insert_entry(sb, inode_ref: parent, dst_blk: &block, child, |
400 | name, name_len); |
401 | if (r == EOK) |
402 | success = true; |
403 | |
404 | r = ext4_block_set(bdev: fs->bdev, b: &block); |
405 | if (r != EOK) |
406 | return r; |
407 | |
408 | if (success) |
409 | return EOK; |
410 | } |
411 | |
412 | /* No free block found - needed to allocate next data block */ |
413 | |
414 | iblock = 0; |
415 | fblock = 0; |
416 | r = ext4_fs_append_inode_dblk(inode_ref: parent, fblock: &fblock, iblock: &iblock); |
417 | if (r != EOK) |
418 | return r; |
419 | |
420 | /* Load new block */ |
421 | struct ext4_block b; |
422 | |
423 | r = ext4_trans_block_get_noread(bdev: fs->bdev, b: &b, lba: fblock); |
424 | if (r != EOK) |
425 | return r; |
426 | |
427 | /* Fill block with zeroes */ |
428 | memset(dest: b.data, c: 0, size: block_size); |
429 | struct ext4_dir_en *blk_en = (void *)b.data; |
430 | |
431 | /* Save new block */ |
432 | if (ext4_sb_feature_ro_com(s: sb, EXT4_FRO_COM_METADATA_CSUM)) { |
433 | uint16_t el = block_size - sizeof(struct ext4_dir_entry_tail); |
434 | ext4_dir_write_entry(sb, en: blk_en, entry_len: el, child, name, name_len); |
435 | ext4_dir_init_entry_tail(EXT4_DIRENT_TAIL(b.data, block_size)); |
436 | } else { |
437 | ext4_dir_write_entry(sb, en: blk_en, entry_len: block_size, child, name, |
438 | name_len); |
439 | } |
440 | |
441 | ext4_dir_set_csum(inode_ref: parent, dirent: (void *)b.data); |
442 | ext4_trans_set_block_dirty(buf: b.buf); |
443 | r = ext4_block_set(bdev: fs->bdev, b: &b); |
444 | |
445 | return r; |
446 | } |
447 | |
448 | int ext4_dir_find_entry(struct ext4_dir_search_result *result, |
449 | struct ext4_inode_ref *parent, const char *name, |
450 | uint32_t name_len) |
451 | { |
452 | int r; |
453 | struct ext4_sblock *sb = &parent->fs->sb; |
454 | |
455 | /* Entry clear */ |
456 | result->block.lb_id = 0; |
457 | result->dentry = NULL; |
458 | |
459 | #if CONFIG_DIR_INDEX_ENABLE |
460 | /* Index search */ |
461 | if ((ext4_sb_feature_com(s: sb, EXT4_FCOM_DIR_INDEX)) && |
462 | (ext4_inode_has_flag(inode: parent->inode, EXT4_INODE_FLAG_INDEX))) { |
463 | r = ext4_dir_dx_find_entry(result, inode_ref: parent, name_len, name); |
464 | /* Check if index is not corrupted */ |
465 | if (r != EXT4_ERR_BAD_DX_DIR) { |
466 | if (r != EOK) |
467 | return r; |
468 | |
469 | return EOK; |
470 | } |
471 | |
472 | /* Needed to clear dir index flag if corrupted */ |
473 | ext4_inode_clear_flag(inode: parent->inode, EXT4_INODE_FLAG_INDEX); |
474 | parent->dirty = true; |
475 | } |
476 | #endif |
477 | |
478 | /* Linear algorithm */ |
479 | |
480 | uint32_t iblock; |
481 | ext4_fsblk_t fblock; |
482 | uint32_t block_size = ext4_sb_get_block_size(s: sb); |
483 | uint64_t inode_size = ext4_inode_get_size(sb, inode: parent->inode); |
484 | uint32_t total_blocks = (uint32_t)(inode_size / block_size); |
485 | |
486 | /* Walk through all data blocks */ |
487 | for (iblock = 0; iblock < total_blocks; ++iblock) { |
488 | /* Load block address */ |
489 | r = ext4_fs_get_inode_dblk_idx(inode_ref: parent, iblock, fblock: &fblock, support_unwritten: false); |
490 | if (r != EOK) |
491 | return r; |
492 | |
493 | /* Load data block */ |
494 | struct ext4_block b; |
495 | r = ext4_trans_block_get(bdev: parent->fs->bdev, b: &b, lba: fblock); |
496 | if (r != EOK) |
497 | return r; |
498 | |
499 | if (!ext4_dir_csum_verify(inode_ref: parent, dirent: (void *)b.data)) { |
500 | ext4_dbg(DEBUG_DIR, |
501 | DBG_WARN "Leaf block checksum failed." |
502 | "Inode: %" PRIu32", " |
503 | "Block: %" PRIu32"\n" , |
504 | parent->index, |
505 | iblock); |
506 | } |
507 | |
508 | /* Try to find entry in block */ |
509 | struct ext4_dir_en *res_entry; |
510 | r = ext4_dir_find_in_block(block: &b, sb, name_len, name, res_entry: &res_entry); |
511 | if (r == EOK) { |
512 | result->block = b; |
513 | result->dentry = res_entry; |
514 | return EOK; |
515 | } |
516 | |
517 | /* Entry not found - put block and continue to the next block */ |
518 | |
519 | r = ext4_block_set(bdev: parent->fs->bdev, b: &b); |
520 | if (r != EOK) |
521 | return r; |
522 | } |
523 | |
524 | return ENOENT; |
525 | } |
526 | |
527 | int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name, |
528 | uint32_t name_len) |
529 | { |
530 | struct ext4_sblock *sb = &parent->fs->sb; |
531 | /* Check if removing from directory */ |
532 | if (!ext4_inode_is_type(sb, inode: parent->inode, EXT4_INODE_MODE_DIRECTORY)) |
533 | return ENOTDIR; |
534 | |
535 | /* Try to find entry */ |
536 | struct ext4_dir_search_result result; |
537 | int rc = ext4_dir_find_entry(result: &result, parent, name, name_len); |
538 | if (rc != EOK) |
539 | return rc; |
540 | |
541 | /* Invalidate entry */ |
542 | ext4_dir_en_set_inode(de: result.dentry, inode: 0); |
543 | |
544 | /* Store entry position in block */ |
545 | uint32_t pos = (uint8_t *)result.dentry - result.block.data; |
546 | |
547 | /* |
548 | * If entry is not the first in block, it must be merged |
549 | * with previous entry |
550 | */ |
551 | if (pos != 0) { |
552 | uint32_t offset = 0; |
553 | |
554 | /* Start from the first entry in block */ |
555 | struct ext4_dir_en *tmp_de =(void *)result.block.data; |
556 | uint16_t de_len = ext4_dir_en_get_entry_len(de: tmp_de); |
557 | |
558 | /* Find direct predecessor of removed entry */ |
559 | while ((offset + de_len) < pos) { |
560 | offset += ext4_dir_en_get_entry_len(de: tmp_de); |
561 | tmp_de = (void *)(result.block.data + offset); |
562 | de_len = ext4_dir_en_get_entry_len(de: tmp_de); |
563 | } |
564 | |
565 | ext4_assert(de_len + offset == pos); |
566 | |
567 | /* Add to removed entry length to predecessor's length */ |
568 | uint16_t del_len; |
569 | del_len = ext4_dir_en_get_entry_len(de: result.dentry); |
570 | ext4_dir_en_set_entry_len(de: tmp_de, l: de_len + del_len); |
571 | } |
572 | |
573 | ext4_dir_set_csum(inode_ref: parent, |
574 | dirent: (struct ext4_dir_en *)result.block.data); |
575 | ext4_trans_set_block_dirty(buf: result.block.buf); |
576 | |
577 | return ext4_dir_destroy_result(parent, result: &result); |
578 | } |
579 | |
580 | int ext4_dir_try_insert_entry(struct ext4_sblock *sb, |
581 | struct ext4_inode_ref *inode_ref, |
582 | struct ext4_block *dst_blk, |
583 | struct ext4_inode_ref *child, const char *name, |
584 | uint32_t name_len) |
585 | { |
586 | /* Compute required length entry and align it to 4 bytes */ |
587 | uint32_t block_size = ext4_sb_get_block_size(s: sb); |
588 | uint16_t required_len = sizeof(struct ext4_fake_dir_entry) + name_len; |
589 | |
590 | if ((required_len % 4) != 0) |
591 | required_len += 4 - (required_len % 4); |
592 | |
593 | /* Initialize pointers, stop means to upper bound */ |
594 | struct ext4_dir_en *start = (void *)dst_blk->data; |
595 | struct ext4_dir_en *stop = (void *)(dst_blk->data + block_size); |
596 | |
597 | /* |
598 | * Walk through the block and check for invalid entries |
599 | * or entries with free space for new entry |
600 | */ |
601 | while (start < stop) { |
602 | uint32_t inode = ext4_dir_en_get_inode(de: start); |
603 | uint16_t rec_len = ext4_dir_en_get_entry_len(de: start); |
604 | uint8_t itype = ext4_dir_en_get_inode_type(sb, de: start); |
605 | |
606 | /* If invalid and large enough entry, use it */ |
607 | if ((inode == 0) && (itype != EXT4_DIRENTRY_DIR_CSUM) && |
608 | (rec_len >= required_len)) { |
609 | ext4_dir_write_entry(sb, en: start, entry_len: rec_len, child, name, |
610 | name_len); |
611 | ext4_dir_set_csum(inode_ref, dirent: (void *)dst_blk->data); |
612 | ext4_trans_set_block_dirty(buf: dst_blk->buf); |
613 | |
614 | return EOK; |
615 | } |
616 | |
617 | /* Valid entry, try to split it */ |
618 | if (inode != 0) { |
619 | uint16_t used_len; |
620 | used_len = ext4_dir_en_get_name_len(sb, de: start); |
621 | |
622 | uint16_t sz; |
623 | sz = sizeof(struct ext4_fake_dir_entry) + used_len; |
624 | |
625 | if ((used_len % 4) != 0) |
626 | sz += 4 - (used_len % 4); |
627 | |
628 | uint16_t free_space = rec_len - sz; |
629 | |
630 | /* There is free space for new entry */ |
631 | if (free_space >= required_len) { |
632 | /* Cut tail of current entry */ |
633 | struct ext4_dir_en * new_entry; |
634 | new_entry = (void *)((uint8_t *)start + sz); |
635 | ext4_dir_en_set_entry_len(de: start, l: sz); |
636 | ext4_dir_write_entry(sb, en: new_entry, entry_len: free_space, |
637 | child, name, name_len); |
638 | |
639 | ext4_dir_set_csum(inode_ref, |
640 | dirent: (void *)dst_blk->data); |
641 | ext4_trans_set_block_dirty(buf: dst_blk->buf); |
642 | return EOK; |
643 | } |
644 | } |
645 | |
646 | /* Jump to the next entry */ |
647 | start = (void *)((uint8_t *)start + rec_len); |
648 | } |
649 | |
650 | /* No free space found for new entry */ |
651 | return ENOSPC; |
652 | } |
653 | |
654 | int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb, |
655 | size_t name_len, const char *name, |
656 | struct ext4_dir_en **res_entry) |
657 | { |
658 | /* Start from the first entry in block */ |
659 | struct ext4_dir_en *de = (struct ext4_dir_en *)block->data; |
660 | |
661 | /* Set upper bound for cycling */ |
662 | uint8_t *addr_limit = block->data + ext4_sb_get_block_size(s: sb); |
663 | |
664 | /* Walk through the block and check entries */ |
665 | while ((uint8_t *)de < addr_limit) { |
666 | /* Termination condition */ |
667 | if ((uint8_t *)de + name_len > addr_limit) |
668 | break; |
669 | |
670 | /* Valid entry - check it */ |
671 | if (ext4_dir_en_get_inode(de) != 0) { |
672 | /* For more efficient compare only lengths firstly*/ |
673 | uint16_t el = ext4_dir_en_get_name_len(sb, de); |
674 | if (el == name_len) { |
675 | /* Compare names */ |
676 | if (memcmp(a: name, b: de->name, size: name_len) == 0) { |
677 | *res_entry = de; |
678 | return EOK; |
679 | } |
680 | } |
681 | } |
682 | |
683 | uint16_t de_len = ext4_dir_en_get_entry_len(de); |
684 | |
685 | /* Corrupted entry */ |
686 | if (de_len == 0) |
687 | return EINVAL; |
688 | |
689 | /* Jump to next entry */ |
690 | de = (struct ext4_dir_en *)((uint8_t *)de + de_len); |
691 | } |
692 | |
693 | /* Entry not found */ |
694 | return ENOENT; |
695 | } |
696 | |
697 | int ext4_dir_destroy_result(struct ext4_inode_ref *parent, |
698 | struct ext4_dir_search_result *result) |
699 | { |
700 | if (result->block.lb_id) |
701 | return ext4_block_set(bdev: parent->fs->bdev, b: &result->block); |
702 | |
703 | return EOK; |
704 | } |
705 | |
706 | /** |
707 | * @} |
708 | */ |
709 | |