source: mainline/uspace/lib/ext2/libext2_filesystem.c@ 32cd37f

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

Fix memory leak in libext2 (thx Maurizio Lombardi)

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