source: mainline/uspace/lib/ext2/libext2_filesystem.c@ 18626b3

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 18626b3 was c7faade, checked in by Martin Sucha <sucha14@…>, 14 years ago

Fix reading large files

  • Property mode set to 100644
File size: 12.5 KB
Line 
1/*
2 * Copyright (c) 2011 Martin Sucha
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 libext2
30 * @{
31 */
32/**
33 * @file
34 */
35
36#include "libext2_filesystem.h"
37#include "libext2_superblock.h"
38#include "libext2_block_group.h"
39#include "libext2_inode.h"
40#include <errno.h>
41#include <libblock.h>
42#include <malloc.h>
43#include <assert.h>
44
45/**
46 * Initialize an instance of filesystem on the device.
47 * This function reads superblock from the device and
48 * initializes libblock cache with appropriate logical block size.
49 *
50 * @param fs Pointer to ext2_filesystem_t to initialize
51 * @param devmap_handle Device handle of the block device
52 *
53 * @return EOK on success or negative error code on failure
54 */
55int ext2_filesystem_init(ext2_filesystem_t *fs, devmap_handle_t devmap_handle)
56{
57 int rc;
58 ext2_superblock_t *temp_superblock;
59 size_t block_size;
60
61 fs->device = devmap_handle;
62
63 rc = block_init(fs->device, 2048);
64 if (rc != EOK) {
65 return rc;
66 }
67
68 rc = ext2_superblock_read_direct(fs->device, &temp_superblock);
69 if (rc != EOK) {
70 block_fini(fs->device);
71 return rc;
72 }
73
74 block_size = ext2_superblock_get_block_size(temp_superblock);
75
76 if (block_size > EXT2_MAX_BLOCK_SIZE) {
77 block_fini(fs->device);
78 return ENOTSUP;
79 }
80
81 rc = block_cache_init(devmap_handle, block_size, 0, CACHE_MODE_WT);
82 if (rc != EOK) {
83 block_fini(fs->device);
84 return rc;
85 }
86
87 fs->superblock = temp_superblock;
88
89 return EOK;
90}
91
92/**
93 * Check filesystem for sanity
94 *
95 * @param fs Pointer to ext2_filesystem_t to check
96 * @return EOK on success or negative error code on failure
97 */
98int ext2_filesystem_check_sanity(ext2_filesystem_t *fs)
99{
100 int rc;
101
102 rc = ext2_superblock_check_sanity(fs->superblock);
103 if (rc != EOK) {
104 return rc;
105 }
106
107 return EOK;
108}
109
110/**
111 * Check feature flags
112 *
113 * @param fs Pointer to ext2_filesystem_t to check
114 * @param read_only bool to set to true if the fs needs to be read-only
115 * @return EOK on success or negative error code on failure
116 */
117int ext2_filesystem_check_flags(ext2_filesystem_t *fs, bool *o_read_only)
118{
119 // feature flags are present in rev 1 and later
120 if (ext2_superblock_get_rev_major(fs->superblock) == 0) {
121 *o_read_only = false;
122 return 0;
123 }
124
125 uint32_t incompatible;
126 uint32_t read_only;
127
128 incompatible = ext2_superblock_get_features_incompatible(fs->superblock);
129 read_only = ext2_superblock_get_features_read_only(fs->superblock);
130
131 // unset any supported features
132 incompatible &= ~EXT2_SUPPORTED_INCOMPATIBLE_FEATURES;
133 read_only &= ~EXT2_SUPPORTED_READ_ONLY_FEATURES;
134
135 if (incompatible > 0) {
136 *o_read_only = true;
137 return ENOTSUP;
138 }
139
140 if (read_only > 0) {
141 *o_read_only = true;
142 }
143
144 return EOK;
145}
146
147/**
148 * Get a reference to block descriptor
149 *
150 * @param fs Pointer to filesystem information
151 * @param bgid Index of block group to find
152 * @param ref Pointer where to store pointer to block group reference
153 *
154 * @return EOK on success or negative error code on failure
155 */
156int ext2_filesystem_get_block_group_ref(ext2_filesystem_t *fs, uint32_t bgid,
157 ext2_block_group_ref_t **ref)
158{
159 int rc;
160 aoff64_t block_id;
161 uint32_t descriptors_per_block;
162 size_t offset;
163 ext2_block_group_ref_t *newref;
164
165 newref = malloc(sizeof(ext2_block_group_ref_t));
166 if (newref == NULL) {
167 return ENOMEM;
168 }
169
170 descriptors_per_block = ext2_superblock_get_block_size(fs->superblock)
171 / EXT2_BLOCK_GROUP_DESCRIPTOR_SIZE;
172
173 // Block group descriptor table starts at the next block after superblock
174 block_id = ext2_superblock_get_first_block(fs->superblock) + 1;
175
176 // Find the block containing the descriptor we are looking for
177 block_id += bgid / descriptors_per_block;
178 offset = (bgid % descriptors_per_block) * EXT2_BLOCK_GROUP_DESCRIPTOR_SIZE;
179
180 rc = block_get(&newref->block, fs->device, block_id, 0);
181 if (rc != EOK) {
182 free(newref);
183 return rc;
184 }
185
186 newref->block_group = newref->block->data + offset;
187
188 *ref = newref;
189
190 return EOK;
191}
192
193/**
194 * Free a reference to block group
195 *
196 * @param ref Pointer to block group reference to free
197 *
198 * @return EOK on success or negative error code on failure
199 */
200int ext2_filesystem_put_block_group_ref(ext2_block_group_ref_t *ref)
201{
202 int rc;
203
204 rc = block_put(ref->block);
205 free(ref);
206
207 return rc;
208}
209
210/**
211 * Get a reference to inode
212 *
213 * @param fs Pointer to filesystem information
214 * @param index The index number of the inode
215 * @param ref Pointer where to store pointer to inode reference
216 *
217 * @return EOK on success or negative error code on failure
218 */
219int ext2_filesystem_get_inode_ref(ext2_filesystem_t *fs, uint32_t index,
220 ext2_inode_ref_t **ref)
221{
222 int rc;
223 aoff64_t block_id;
224 uint32_t block_group;
225 uint32_t offset_in_group;
226 uint32_t byte_offset_in_group;
227 size_t offset_in_block;
228 uint32_t inodes_per_group;
229 uint32_t inode_table_start;
230 uint16_t inode_size;
231 uint32_t block_size;
232 ext2_block_group_ref_t *bg_ref;
233 ext2_inode_ref_t *newref;
234
235 newref = malloc(sizeof(ext2_inode_ref_t));
236 if (newref == NULL) {
237 return ENOMEM;
238 }
239
240 inodes_per_group = ext2_superblock_get_inodes_per_group(fs->superblock);
241
242 // inode numbers are 1-based
243 index -= 1;
244 block_group = index / inodes_per_group;
245 offset_in_group = index % inodes_per_group;
246
247 rc = ext2_filesystem_get_block_group_ref(fs, block_group, &bg_ref);
248 if (rc != EOK) {
249 free(newref);
250 return rc;
251 }
252
253 inode_table_start = ext2_block_group_get_inode_table_first_block(
254 bg_ref->block_group);
255
256 inode_size = ext2_superblock_get_inode_size(fs->superblock);
257 block_size = ext2_superblock_get_block_size(fs->superblock);
258
259 byte_offset_in_group = offset_in_group * inode_size;
260
261 block_id = inode_table_start + (byte_offset_in_group / block_size);
262 offset_in_block = byte_offset_in_group % block_size;
263
264 rc = block_get(&newref->block, fs->device, block_id, 0);
265 if (rc != EOK) {
266 free(newref);
267 return rc;
268 }
269
270 newref->inode = newref->block->data + offset_in_block;
271 newref->index = index+1; // we decremented index above
272
273 *ref = newref;
274
275 return EOK;
276}
277
278/**
279 * Free a reference to inode
280 *
281 * @param ref Pointer to inode reference to free
282 *
283 * @return EOK on success or negative error code on failure
284 */
285int ext2_filesystem_put_inode_ref(ext2_inode_ref_t *ref)
286{
287 int rc;
288
289 rc = block_put(ref->block);
290 free(ref);
291
292 return rc;
293}
294
295/**
296 * Find a filesystem block number where iblock-th data block
297 * of the given inode is located.
298 *
299 * @param fblock the number of filesystem block, or 0 if no such block is allocated yet
300 *
301 * @return EOK on success or negative error code on failure
302 */
303int ext2_filesystem_get_inode_data_block_index(ext2_filesystem_t *fs, ext2_inode_t* inode,
304 aoff64_t iblock, uint32_t* fblock)
305{
306 int rc;
307 aoff64_t limits[4];
308 uint32_t block_ids_per_block;
309 aoff64_t blocks_per_level[4];
310 uint32_t offset_in_block;
311 uint32_t current_block;
312 aoff64_t block_offset_in_level;
313 int i;
314 int level;
315 block_t *block;
316
317 if (iblock < EXT2_INODE_DIRECT_BLOCKS) {
318 current_block = ext2_inode_get_direct_block(inode, (uint32_t)iblock);
319 *fblock = current_block;
320 return EOK;
321 }
322
323 // Compute limits for indirect block levels
324 // TODO: compute this once when loading filesystem and store in ext2_filesystem_t
325 block_ids_per_block = ext2_superblock_get_block_size(fs->superblock) / sizeof(uint32_t);
326 limits[0] = EXT2_INODE_DIRECT_BLOCKS;
327 blocks_per_level[0] = 1;
328 for (i = 1; i < 4; i++) {
329 blocks_per_level[i] = blocks_per_level[i-1] *
330 block_ids_per_block;
331 limits[i] = limits[i-1] + blocks_per_level[i];
332 }
333
334 // Determine the indirection level needed to get the desired block
335 level = -1;
336 for (i = 1; i < 4; i++) {
337 if (iblock < limits[i]) {
338 level = i;
339 break;
340 }
341 }
342
343 if (level == -1) {
344 return EIO;
345 }
346
347 block_offset_in_level = iblock - limits[level-1];
348 current_block = ext2_inode_get_indirect_block(inode, level-1);
349 offset_in_block = block_offset_in_level / blocks_per_level[level-1];
350
351 while (level > 0) {
352 rc = block_get(&block, fs->device, current_block, 0);
353 if (rc != EOK) {
354 return rc;
355 }
356
357 assert(offset_in_block < block_ids_per_block);
358 current_block = ((uint32_t*)block->data)[offset_in_block];
359
360 rc = block_put(block);
361 if (rc != EOK) {
362 return rc;
363 }
364
365 if (current_block == 0) {
366 *fblock = 0;
367 return EOK;
368 }
369
370 level -= 1;
371
372 if (level == 0) {
373 break;
374 }
375
376 block_offset_in_level %= blocks_per_level[level];
377 offset_in_block = block_offset_in_level / blocks_per_level[level-1];
378 }
379
380 *fblock = current_block;
381
382 return EOK;
383}
384
385/**
386 * Allocate a given number of blocks and store their ids in blocks
387 *
388 * @param fs pointer to filesystem
389 * @param blocks array of count uint32_t values where store block ids
390 * @param count number of blocks to allocate and elements in blocks array
391 * @param preferred_bg preferred block group number
392 *
393 * @return EOK on success or negative error code on failure
394 */
395int ext2_filesystem_allocate_blocks(ext2_filesystem_t *fs, uint32_t *blocks,
396 size_t count, uint32_t preferred_bg)
397{
398 uint32_t bg_count = ext2_superblock_get_block_group_count(fs->superblock);
399 uint32_t bpg = ext2_superblock_get_blocks_per_group(fs->superblock);
400 uint32_t block_size = ext2_superblock_get_block_size(fs->superblock);
401 uint32_t block_group = preferred_bg;
402 uint32_t free_blocks_sb;
403 uint32_t block_groups_left;
404 size_t idx;
405 ext2_block_group_ref_t *bg;
406 int rc;
407 uint32_t bb_block;
408 block_t *block;
409 size_t bb_idx;
410 size_t bb_bit;
411
412 free_blocks_sb = ext2_superblock_get_free_block_count(fs->superblock);
413
414 if (count > free_blocks_sb) {
415 return EIO;
416 }
417
418 block_groups_left = bg_count;
419
420 idx = 0;
421
422 // Read the block group descriptor
423 rc = ext2_filesystem_get_block_group_ref(fs, block_group, &bg);
424 if (rc != EOK) {
425 goto failed;
426 }
427
428 while (idx < count && block_groups_left > 0) {
429 uint16_t fb = ext2_block_group_get_free_block_count(bg->block_group);
430 if (fb == 0) {
431 block_group = (block_group + 1) % bg_count;
432 block_groups_left -= 1;
433
434 rc = ext2_filesystem_put_block_group_ref(bg);
435 if (rc != EOK) {
436 goto failed;
437 }
438
439 rc = ext2_filesystem_get_block_group_ref(fs, block_group, &bg);
440 if (rc != EOK) {
441 goto failed;
442 }
443 continue;
444 }
445
446 // We found a block group with free block, let's look at the block bitmap
447 bb_block = ext2_block_group_get_block_bitmap_block(bg->block_group);
448
449 rc = block_get(&block, fs->device, bb_block, BLOCK_FLAGS_NONE);
450 if (rc != EOK) {
451 goto failed;
452 }
453
454 // Use all blocks from this block group
455 for (bb_idx = 0; bb_idx < block_size && idx < count; bb_idx++) {
456 uint8_t *data = (uint8_t *) block->data;
457 if (data[bb_idx] == 0xff) {
458 continue;
459 }
460 // find an empty bit
461 uint8_t mask;
462 for (mask = 1, bb_bit = 0;
463 bb_bit < 8 && idx < count;
464 bb_bit++, mask = mask << 1) {
465 if ((data[bb_idx] & mask) == 0) {
466 // free block found
467 blocks[idx] = block_group * bpg + bb_idx*8 + bb_bit;
468 data[bb_idx] |= mask;
469 idx += 1;
470 fb -= 1;
471 ext2_block_group_set_free_block_count(bg->block_group, fb);
472 }
473 }
474 }
475 }
476
477 rc = ext2_filesystem_put_block_group_ref(bg);
478 if (rc != EOK) {
479 goto failed;
480 }
481
482 // TODO update superblock
483
484 return EOK;
485failed:
486 // TODO deallocate already allocated blocks, if possible
487
488 return rc;
489}
490
491/**
492 * Finalize an instance of filesystem
493 *
494 * @param fs Pointer to ext2_filesystem_t to finalize
495 */
496void ext2_filesystem_fini(ext2_filesystem_t *fs)
497{
498 free(fs->superblock);
499 block_fini(fs->device);
500}
501
502
503/** @}
504 */
Note: See TracBrowser for help on using the repository browser.