source: mainline/uspace/lib/ext4/libext4_filesystem.c@ d510ac01

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since d510ac01 was d510ac01, checked in by Frantisek Princ <frantisek.princ@…>, 13 years ago

Fixed bug with different sizes in writing file on extents

  • Property mode set to 100644
File size: 32.5 KB
Line 
1/*
2 * Copyright (c) 2012 Frantisek Princ
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libext4
30 * @{
31 */
32
33/**
34 * @file libext4_filesystem.c
35 * @brief More complex filesystem operations.
36 */
37
38#include <byteorder.h>
39#include <errno.h>
40#include <malloc.h>
41#include "libext4.h"
42
43/** Initialize filesystem and read all needed data.
44 *
45 * @param fs filesystem instance to be initialized
46 * @param service_id identifier if device with the filesystem
47 * @return error code
48 */
49int ext4_filesystem_init(ext4_filesystem_t *fs, service_id_t service_id)
50{
51 int rc;
52
53 fs->device = service_id;
54
55 /* Initialize block library (4096 is size of communication channel) */
56 rc = block_init(EXCHANGE_SERIALIZE, fs->device, 4096);
57 if (rc != EOK) {
58 EXT4FS_DBG("block init error: \%d", rc);
59 return rc;
60 }
61
62 /* Read superblock from device to memory */
63 ext4_superblock_t *temp_superblock;
64 rc = ext4_superblock_read_direct(fs->device, &temp_superblock);
65 if (rc != EOK) {
66 block_fini(fs->device);
67 EXT4FS_DBG("superblock read error: \%d", rc);
68 return rc;
69 }
70
71 /* Read block size from superblock and check */
72 uint32_t block_size = ext4_superblock_get_block_size(temp_superblock);
73 if (block_size > EXT4_MAX_BLOCK_SIZE) {
74 block_fini(fs->device);
75 EXT4FS_DBG("get blocksize error: \%d", rc);
76 return ENOTSUP;
77 }
78
79 /* Initialize block caching by libblock */
80 rc = block_cache_init(service_id, block_size, 0, CACHE_MODE_WT);
81 if (rc != EOK) {
82 block_fini(fs->device);
83 EXT4FS_DBG("block cache init error: \%d", rc);
84 return rc;
85 }
86
87 /* Compute limits for indirect block levels */
88 uint32_t block_ids_per_block = block_size / sizeof(uint32_t);
89 fs->inode_block_limits[0] = EXT4_INODE_DIRECT_BLOCK_COUNT;
90 fs->inode_blocks_per_level[0] = 1;
91 for (int i = 1; i < 4; i++) {
92 fs->inode_blocks_per_level[i] = fs->inode_blocks_per_level[i-1] *
93 block_ids_per_block;
94 fs->inode_block_limits[i] = fs->inode_block_limits[i-1] +
95 fs->inode_blocks_per_level[i];
96 }
97
98 /* Return loaded superblock */
99 fs->superblock = temp_superblock;
100
101 uint16_t state = ext4_superblock_get_state(fs->superblock);
102
103 if (state != EXT4_SUPERBLOCK_STATE_VALID_FS) {
104 block_cache_fini(fs->device);
105 block_fini(fs->device);
106 EXT4FS_DBG("invalid state error");
107 return ENOTSUP;
108 }
109
110 /* Mark system as mounted */
111 ext4_superblock_set_state(fs->superblock, EXT4_SUPERBLOCK_STATE_ERROR_FS);
112 rc = ext4_superblock_write_direct(fs->device, fs->superblock);
113 if (rc != EOK) {
114 block_cache_fini(fs->device);
115 block_fini(fs->device);
116 EXT4FS_DBG("state write error: \%d", rc);
117 return rc;
118 }
119
120 return EOK;
121}
122
123/** Destroy filesystem instance (used by unmount operation).
124 *
125 * @param fs filesystem to be destroyed
126 * @param write_sb flag if superblock should be written to device
127 * @return error code
128 */
129int ext4_filesystem_fini(ext4_filesystem_t *fs)
130{
131 int rc = EOK;
132
133 /* Write the superblock to the device */
134 ext4_superblock_set_state(fs->superblock, EXT4_SUPERBLOCK_STATE_VALID_FS);
135 rc = ext4_superblock_write_direct(fs->device, fs->superblock);
136
137 /* Release memory space for superblock */
138 free(fs->superblock);
139
140 /* Finish work with block library */
141 block_fini(fs->device);
142
143 return rc;
144}
145
146/** Check sanity of the filesystem.
147 *
148 * Main is the check of the superblock structure.
149 *
150 * @param fs filesystem to be checked
151 * @return error code
152 */
153int ext4_filesystem_check_sanity(ext4_filesystem_t *fs)
154{
155 int rc;
156
157 /* Check superblock */
158 rc = ext4_superblock_check_sanity(fs->superblock);
159 if (rc != EOK) {
160 return rc;
161 }
162
163 return EOK;
164}
165
166/** Check filesystem's features, if supported by this driver
167 *
168 * Function can return EOK and set read_only flag. It mean's that
169 * there are some not-supported features, that can cause problems
170 * during some write operations.
171 *
172 * @param fs filesystem to be checked
173 * @param read_only flag if filesystem should be mounted only for reading
174 * @return error code
175 */
176int ext4_filesystem_check_features(ext4_filesystem_t *fs, bool *read_only)
177{
178 /* Feature flags are present only in higher revisions */
179 if (ext4_superblock_get_rev_level(fs->superblock) == 0) {
180 *read_only = false;
181 return EOK;
182 }
183
184 /* Check incompatible features - if filesystem has some,
185 * volume can't be mounted
186 */
187 uint32_t incompatible_features;
188 incompatible_features = ext4_superblock_get_features_incompatible(fs->superblock);
189 incompatible_features &= ~EXT4_FEATURE_INCOMPAT_SUPP;
190 if (incompatible_features > 0) {
191 return ENOTSUP;
192 }
193
194 /* Check read-only features, if filesystem has some,
195 * volume can be mount only in read-only mode
196 */
197 uint32_t compatible_read_only;
198 compatible_read_only = ext4_superblock_get_features_read_only(fs->superblock);
199 compatible_read_only &= ~EXT4_FEATURE_RO_COMPAT_SUPP;
200 if (compatible_read_only > 0) {
201 *read_only = true;
202 return EOK;
203 }
204
205 return EOK;
206}
207
208/** Get reference to block group specified by index.
209 *
210 * @param fs filesystem to find block group on
211 * @param bgid index of block group to load
212 * @param ref output pointer for reference
213 * @return error code
214 */
215int ext4_filesystem_get_block_group_ref(ext4_filesystem_t *fs, uint32_t bgid,
216 ext4_block_group_ref_t **ref)
217{
218 int rc;
219
220 /* Allocate memory for new structure */
221 ext4_block_group_ref_t *newref = malloc(sizeof(ext4_block_group_ref_t));
222 if (newref == NULL) {
223 return ENOMEM;
224 }
225
226 /* Compute number of descriptors, that fits in one data block */
227 uint32_t descriptors_per_block = ext4_superblock_get_block_size(fs->superblock)
228 / ext4_superblock_get_desc_size(fs->superblock);
229
230 /* Block group descriptor table starts at the next block after superblock */
231 aoff64_t block_id = ext4_superblock_get_first_data_block(fs->superblock) + 1;
232
233 /* Find the block containing the descriptor we are looking for */
234 block_id += bgid / descriptors_per_block;
235 uint32_t offset = (bgid % descriptors_per_block) * ext4_superblock_get_desc_size(fs->superblock);
236
237 /* Load block with descriptors */
238 rc = block_get(&newref->block, fs->device, block_id, 0);
239 if (rc != EOK) {
240 free(newref);
241 return rc;
242 }
243
244 /* Inititialize in-memory representation */
245 newref->block_group = newref->block->data + offset;
246 newref->fs = fs;
247 newref->index = bgid;
248 newref->dirty = false;
249
250 *ref = newref;
251
252 return EOK;
253}
254
255/** Compute checksum of block group descriptor.
256 *
257 * It uses crc functions from Linux kernel implementation.
258 *
259 * @param sb superblock
260 * @param bgid index of block group in the filesystem
261 * @param bg block group to compute checksum for
262 * @return checksum value
263 */
264static uint16_t ext4_filesystem_bg_checksum(ext4_superblock_t *sb, uint32_t bgid,
265 ext4_block_group_t *bg)
266{
267 /* If checksum not supported, 0 will be returned */
268 uint16_t crc = 0;
269
270 /* Compute the checksum only if the filesystem supports it */
271 if (ext4_superblock_has_feature_read_only(sb, EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) {
272
273 void *base = bg;
274 void *checksum = &bg->checksum;
275
276 uint32_t offset = (uint32_t)(checksum - base);
277
278 /* Convert block group index to little endian */
279 uint32_t le_group = host2uint32_t_le(bgid);
280
281 /* Initialization */
282 crc = crc16(~0, sb->uuid, sizeof(sb->uuid));
283
284 /* Include index of block group */
285 crc = crc16(crc, (uint8_t *)&le_group, sizeof(le_group));
286
287 /* Compute crc from the first part (stop before checksum field) */
288 crc = crc16(crc, (uint8_t *)bg, offset);
289
290 /* Skip checksum */
291 offset += sizeof(bg->checksum);
292
293 /* Checksum of the rest of block group descriptor */
294 if ((ext4_superblock_has_feature_incompatible(sb, EXT4_FEATURE_INCOMPAT_64BIT)) &&
295 offset < ext4_superblock_get_desc_size(sb)) {
296
297 crc = crc16(crc, ((uint8_t *)bg) + offset, ext4_superblock_get_desc_size(sb) - offset);
298 }
299 }
300
301 return crc;
302
303}
304
305/** Put reference to block group.
306 *
307 * @oaram ref pointer for reference to be put back
308 * @return error code
309 */
310int ext4_filesystem_put_block_group_ref(ext4_block_group_ref_t *ref)
311{
312 int rc;
313
314 /* Check if reference modified */
315 if (ref->dirty) {
316
317 /* Compute new checksum of block group */
318 uint16_t checksum = ext4_filesystem_bg_checksum(
319 ref->fs->superblock, ref->index, ref->block_group);
320 ext4_block_group_set_checksum(ref->block_group, checksum);
321
322 /* Mark block dirty for writing changes to physical device */
323 ref->block->dirty = true;
324 }
325
326 /* Put back block, that contains block group descriptor */
327 rc = block_put(ref->block);
328 free(ref);
329
330 return rc;
331}
332
333/** Get reference to i-node specified by index.
334 *
335 * @param fs filesystem to find i-node on
336 * @param index index of i-node to load
337 * @oaram ref output pointer for reference
338 * @return error code
339 */
340int ext4_filesystem_get_inode_ref(ext4_filesystem_t *fs, uint32_t index,
341 ext4_inode_ref_t **ref)
342{
343 int rc;
344
345 /* Allocate memory for new structure */
346 ext4_inode_ref_t *newref = malloc(sizeof(ext4_inode_ref_t));
347 if (newref == NULL) {
348 return ENOMEM;
349 }
350
351 /* Compute number of i-nodes, that fits in one data block */
352 uint32_t inodes_per_group =
353 ext4_superblock_get_inodes_per_group(fs->superblock);
354
355 /* Inode numbers are 1-based, but it is simpler to work with 0-based
356 * when computing indices
357 */
358 index -= 1;
359 uint32_t block_group = index / inodes_per_group;
360 uint32_t offset_in_group = index % inodes_per_group;
361
362 /* Load block group, where i-node is located */
363 ext4_block_group_ref_t *bg_ref;
364 rc = ext4_filesystem_get_block_group_ref(fs, block_group, &bg_ref);
365 if (rc != EOK) {
366 free(newref);
367 return rc;
368 }
369
370 /* Load block address, where i-node table is located */
371 uint32_t inode_table_start = ext4_block_group_get_inode_table_first_block(
372 bg_ref->block_group, fs->superblock);
373
374 /* Put back block group reference (not needed more) */
375 rc = ext4_filesystem_put_block_group_ref(bg_ref);
376 if (rc != EOK) {
377 free(newref);
378 return rc;
379 }
380
381 /* Compute position of i-node in the block group */
382 uint16_t inode_size = ext4_superblock_get_inode_size(fs->superblock);
383 uint32_t block_size = ext4_superblock_get_block_size(fs->superblock);
384 uint32_t byte_offset_in_group = offset_in_group * inode_size;
385
386 /* Compute block address */
387 aoff64_t block_id = inode_table_start + (byte_offset_in_group / block_size);
388 rc = block_get(&newref->block, fs->device, block_id, 0);
389 if (rc != EOK) {
390 free(newref);
391 return rc;
392 }
393
394 /* Compute position of i-node in the data block */
395 uint32_t offset_in_block = byte_offset_in_group % block_size;
396 newref->inode = newref->block->data + offset_in_block;
397
398 /* We need to store the original value of index in the reference */
399 newref->index = index + 1;
400 newref->fs = fs;
401 newref->dirty = false;
402
403 *ref = newref;
404
405 return EOK;
406}
407
408/** Put reference to i-node.
409 *
410 * @param ref pointer for reference to be put back
411 * @return error code
412 */
413int ext4_filesystem_put_inode_ref(ext4_inode_ref_t *ref)
414{
415 int rc;
416
417 /* Check if reference modified */
418 if (ref->dirty) {
419
420 /* Mark block dirty for writing changes to physical device */
421 ref->block->dirty = true;
422 }
423
424 /* Put back block, that contains i-node */
425 rc = block_put(ref->block);
426 free(ref);
427
428 return rc;
429}
430
431/** Allocate new i-node in the filesystem.
432 *
433 * @param fs filesystem to allocated i-node on
434 * @param inode_ref output pointer to return reference to allocated i-node
435 * @param flags flags to be set for newly created i-node
436 * @return error code
437 */
438int ext4_filesystem_alloc_inode(ext4_filesystem_t *fs,
439 ext4_inode_ref_t **inode_ref, int flags)
440{
441 int rc;
442
443 /* Check if newly allocated i-node will be a directory */
444 bool is_dir = false;
445 if (flags & L_DIRECTORY) {
446 is_dir = true;
447 }
448
449 /* Allocate inode by allocation algorithm */
450 uint32_t index;
451 rc = ext4_ialloc_alloc_inode(fs, &index, is_dir);
452 if (rc != EOK) {
453 return rc;
454 }
455
456 /* Load i-node from on-disk i-node table */
457 rc = ext4_filesystem_get_inode_ref(fs, index, inode_ref);
458 if (rc != EOK) {
459 ext4_ialloc_free_inode(fs, index, is_dir);
460 return rc;
461 }
462
463 /* Initialize i-node */
464 ext4_inode_t *inode = (*inode_ref)->inode;
465
466 if (is_dir) {
467 ext4_inode_set_mode(fs->superblock, inode, EXT4_INODE_MODE_DIRECTORY);
468 ext4_inode_set_links_count(inode, 1); /* '.' entry */
469 } else {
470 ext4_inode_set_mode(fs->superblock, inode, EXT4_INODE_MODE_FILE);
471 ext4_inode_set_links_count(inode, 0);
472 }
473
474 ext4_inode_set_uid(inode, 0);
475 ext4_inode_set_gid(inode, 0);
476 ext4_inode_set_size(inode, 0);
477 ext4_inode_set_access_time(inode, 0);
478 ext4_inode_set_change_inode_time(inode, 0);
479 ext4_inode_set_modification_time(inode, 0);
480 ext4_inode_set_deletion_time(inode, 0);
481 ext4_inode_set_blocks_count(fs->superblock, inode, 0);
482 ext4_inode_set_flags(inode, 0);
483 ext4_inode_set_generation(inode, 0);
484
485 /* Reset blocks array */
486 for (uint32_t i = 0; i < EXT4_INODE_BLOCKS; i++) {
487 inode->blocks[i] = 0;
488 }
489
490 /* Initialize extents if needed */
491 if (ext4_superblock_has_feature_incompatible(
492 fs->superblock, EXT4_FEATURE_INCOMPAT_EXTENTS)) {
493
494 ext4_inode_set_flag(inode, EXT4_INODE_FLAG_EXTENTS);
495
496 /* Initialize extent root header */
497 ext4_extent_header_t *header = ext4_inode_get_extent_header(inode);
498 ext4_extent_header_set_depth(header, 0);
499 ext4_extent_header_set_entries_count(header, 0);
500 ext4_extent_header_set_generation(header, 0);
501 ext4_extent_header_set_magic(header, EXT4_EXTENT_MAGIC);
502
503 uint16_t max_entries = (EXT4_INODE_BLOCKS * sizeof (uint32_t) - sizeof(ext4_extent_header_t))
504 / sizeof(ext4_extent_t);
505
506 ext4_extent_header_set_max_entries_count(header, max_entries);
507 }
508
509 (*inode_ref)->dirty = true;
510
511 return EOK;
512}
513
514/** Release i-node and mark it as free.
515 *
516 * @param inode_ref i-node to be released
517 * @return error code
518 */
519int ext4_filesystem_free_inode(ext4_inode_ref_t *inode_ref)
520{
521 int rc;
522
523 ext4_filesystem_t *fs = inode_ref->fs;
524
525 /* For extents must be data block destroyed by other way */
526 if (ext4_superblock_has_feature_incompatible(
527 fs->superblock, EXT4_FEATURE_INCOMPAT_EXTENTS) &&
528 ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)) {
529
530 /* Data structures are released during truncate operation... */
531 goto finish;
532 }
533
534 /* Release all indirect (no data) blocks */
535
536 /* 1) Single indirect */
537 uint32_t fblock = ext4_inode_get_indirect_block(inode_ref->inode, 0);
538 if (fblock != 0) {
539 rc = ext4_balloc_free_block(inode_ref, fblock);
540 if (rc != EOK) {
541 return rc;
542 }
543
544 ext4_inode_set_indirect_block(inode_ref->inode, 0, 0);
545 }
546
547 block_t *block;
548 uint32_t block_size = ext4_superblock_get_block_size(fs->superblock);
549 uint32_t count = block_size / sizeof(uint32_t);
550
551 /* 2) Double indirect */
552 fblock = ext4_inode_get_indirect_block(inode_ref->inode, 1);
553 if (fblock != 0) {
554 rc = block_get(&block, fs->device, fblock, BLOCK_FLAGS_NONE);
555 if (rc != EOK) {
556 return rc;
557 }
558
559 uint32_t ind_block;
560 for (uint32_t offset = 0; offset < count; ++offset) {
561 ind_block = uint32_t_le2host(((uint32_t*)block->data)[offset]);
562
563 if (ind_block != 0) {
564 rc = ext4_balloc_free_block(inode_ref, ind_block);
565 if (rc != EOK) {
566 block_put(block);
567 return rc;
568 }
569 }
570 }
571
572 block_put(block);
573 rc = ext4_balloc_free_block(inode_ref, fblock);
574 if (rc != EOK) {
575 return rc;
576 }
577
578 ext4_inode_set_indirect_block(inode_ref->inode, 1, 0);
579 }
580
581
582 /* 3) Tripple indirect */
583 block_t *subblock;
584 fblock = ext4_inode_get_indirect_block(inode_ref->inode, 2);
585 if (fblock != 0) {
586 rc = block_get(&block, fs->device, fblock, BLOCK_FLAGS_NONE);
587 if (rc != EOK) {
588 return rc;
589 }
590
591 uint32_t ind_block;
592 for (uint32_t offset = 0; offset < count; ++offset) {
593 ind_block = uint32_t_le2host(((uint32_t*)block->data)[offset]);
594
595 if (ind_block != 0) {
596 rc = block_get(&subblock, fs->device, ind_block, BLOCK_FLAGS_NONE);
597 if (rc != EOK) {
598 block_put(block);
599 return rc;
600 }
601
602 uint32_t ind_subblock;
603 for (uint32_t suboffset = 0; suboffset < count; ++suboffset) {
604 ind_subblock = uint32_t_le2host(((uint32_t*)subblock->data)[suboffset]);
605
606 if (ind_subblock != 0) {
607 rc = ext4_balloc_free_block(inode_ref, ind_subblock);
608 if (rc != EOK) {
609 block_put(subblock);
610 block_put(block);
611 return rc;
612 }
613 }
614
615 }
616 block_put(subblock);
617
618 }
619
620 rc = ext4_balloc_free_block(inode_ref, ind_block);
621 if (rc != EOK) {
622 block_put(block);
623 return rc;
624 }
625
626
627 }
628
629 block_put(block);
630 rc = ext4_balloc_free_block(inode_ref, fblock);
631 if (rc != EOK) {
632 return rc;
633 }
634
635 ext4_inode_set_indirect_block(inode_ref->inode, 2, 0);
636 }
637
638finish:
639
640 /* Mark inode dirty for writing to the physical device */
641 inode_ref->dirty = true;
642
643 /* Free inode by allocator */
644 if (ext4_inode_is_type(fs->superblock, inode_ref->inode,
645 EXT4_INODE_MODE_DIRECTORY)) {
646 rc = ext4_ialloc_free_inode(fs, inode_ref->index, true);
647 } else {
648 rc = ext4_ialloc_free_inode(fs, inode_ref->index, false);
649 }
650 if (rc != EOK) {
651 return rc;
652 }
653
654 return EOK;
655}
656
657/** Truncate i-node data blocks.
658 *
659 * @param inode_ref i-node to be truncated
660 * @param new_size new size of inode (must be < current size)
661 * @return error code
662 */
663int ext4_filesystem_truncate_inode(
664 ext4_inode_ref_t *inode_ref, aoff64_t new_size)
665{
666 int rc;
667
668 ext4_superblock_t *sb = inode_ref->fs->superblock;
669
670 /* Check flags, if i-node can be truncated */
671 if (! ext4_inode_can_truncate(sb, inode_ref->inode)) {
672 return EINVAL;
673 }
674
675 /* If sizes are equal, nothing has to be done. */
676 aoff64_t old_size = ext4_inode_get_size(sb, inode_ref->inode);
677 if (old_size == new_size) {
678 return EOK;
679 }
680
681 /* It's not suppported to make the larger file by truncate operation */
682 if (old_size < new_size) {
683 return EINVAL;
684 }
685
686 /* Compute how many blocks will be released */
687 aoff64_t size_diff = old_size - new_size;
688 uint32_t block_size = ext4_superblock_get_block_size(sb);
689 uint32_t diff_blocks_count = size_diff / block_size;
690 if (size_diff % block_size != 0) {
691 diff_blocks_count++;
692 }
693
694 uint32_t old_blocks_count = old_size / block_size;
695 if (old_size % block_size != 0) {
696 old_blocks_count++;
697 }
698
699 if (ext4_superblock_has_feature_incompatible(
700 inode_ref->fs->superblock, EXT4_FEATURE_INCOMPAT_EXTENTS) &&
701 ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)) {
702
703 /* Extents require special operation */
704
705 rc = ext4_extent_release_blocks_from(inode_ref,
706 old_blocks_count - diff_blocks_count);
707 if (rc != EOK) {
708 return rc;
709 }
710 } else {
711
712 /* Release data blocks from the end of file */
713
714 /* Starting from 1 because of logical blocks are numbered from 0 */
715 for (uint32_t i = 1; i <= diff_blocks_count; ++i) {
716 rc = ext4_filesystem_release_inode_block(inode_ref, old_blocks_count - i);
717 if (rc != EOK) {
718 return rc;
719 }
720 }
721 }
722
723 /* Update i-node */
724 ext4_inode_set_size(inode_ref->inode, new_size);
725 inode_ref->dirty = true;
726
727 return EOK;
728}
729
730/** Get physical block address by logical index of the block.
731 *
732 * @param inode_ref i-node to read block address from
733 * @param iblock logical index of block
734 * @param fblock output pointer for return physical block address
735 * @return error code
736 */
737int ext4_filesystem_get_inode_data_block_index(ext4_inode_ref_t *inode_ref,
738 aoff64_t iblock, uint32_t *fblock)
739{
740 int rc;
741
742 ext4_filesystem_t *fs = inode_ref->fs;
743
744 /* For empty file is situation simple */
745 if (ext4_inode_get_size(fs->superblock, inode_ref->inode) == 0) {
746 *fblock = 0;
747 return EOK;
748 }
749
750 uint32_t current_block;
751
752 /* Handle i-node using extents */
753 if (ext4_superblock_has_feature_incompatible(fs->superblock, EXT4_FEATURE_INCOMPAT_EXTENTS) &&
754 ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)) {
755
756 rc = ext4_extent_find_block(inode_ref, iblock, &current_block);
757
758 if (rc != EOK) {
759 return rc;
760 }
761
762 *fblock = current_block;
763 return EOK;
764
765 }
766
767 ext4_inode_t *inode = inode_ref->inode;
768
769 /* Direct block are read directly from array in i-node structure */
770 if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) {
771 current_block = ext4_inode_get_direct_block(inode, (uint32_t)iblock);
772 *fblock = current_block;
773 return EOK;
774 }
775
776 /* Determine indirection level of the target block */
777 int level = -1;
778 for (int i = 1; i < 4; i++) {
779 if (iblock < fs->inode_block_limits[i]) {
780 level = i;
781 break;
782 }
783 }
784
785 if (level == -1) {
786 return EIO;
787 }
788
789 /* Compute offsets for the topmost level */
790 aoff64_t block_offset_in_level = iblock - fs->inode_block_limits[level-1];
791 current_block = ext4_inode_get_indirect_block(inode, level-1);
792 uint32_t offset_in_block = block_offset_in_level / fs->inode_blocks_per_level[level-1];
793
794 /* Sparse file */
795 if (current_block == 0) {
796 *fblock = 0;
797 return EOK;
798 }
799
800 block_t *block;
801
802 /* Navigate through other levels, until we find the block number
803 * or find null reference meaning we are dealing with sparse file
804 */
805 while (level > 0) {
806
807 /* Load indirect block */
808 rc = block_get(&block, fs->device, current_block, 0);
809 if (rc != EOK) {
810 return rc;
811 }
812
813 /* Read block address from indirect block */
814 current_block = uint32_t_le2host(((uint32_t*)block->data)[offset_in_block]);
815
816 /* Put back indirect block untouched */
817 rc = block_put(block);
818 if (rc != EOK) {
819 return rc;
820 }
821
822 /* Check for sparse file */
823 if (current_block == 0) {
824 *fblock = 0;
825 return EOK;
826 }
827
828 /* Jump to the next level */
829 level -= 1;
830
831 /* Termination condition - we have address of data block loaded */
832 if (level == 0) {
833 break;
834 }
835
836 /* Visit the next level */
837 block_offset_in_level %= fs->inode_blocks_per_level[level];
838 offset_in_block = block_offset_in_level / fs->inode_blocks_per_level[level-1];
839 }
840
841 *fblock = current_block;
842
843 return EOK;
844}
845
846/** Set physical block address for the block logical address into the i-node.
847 *
848 * @param inode_ref i-node to set block address to
849 * @param iblock logical index of block
850 * @param fblock physical block address
851 * @return error code
852 */
853int ext4_filesystem_set_inode_data_block_index(ext4_inode_ref_t *inode_ref,
854 aoff64_t iblock, uint32_t fblock)
855{
856 int rc;
857
858 ext4_filesystem_t *fs = inode_ref->fs;
859
860 /* Handle inode using extents */
861 if (ext4_superblock_has_feature_compatible(fs->superblock, EXT4_FEATURE_INCOMPAT_EXTENTS) &&
862 ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)) {
863 /* not reachable !!! */
864 return ENOTSUP;
865 }
866
867 /* Handle simple case when we are dealing with direct reference */
868 if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) {
869 ext4_inode_set_direct_block(inode_ref->inode, (uint32_t)iblock, fblock);
870 inode_ref->dirty = true;
871 return EOK;
872 }
873
874 /* Determine the indirection level needed to get the desired block */
875 int level = -1;
876 for (int i = 1; i < 4; i++) {
877 if (iblock < fs->inode_block_limits[i]) {
878 level = i;
879 break;
880 }
881 }
882
883 if (level == -1) {
884 return EIO;
885 }
886
887 uint32_t block_size = ext4_superblock_get_block_size(fs->superblock);
888
889 /* Compute offsets for the topmost level */
890 aoff64_t block_offset_in_level = iblock - fs->inode_block_limits[level-1];
891 uint32_t current_block = ext4_inode_get_indirect_block(inode_ref->inode, level-1);
892 uint32_t offset_in_block = block_offset_in_level / fs->inode_blocks_per_level[level-1];
893
894 uint32_t new_block_addr;
895 block_t *block, *new_block;
896
897 /* Is needed to allocate indirect block on the i-node level */
898 if (current_block == 0) {
899
900 /* Allocate new indirect block */
901 rc = ext4_balloc_alloc_block(inode_ref, &new_block_addr);
902 if (rc != EOK) {
903 return rc;
904 }
905
906 /* Update i-node */
907 ext4_inode_set_indirect_block(inode_ref->inode, level - 1, new_block_addr);
908 inode_ref->dirty = true;
909
910 /* Load newly allocated block */
911 rc = block_get(&new_block, fs->device, new_block_addr, BLOCK_FLAGS_NOREAD);
912 if (rc != EOK) {
913 ext4_balloc_free_block(inode_ref, new_block_addr);
914 return rc;
915 }
916
917 /* Initialize new block */
918 memset(new_block->data, 0, block_size);
919 new_block->dirty = true;
920
921 /* Put back the allocated block */
922 rc = block_put(new_block);
923 if (rc != EOK) {
924 return rc;
925 }
926
927 current_block = new_block_addr;
928 }
929
930 /* Navigate through other levels, until we find the block number
931 * or find null reference meaning we are dealing with sparse file
932 */
933 while (level > 0) {
934
935 rc = block_get(&block, fs->device, current_block, 0);
936 if (rc != EOK) {
937 return rc;
938 }
939
940 current_block = uint32_t_le2host(((uint32_t*)block->data)[offset_in_block]);
941
942 if ((level > 1) && (current_block == 0)) {
943
944 /* Allocate new block */
945 rc = ext4_balloc_alloc_block(inode_ref, &new_block_addr);
946 if (rc != EOK) {
947 block_put(block);
948 return rc;
949 }
950
951 /* Load newly allocated block */
952 rc = block_get(&new_block, fs->device, new_block_addr, BLOCK_FLAGS_NOREAD);
953 if (rc != EOK) {
954 block_put(block);
955 return rc;
956 }
957
958 /* Initialize allocated block */
959 memset(new_block->data, 0, block_size);
960 new_block->dirty = true;
961
962 rc = block_put(new_block);
963 if (rc != EOK) {
964 block_put(block);
965 return rc;
966 }
967
968 /* Write block address to the parent */
969 ((uint32_t*)block->data)[offset_in_block] = host2uint32_t_le(new_block_addr);
970 block->dirty = true;
971 current_block = new_block_addr;
972 }
973
974 /* Will be finished, write the fblock address */
975 if (level == 1) {
976 ((uint32_t*)block->data)[offset_in_block] = host2uint32_t_le(fblock);
977 block->dirty = true;
978 }
979
980 rc = block_put(block);
981 if (rc != EOK) {
982 return rc;
983 }
984
985 level -= 1;
986
987 /* If we are on the last level, break here as
988 * there is no next level to visit
989 */
990 if (level == 0) {
991 break;
992 }
993
994 /* Visit the next level */
995 block_offset_in_level %= fs->inode_blocks_per_level[level];
996 offset_in_block = block_offset_in_level / fs->inode_blocks_per_level[level-1];
997 }
998
999 return EOK;
1000}
1001
1002/** Release data block from i-node
1003 *
1004 * @param inode_ref i-node to release block from
1005 * @param iblock logical block to be released
1006 * @return error code
1007 */
1008int ext4_filesystem_release_inode_block(
1009 ext4_inode_ref_t *inode_ref, uint32_t iblock)
1010{
1011 int rc;
1012
1013 uint32_t fblock;
1014
1015 ext4_filesystem_t *fs = inode_ref->fs;
1016
1017 /* EXTENTS are handled otherwise = there is not support in this function */
1018 assert(! (ext4_superblock_has_feature_incompatible(fs->superblock,
1019 EXT4_FEATURE_INCOMPAT_EXTENTS) &&
1020 ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)));
1021
1022 ext4_inode_t *inode = inode_ref->inode;
1023
1024 /* Handle simple case when we are dealing with direct reference */
1025 if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) {
1026 fblock = ext4_inode_get_direct_block(inode, iblock);
1027 /* Sparse file */
1028 if (fblock == 0) {
1029 return EOK;
1030 }
1031
1032 ext4_inode_set_direct_block(inode, iblock, 0);
1033 return ext4_balloc_free_block(inode_ref, fblock);
1034 }
1035
1036
1037 /* Determine the indirection level needed to get the desired block */
1038 int level = -1;
1039 for (int i = 1; i < 4; i++) {
1040 if (iblock < fs->inode_block_limits[i]) {
1041 level = i;
1042 break;
1043 }
1044 }
1045
1046 if (level == -1) {
1047 return EIO;
1048 }
1049
1050 /* Compute offsets for the topmost level */
1051 aoff64_t block_offset_in_level = iblock - fs->inode_block_limits[level-1];
1052 uint32_t current_block = ext4_inode_get_indirect_block(inode, level-1);
1053 uint32_t offset_in_block = block_offset_in_level / fs->inode_blocks_per_level[level-1];
1054
1055 /* Navigate through other levels, until we find the block number
1056 * or find null reference meaning we are dealing with sparse file
1057 */
1058 block_t *block;
1059 while (level > 0) {
1060 rc = block_get(&block, fs->device, current_block, 0);
1061 if (rc != EOK) {
1062 return rc;
1063 }
1064
1065 current_block = uint32_t_le2host(((uint32_t*)block->data)[offset_in_block]);
1066
1067 /* Set zero if physical data block address found */
1068 if (level == 1) {
1069 ((uint32_t*)block->data)[offset_in_block] = host2uint32_t_le(0);
1070 block->dirty = true;
1071 }
1072
1073 rc = block_put(block);
1074 if (rc != EOK) {
1075 return rc;
1076 }
1077
1078 level -= 1;
1079
1080 /* If we are on the last level, break here as
1081 * there is no next level to visit
1082 */
1083 if (level == 0) {
1084 break;
1085 }
1086
1087 /* Visit the next level */
1088 block_offset_in_level %= fs->inode_blocks_per_level[level];
1089 offset_in_block = block_offset_in_level / fs->inode_blocks_per_level[level-1];
1090 }
1091
1092 fblock = current_block;
1093
1094 if (fblock == 0) {
1095 return EOK;
1096 }
1097
1098 /* Physical block is not referenced, it can be released */
1099
1100 return ext4_balloc_free_block(inode_ref, fblock);
1101
1102}
1103
1104/** Append following logical block to the i-node.
1105 *
1106 * @param inode_ref i-node to append block to
1107 * @param fblock output physical block address of newly allocated block
1108 * @param iblock output logical number of newly allocated block
1109 * @return error code
1110 */
1111int ext4_filesystem_append_inode_block(ext4_inode_ref_t *inode_ref,
1112 uint32_t *fblock, uint32_t *iblock)
1113{
1114 int rc;
1115
1116 /* Handle extents separately */
1117 if (ext4_superblock_has_feature_incompatible(
1118 inode_ref->fs->superblock, EXT4_FEATURE_INCOMPAT_EXTENTS) &&
1119 ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)) {
1120
1121 return ext4_extent_append_block(inode_ref, iblock, fblock, true);
1122
1123 }
1124
1125 ext4_superblock_t *sb = inode_ref->fs->superblock;
1126
1127 /* Compute next block index and allocate data block */
1128 uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode);
1129 uint32_t block_size = ext4_superblock_get_block_size(sb);
1130
1131 /* Align size i-node size */
1132 if ((inode_size % block_size) != 0) {
1133 inode_size += block_size - (inode_size % block_size);
1134 }
1135
1136 /* Logical blocks are numbered from 0 */
1137 uint32_t new_block_idx = inode_size / block_size;
1138
1139 /* Allocate new physical block */
1140 uint32_t phys_block;
1141 rc = ext4_balloc_alloc_block(inode_ref, &phys_block);
1142 if (rc != EOK) {
1143 return rc;
1144 }
1145
1146 /* Add physical block address to the i-node */
1147 rc = ext4_filesystem_set_inode_data_block_index(inode_ref, new_block_idx, phys_block);
1148 if (rc != EOK) {
1149 ext4_balloc_free_block(inode_ref, phys_block);
1150 return rc;
1151 }
1152
1153 /* Update i-node */
1154 ext4_inode_set_size(inode_ref->inode, inode_size + block_size);
1155 inode_ref->dirty = true;
1156
1157 *fblock = phys_block;
1158 *iblock = new_block_idx;
1159
1160 return EOK;
1161}
1162
1163/** Add orphaned i-node to the orphans linked list.
1164 *
1165 * @param inode_ref i-node to be added to orphans list
1166 * @return error code
1167 */
1168int ext4_filesystem_add_orphan(ext4_inode_ref_t *inode_ref)
1169{
1170
1171 EXT4FS_DBG("adding orphan \%u", inode_ref->index);
1172
1173 uint32_t next_orphan = ext4_superblock_get_last_orphan(
1174 inode_ref->fs->superblock);
1175
1176 /* Deletion time is used for holding next item of the list */
1177 ext4_inode_set_deletion_time(inode_ref->inode, next_orphan);
1178
1179 /* Head of the list is in the superblock */
1180 ext4_superblock_set_last_orphan(
1181 inode_ref->fs->superblock, inode_ref->index);
1182 inode_ref->dirty = true;
1183
1184 return EOK;
1185}
1186
1187/** Delete orphaned i-node from the orphans linked list.
1188 *
1189 * @param inode_ref i-node to be deleted from the orphans list
1190 * @return error code
1191 */
1192int ext4_filesystem_delete_orphan(ext4_inode_ref_t *inode_ref)
1193{
1194
1195 EXT4FS_DBG("adding orphan \%u", inode_ref->index);
1196
1197 int rc;
1198
1199 /* Get head of the linked list */
1200 uint32_t last_orphan = ext4_superblock_get_last_orphan(
1201 inode_ref->fs->superblock);
1202
1203 assert (last_orphan != 0);
1204
1205 ext4_inode_ref_t *current;
1206 rc = ext4_filesystem_get_inode_ref(inode_ref->fs, last_orphan, &current);
1207 if (rc != EOK) {
1208 return rc;
1209 }
1210
1211 uint32_t next_orphan = ext4_inode_get_deletion_time(current->inode);
1212
1213 /* Check if the head is the target */
1214 if (last_orphan == inode_ref->index) {
1215 ext4_superblock_set_last_orphan(inode_ref->fs->superblock, next_orphan);
1216 return EOK;
1217 }
1218
1219 bool found = false;
1220
1221 /* Walk thourgh the linked list */
1222 while (next_orphan != 0) {
1223
1224 /* Found? */
1225 if (next_orphan == inode_ref->index) {
1226 next_orphan = ext4_inode_get_deletion_time(inode_ref->inode);
1227 ext4_inode_set_deletion_time(current->inode, next_orphan);
1228 current->dirty = true;
1229 found = true;
1230 break;
1231 }
1232
1233 ext4_filesystem_put_inode_ref(current);
1234
1235 rc = ext4_filesystem_get_inode_ref(inode_ref->fs, next_orphan, &current);
1236 if (rc != EOK) {
1237 return rc;
1238 }
1239 next_orphan = ext4_inode_get_deletion_time(current->inode);
1240
1241 }
1242
1243 rc = ext4_filesystem_put_inode_ref(current);
1244 if (rc != EOK) {
1245 return rc;
1246 }
1247
1248 if (!found) {
1249 return ENOENT;
1250 } else {
1251 return EOK;
1252 }
1253}
1254
1255/**
1256 * @}
1257 */
Note: See TracBrowser for help on using the repository browser.