Linux line-endings
[lwext4.git] / lwext4 / ext4_dir.c
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_dir.h"
44 #include "ext4_dir_idx.h"
45 #include "ext4_inode.h"
46 #include "ext4_fs.h"
47
48 #include <string.h>
49
50 /****************************************************************************/
51
52 /**@brief Do some checks before returning iterator.
53  * @param it Iterator to be checked
54  * @param block_size Size of data block
55  * @return Error code
56  */
57 static int ext4_dir_iterator_set(struct ext4_directory_iterator *it,
58                                  uint32_t block_size)
59 {
60         it->current = NULL;
61
62         uint32_t offset_in_block = it->current_offset % block_size;
63
64         /* Ensure proper alignment */
65         if ((offset_in_block % 4) != 0)
66                 return EIO;
67
68         /* Ensure that the core of the entry does not overflow the block */
69         if (offset_in_block > block_size - 8)
70                 return EIO;
71
72         struct ext4_directory_entry_ll *entry =
73             (void *)(it->current_block.data + offset_in_block);
74
75         /* Ensure that the whole entry does not overflow the block */
76         uint16_t length = ext4_dir_entry_ll_get_entry_length(entry);
77         if (offset_in_block + length > block_size)
78                 return EIO;
79
80         /* Ensure the name length is not too large */
81         if (ext4_dir_entry_ll_get_name_length(&it->inode_ref->fs->sb, entry) >
82             length - 8)
83                 return EIO;
84
85         /* Everything OK - "publish" the entry */
86         it->current = entry;
87         return EOK;
88 }
89
90 /**@brief Seek to next valid directory entry.
91  *        Here can be jumped to the next data block.
92  * @param it  Initialized iterator
93  * @param pos Position of the next entry
94  * @return Error code
95  */
96 static int ext4_dir_iterator_seek(struct ext4_directory_iterator *it,
97                                   uint64_t pos)
98 {
99         uint64_t size =
100             ext4_inode_get_size(&it->inode_ref->fs->sb, it->inode_ref->inode);
101
102         /* The iterator is not valid until we seek to the desired position */
103         it->current = NULL;
104
105         /* Are we at the end? */
106         if (pos >= size) {
107                 if (it->current_block.lb_id) {
108
109                         int rc = ext4_block_set(it->inode_ref->fs->bdev,
110                                                 &it->current_block);
111                         it->current_block.lb_id = 0;
112
113                         if (rc != EOK)
114                                 return rc;
115                 }
116
117                 it->current_offset = pos;
118                 return EOK;
119         }
120
121         /* Compute next block address */
122         uint32_t block_size = ext4_sb_get_block_size(&it->inode_ref->fs->sb);
123         uint64_t current_block_idx = it->current_offset / block_size;
124         uint64_t next_block_idx = pos / block_size;
125
126         /*
127          * If we don't have a block or are moving across block boundary,
128          * we need to get another block
129          */
130         if ((it->current_block.lb_id == 0) ||
131             (current_block_idx != next_block_idx)) {
132                 if (it->current_block.lb_id) {
133                         int rc = ext4_block_set(it->inode_ref->fs->bdev,
134                                                 &it->current_block);
135                         it->current_block.lb_id = 0;
136
137                         if (rc != EOK)
138                                 return rc;
139                 }
140
141                 uint32_t next_block_phys_idx;
142                 int rc = ext4_fs_get_inode_data_block_index(
143                     it->inode_ref, next_block_idx, &next_block_phys_idx);
144                 if (rc != EOK)
145                         return rc;
146
147                 rc = ext4_block_get(it->inode_ref->fs->bdev, &it->current_block,
148                                     next_block_phys_idx);
149                 if (rc != EOK) {
150                         it->current_block.lb_id = 0;
151                         return rc;
152                 }
153         }
154
155         it->current_offset = pos;
156
157         return ext4_dir_iterator_set(it, block_size);
158 }
159
160 int ext4_dir_iterator_init(struct ext4_directory_iterator *it,
161                            struct ext4_inode_ref *inode_ref, uint64_t pos)
162 {
163         it->inode_ref = inode_ref;
164         it->current = 0;
165         it->current_offset = 0;
166         it->current_block.lb_id = 0;
167
168         return ext4_dir_iterator_seek(it, pos);
169 }
170
171 int ext4_dir_iterator_next(struct ext4_directory_iterator *it)
172 {
173         int r = EOK;
174         uint16_t skip;
175
176         while (r == EOK) {
177                 skip = ext4_dir_entry_ll_get_entry_length(it->current);
178                 r = ext4_dir_iterator_seek(it, it->current_offset + skip);
179
180                 if (!it->current)
181                         break;
182                 /*Skip NULL referenced entry*/
183                 if (ext4_dir_entry_ll_get_inode(it->current) != 0)
184                         break;
185         }
186
187         return r;
188 }
189
190 int ext4_dir_iterator_fini(struct ext4_directory_iterator *it)
191 {
192         it->current = 0;
193
194         if (it->current_block.lb_id)
195                 return ext4_block_set(it->inode_ref->fs->bdev,
196                                       &it->current_block);
197
198         return EOK;
199 }
200
201 void ext4_dir_write_entry(struct ext4_sblock *sb,
202                           struct ext4_directory_entry_ll *entry,
203                           uint16_t entry_len, struct ext4_inode_ref *child,
204                           const char *name, size_t name_len)
205 {
206         /* Check maximum entry length */
207         ext4_assert(entry_len <= ext4_sb_get_block_size(sb));
208
209         /* Set basic attributes */
210         ext4_dir_entry_ll_set_inode(entry, child->index);
211         ext4_dir_entry_ll_set_entry_length(entry, entry_len);
212         ext4_dir_entry_ll_set_name_length(sb, entry, name_len);
213
214         /* Write name */
215         memcpy(entry->name, name, name_len);
216
217         /* Set type of entry */
218         if (ext4_inode_is_type(sb, child->inode, EXT4_INODE_MODE_DIRECTORY))
219                 ext4_dir_entry_ll_set_inode_type(sb, entry,
220                                                  EXT4_DIRECTORY_FILETYPE_DIR);
221         else
222                 ext4_dir_entry_ll_set_inode_type(
223                     sb, entry, EXT4_DIRECTORY_FILETYPE_REG_FILE);
224 }
225
226 int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name,
227                        uint32_t name_len, struct ext4_inode_ref *child)
228 {
229         struct ext4_fs *fs = parent->fs;
230
231 #if CONFIG_DIR_INDEX_ENABLE
232         /* Index adding (if allowed) */
233         if ((ext4_sb_has_feature_compatible(&fs->sb,
234                                             EXT4_FEATURE_COMPAT_DIR_INDEX)) &&
235             (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) {
236                 int rc = ext4_dir_dx_add_entry(parent, child, name);
237
238                 /* Check if index is not corrupted */
239                 if (rc != EXT4_ERR_BAD_DX_DIR) {
240                         if (rc != EOK)
241                                 return rc;
242
243                         return EOK;
244                 }
245
246                 /* Needed to clear dir index flag if corrupted */
247                 ext4_inode_clear_flag(parent->inode, EXT4_INODE_FLAG_INDEX);
248                 parent->dirty = true;
249         }
250 #endif
251
252         /* Linear algorithm */
253         uint32_t iblock = 0;
254         uint32_t fblock = 0;
255         uint32_t block_size = ext4_sb_get_block_size(&fs->sb);
256         uint32_t inode_size = ext4_inode_get_size(&fs->sb, parent->inode);
257         uint32_t total_blocks = inode_size / block_size;
258
259         /* Find block, where is space for new entry and try to add */
260         bool success = false;
261         for (iblock = 0; iblock < total_blocks; ++iblock) {
262                 int rc =
263                     ext4_fs_get_inode_data_block_index(parent, iblock, &fblock);
264                 if (rc != EOK)
265                         return rc;
266
267                 struct ext4_block block;
268                 rc = ext4_block_get(fs->bdev, &block, fblock);
269                 if (rc != EOK)
270                         return rc;
271
272                 /* If adding is successful, function can finish */
273                 rc = ext4_dir_try_insert_entry(&fs->sb, &block, child, name,
274                                                name_len);
275                 if (rc == EOK)
276                         success = true;
277
278                 rc = ext4_block_set(fs->bdev, &block);
279                 if (rc != EOK)
280                         return rc;
281
282                 if (success)
283                         return EOK;
284         }
285
286         /* No free block found - needed to allocate next data block */
287
288         iblock = 0;
289         fblock = 0;
290         int rc = ext4_fs_append_inode_block(parent, &fblock, &iblock);
291         if (rc != EOK)
292                 return rc;
293
294         /* Load new block */
295         struct ext4_block new_block;
296
297         rc = ext4_block_get(fs->bdev, &new_block, fblock);
298         if (rc != EOK)
299                 return rc;
300
301         /* Fill block with zeroes */
302         memset(new_block.data, 0, block_size);
303         struct ext4_directory_entry_ll *block_entry = (void *)new_block.data;
304         ext4_dir_write_entry(&fs->sb, block_entry, block_size, child, name,
305                              name_len);
306
307         /* Save new block */
308         new_block.dirty = true;
309         rc = ext4_block_set(fs->bdev, &new_block);
310
311         return rc;
312 }
313
314 int ext4_dir_find_entry(struct ext4_directory_search_result *result,
315                         struct ext4_inode_ref *parent, const char *name,
316                         uint32_t name_len)
317 {
318         struct ext4_sblock *sb = &parent->fs->sb;
319
320 #if CONFIG_DIR_INDEX_ENABLE
321         /* Index search */
322         if ((ext4_sb_has_feature_compatible(sb,
323                                             EXT4_FEATURE_COMPAT_DIR_INDEX)) &&
324             (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) {
325                 int rc = ext4_dir_dx_find_entry(result, parent, name_len, name);
326
327                 /* Check if index is not corrupted */
328                 if (rc != EXT4_ERR_BAD_DX_DIR) {
329                         if (rc != EOK)
330                                 return rc;
331
332                         return EOK;
333                 }
334
335                 /* Needed to clear dir index flag if corrupted */
336                 ext4_inode_clear_flag(parent->inode, EXT4_INODE_FLAG_INDEX);
337                 parent->dirty = true;
338         }
339 #endif
340
341         /* Linear algorithm */
342
343         uint32_t iblock;
344         uint32_t fblock;
345         uint32_t block_size = ext4_sb_get_block_size(sb);
346         uint32_t inode_size = ext4_inode_get_size(sb, parent->inode);
347         uint32_t total_blocks = inode_size / block_size;
348
349         /* Walk through all data blocks */
350         for (iblock = 0; iblock < total_blocks; ++iblock) {
351                 /* Load block address */
352                 int rc =
353                     ext4_fs_get_inode_data_block_index(parent, iblock, &fblock);
354                 if (rc != EOK)
355                         return rc;
356
357                 /* Load data block */
358                 struct ext4_block block;
359                 rc = ext4_block_get(parent->fs->bdev, &block, fblock);
360                 if (rc != EOK)
361                         return rc;
362
363                 /* Try to find entry in block */
364                 struct ext4_directory_entry_ll *res_entry;
365                 rc = ext4_dir_find_in_block(&block, sb, name_len, name,
366                                             &res_entry);
367                 if (rc == EOK) {
368                         result->block = block;
369                         result->dentry = res_entry;
370                         return EOK;
371                 }
372
373                 /* Entry not found - put block and continue to the next block */
374
375                 rc = ext4_block_set(parent->fs->bdev, &block);
376                 if (rc != EOK)
377                         return rc;
378         }
379
380         /* Entry was not found */
381
382         result->block.lb_id = 0;
383         result->dentry = NULL;
384
385         return ENOENT;
386 }
387
388 int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name,
389                           uint32_t name_len)
390 {
391         /* Check if removing from directory */
392         if (!ext4_inode_is_type(&parent->fs->sb, parent->inode,
393                                 EXT4_INODE_MODE_DIRECTORY))
394                 return ENOTDIR;
395
396         /* Try to find entry */
397         struct ext4_directory_search_result result;
398         int rc = ext4_dir_find_entry(&result, parent, name, name_len);
399         if (rc != EOK)
400                 return rc;
401
402         /* Invalidate entry */
403         ext4_dir_entry_ll_set_inode(result.dentry, 0);
404
405         /* Store entry position in block */
406         uint32_t pos = (uint8_t *)result.dentry - result.block.data;
407
408         /*
409          * If entry is not the first in block, it must be merged
410          * with previous entry
411          */
412         if (pos != 0) {
413                 uint32_t offset = 0;
414
415                 /* Start from the first entry in block */
416                 struct ext4_directory_entry_ll *tmp_dentry =
417                     (void *)result.block.data;
418                 uint16_t tmp_dentry_length =
419                     ext4_dir_entry_ll_get_entry_length(tmp_dentry);
420
421                 /* Find direct predecessor of removed entry */
422                 while ((offset + tmp_dentry_length) < pos) {
423                         offset +=
424                             ext4_dir_entry_ll_get_entry_length(tmp_dentry);
425                         tmp_dentry = (void *)(result.block.data + offset);
426                         tmp_dentry_length =
427                             ext4_dir_entry_ll_get_entry_length(tmp_dentry);
428                 }
429
430                 ext4_assert(tmp_dentry_length + offset == pos);
431
432                 /* Add to removed entry length to predecessor's length */
433                 uint16_t del_entry_length =
434                     ext4_dir_entry_ll_get_entry_length(result.dentry);
435                 ext4_dir_entry_ll_set_entry_length(
436                     tmp_dentry, tmp_dentry_length + del_entry_length);
437         }
438
439         result.block.dirty = true;
440
441         return ext4_dir_destroy_result(parent, &result);
442 }
443
444 int ext4_dir_try_insert_entry(struct ext4_sblock *sb,
445                               struct ext4_block *target_block,
446                               struct ext4_inode_ref *child, const char *name,
447                               uint32_t name_len)
448 {
449         /* Compute required length entry and align it to 4 bytes */
450         uint32_t block_size = ext4_sb_get_block_size(sb);
451         uint16_t required_len =
452             sizeof(struct ext4_fake_directory_entry) + name_len;
453
454         if ((required_len % 4) != 0)
455                 required_len += 4 - (required_len % 4);
456
457         /* Initialize pointers, stop means to upper bound */
458         struct ext4_directory_entry_ll *dentry = (void *)target_block->data;
459         struct ext4_directory_entry_ll *stop =
460             (void *)(target_block->data + block_size);
461
462         /*
463          * Walk through the block and check for invalid entries
464          * or entries with free space for new entry
465          */
466         while (dentry < stop) {
467                 uint32_t inode = ext4_dir_entry_ll_get_inode(dentry);
468                 uint16_t rec_len = ext4_dir_entry_ll_get_entry_length(dentry);
469
470                 /* If invalid and large enough entry, use it */
471                 if ((inode == 0) && (rec_len >= required_len)) {
472                         ext4_dir_write_entry(sb, dentry, rec_len, child, name,
473                                              name_len);
474                         target_block->dirty = true;
475
476                         return EOK;
477                 }
478
479                 /* Valid entry, try to split it */
480                 if (inode != 0) {
481                         uint16_t used_name_len =
482                             ext4_dir_entry_ll_get_name_length(sb, dentry);
483
484                         uint16_t used_space =
485                             sizeof(struct ext4_fake_directory_entry) +
486                             used_name_len;
487
488                         if ((used_name_len % 4) != 0)
489                                 used_space += 4 - (used_name_len % 4);
490
491                         uint16_t free_space = rec_len - used_space;
492
493                         /* There is free space for new entry */
494                         if (free_space >= required_len) {
495                                 /* Cut tail of current entry */
496                                 ext4_dir_entry_ll_set_entry_length(dentry,
497                                                                    used_space);
498                                 struct ext4_directory_entry_ll *new_entry =
499                                     (void *)((uint8_t *)dentry + used_space);
500                                 ext4_dir_write_entry(sb, new_entry, free_space,
501                                                      child, name, name_len);
502
503                                 target_block->dirty = true;
504                                 return EOK;
505                         }
506                 }
507
508                 /* Jump to the next entry */
509                 dentry = (void *)((uint8_t *)dentry + rec_len);
510         }
511
512         /* No free space found for new entry */
513         return ENOSPC;
514 }
515
516 int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb,
517                            size_t name_len, const char *name,
518                            struct ext4_directory_entry_ll **res_entry)
519 {
520         /* Start from the first entry in block */
521         struct ext4_directory_entry_ll *dentry =
522             (struct ext4_directory_entry_ll *)block->data;
523
524         /* Set upper bound for cycling */
525         uint8_t *addr_limit = block->data + ext4_sb_get_block_size(sb);
526
527         /* Walk through the block and check entries */
528         while ((uint8_t *)dentry < addr_limit) {
529                 /* Termination condition */
530                 if ((uint8_t *)dentry + name_len > addr_limit)
531                         break;
532
533                 /* Valid entry - check it */
534                 if (ext4_dir_entry_ll_get_inode(dentry) != 0) {
535                         /* For more efficient compare only lengths firstly*/
536                         if (ext4_dir_entry_ll_get_name_length(sb, dentry) ==
537                             name_len) {
538                                 /* Compare names */
539                                 if (memcmp((uint8_t *)name, dentry->name,
540                                            name_len) == 0) {
541                                         *res_entry = dentry;
542                                         return EOK;
543                                 }
544                         }
545                 }
546
547                 uint16_t dentry_len =
548                     ext4_dir_entry_ll_get_entry_length(dentry);
549
550                 /* Corrupted entry */
551                 if (dentry_len == 0)
552                         return EINVAL;
553
554                 /* Jump to next entry */
555                 dentry = (struct ext4_directory_entry_ll *)((uint8_t *)dentry +
556                                                             dentry_len);
557         }
558
559         /* Entry not found */
560         return ENOENT;
561 }
562
563 int ext4_dir_destroy_result(struct ext4_inode_ref *parent,
564                             struct ext4_directory_search_result *result)
565 {
566         if (result->block.lb_id)
567                 return ext4_block_set(parent->fs->bdev, &result->block);
568
569         return EOK;
570 }
571
572 /**
573  * @}
574  */