diff options
Diffstat (limited to 'ext2fs/balloc.c')
-rw-r--r-- | ext2fs/balloc.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/ext2fs/balloc.c b/ext2fs/balloc.c new file mode 100644 index 00000000..437febaa --- /dev/null +++ b/ext2fs/balloc.c @@ -0,0 +1,490 @@ +/* Block allocation routines + + Copyright (C) 1995, 1999 Free Software Foundation, Inc. + + Converted to work under the hurd by Miles Bader <miles@gnu.org> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* + * linux/fs/ext2/balloc.c + * + * Copyright (C) 1992, 1993, 1994, 1995 + * Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * Enhanced block allocation by Stephen Tweedie (sct@dcs.ed.ac.uk), 1993 + */ + +/* + * The free blocks are managed by bitmaps. A file system contains several + * blocks groups. Each group contains 1 bitmap block for blocks, 1 bitmap + * block for inodes, N blocks for the inode table and data blocks. + * + * The file system contains group descriptors which are located after the + * super block. Each descriptor contains the number of the bitmap block and + * the free blocks count in the block. The descriptors are loaded in memory + * when a file system is mounted (see ext2_read_super). + */ + +#include <string.h> +#include "ext2fs.h" +#include "bitmap.c" + +/* Returns a pointer to the first occurence of CH in the buffer BUF of len + LEN, or BUF + LEN if CH doesn't occur. */ +static inline void * +memscan (void *buf, unsigned char ch, size_t len) +{ + return memchr (buf, ch, len) ?: buf + len; +} + +#define in_range(b, first, len) ((b) >= (first) && (b) <= (first) + (len) - 1) + +void +ext2_free_blocks (block_t block, unsigned long count) +{ + char *bh; + unsigned long block_group; + unsigned long bit; + unsigned long i; + struct ext2_group_desc *gdp; + + spin_lock (&global_lock); + + if (block < sblock->s_first_data_block || + (block + count) > sblock->s_blocks_count) + { + ext2_error ("freeing blocks not in datazone - " + "block = %u, count = %lu", block, count); + spin_unlock (&global_lock); + return; + } + + ext2_debug ("freeing block %lu[%lu]", block, count); + + do + { + unsigned long int gcount = count; + + block_group = ((block - sblock->s_first_data_block) + / sblock->s_blocks_per_group); + bit = (block - sblock->s_first_data_block) % sblock->s_blocks_per_group; + if (bit + count > sblock->s_blocks_per_group) + { + unsigned long overflow = bit + count - sblock->s_blocks_per_group; + gcount -= overflow; + ext2_debug ("freeing blocks across group boundary - " + "block = %u, count = %lu", + block, count); + } + gdp = group_desc (block_group); + bh = bptr (gdp->bg_block_bitmap); + + if (in_range (gdp->bg_block_bitmap, block, gcount) || + in_range (gdp->bg_inode_bitmap, block, gcount) || + in_range (block, gdp->bg_inode_table, itb_per_group) || + in_range (block + gcount - 1, gdp->bg_inode_table, itb_per_group)) + ext2_panic ("freeing blocks in system zones - " + "block = %u, count = %lu", + block, count); + + for (i = 0; i < gcount; i++) + { + if (!clear_bit (bit + i, bh)) + ext2_warning ("bit already cleared for block %lu", block + i); + else + { + gdp->bg_free_blocks_count++; + sblock->s_free_blocks_count++; + } + } + + record_global_poke (bh); + record_global_poke (gdp); + + block += gcount; + count -= gcount; + } while (count > 0); + + sblock_dirty = 1; + + spin_unlock (&global_lock); + + alloc_sync (0); +} + +/* + * ext2_new_block uses a goal block to assist allocation. If the goal is + * free, or there is a free block within 32 blocks of the goal, that block + * is allocated. Otherwise a forward search is made for a free block; within + * each block group the search first looks for an entire free byte in the block + * bitmap, and then for any free bit if that fails. + */ +block_t +ext2_new_block (block_t goal, + block_t prealloc_goal, + block_t *prealloc_count, block_t *prealloc_block) +{ + char *bh; + char *p, *r; + int i, j, k, tmp; + unsigned long lmap; + struct ext2_group_desc *gdp; + +#ifdef EXT2FS_DEBUG + static int goal_hits = 0, goal_attempts = 0; +#endif + + spin_lock (&global_lock); + +#ifdef XXX /* Auth check to use reserved blocks */ + if (sblock->s_free_blocks_count <= sblock->s_r_blocks_count && + (!fsuser () && (sb->u.ext2_sb.s_resuid != current->fsuid) && + (sb->u.ext2_sb.s_resgid == 0 || + !in_group_p (sb->u.ext2_sb.s_resgid)))) + { + spin_unlock (&global_lock); + return 0; + } +#endif + + ext2_debug ("goal=%lu", goal); + +repeat: + /* + * First, test whether the goal block is free. + */ + if (goal < sblock->s_first_data_block || goal >= sblock->s_blocks_count) + goal = sblock->s_first_data_block; + i = (goal - sblock->s_first_data_block) / sblock->s_blocks_per_group; + gdp = group_desc (i); + if (gdp->bg_free_blocks_count > 0) + { + j = ((goal - sblock->s_first_data_block) % sblock->s_blocks_per_group); +#ifdef EXT2FS_DEBUG + if (j) + goal_attempts++; +#endif + bh = bptr (gdp->bg_block_bitmap); + + ext2_debug ("goal is at %d:%d", i, j); + + if (!test_bit (j, bh)) + { +#ifdef EXT2FS_DEBUG + goal_hits++; + ext2_debug ("goal bit allocated!"); +#endif + goto got_block; + } + if (j) + { + /* + * The goal was occupied; search forward for a free + * block within the next 32 blocks + */ + lmap = ((((unsigned long *) bh)[j >> 5]) >> + ((j & 31) + 1)); + if (j < sblock->s_blocks_per_group - 32) + lmap |= (((unsigned long *) bh)[(j >> 5) + 1]) << + (31 - (j & 31)); + else + lmap |= 0xffffffff << (31 - (j & 31)); + if (lmap != 0xffffffffl) + { + k = ffz (lmap) + 1; + if ((j + k) < sblock->s_blocks_per_group) + { + j += k; + goto got_block; + } + } + } + + ext2_debug ("bit not found near goal"); + + /* + * There has been no free block found in the near vicinity + * of the goal: do a search forward through the block groups, + * searching in each group first for an entire free byte in + * the bitmap and then for any free bit. + * + * Search first in the remainder of the current group; then, + * cyclicly search through the rest of the groups. + */ + p = ((char *) bh) + (j >> 3); + r = memscan (p, 0, (sblock->s_blocks_per_group - j + 7) >> 3); + k = (r - ((char *) bh)) << 3; + if (k < sblock->s_blocks_per_group) + { + j = k; + goto search_back; + } + k = find_next_zero_bit ((unsigned long *) bh, + sblock->s_blocks_per_group, + j); + if (k < sblock->s_blocks_per_group) + { + j = k; + goto got_block; + } + } + + ext2_debug ("bit not found in block group %d", i); + + /* + * Now search the rest of the groups. We assume that + * i and gdp correctly point to the last group visited. + */ + for (k = 0; k < groups_count; k++) + { + i++; + if (i >= groups_count) + i = 0; + gdp = group_desc (i); + if (gdp->bg_free_blocks_count > 0) + break; + } + if (k >= groups_count) + { + spin_unlock (&global_lock); + return 0; + } + bh = bptr (gdp->bg_block_bitmap); + r = memscan (bh, 0, sblock->s_blocks_per_group >> 3); + j = (r - bh) << 3; + if (j < sblock->s_blocks_per_group) + goto search_back; + else + j = find_first_zero_bit ((unsigned long *) bh, + sblock->s_blocks_per_group); + if (j >= sblock->s_blocks_per_group) + { + ext2_error ("free blocks count corrupted for block group %d", i); + spin_unlock (&global_lock); + return 0; + } + +search_back: + /* + * We have succeeded in finding a free byte in the block + * bitmap. Now search backwards up to 7 bits to find the + * start of this group of free blocks. + */ + for (k = 0; k < 7 && j > 0 && !test_bit (j - 1, bh); k++, j--); + +got_block: + + ext2_debug ("using block group %d (%d)", i, gdp->bg_free_blocks_count); + + tmp = j + i * sblock->s_blocks_per_group + sblock->s_first_data_block; + + if (tmp == gdp->bg_block_bitmap || + tmp == gdp->bg_inode_bitmap || + in_range (tmp, gdp->bg_inode_table, itb_per_group)) + ext2_panic ("allocating block in system zone; block = %u", tmp); + + if (set_bit (j, bh)) + { + ext2_warning ("bit already set for block %d", j); + goto repeat; + } + + /* Since due to bletcherousness block-modified bits are never turned off + when writing disk-pager pages, make sure they are here, in case this + block is being allocated to a file (see pager.c). */ + if (modified_global_blocks) + { + spin_lock (&modified_global_blocks_lock); + clear_bit (tmp, modified_global_blocks); + spin_unlock (&modified_global_blocks_lock); + } + + ext2_debug ("found bit %d", j); + + /* + * Do block preallocation now if required. + */ +#ifdef EXT2_PREALLOCATE + if (prealloc_goal) + { + *prealloc_count = 0; + *prealloc_block = tmp + 1; + for (k = 1; + k < prealloc_goal && (j + k) < sblock->s_blocks_per_group; k++) + { + if (set_bit (j + k, bh)) + break; + (*prealloc_count)++; + + /* (See comment before the clear_bit above) */ + if (modified_global_blocks) + { + spin_lock (&modified_global_blocks_lock); + clear_bit (tmp + k, modified_global_blocks); + spin_unlock (&modified_global_blocks_lock); + } + } + gdp->bg_free_blocks_count -= *prealloc_count; + sblock->s_free_blocks_count -= *prealloc_count; + ext2_debug ("preallocated a further %lu bits", *prealloc_count); + } +#endif + + j = tmp; + + record_global_poke (bh); + + if (j >= sblock->s_blocks_count) + { + ext2_error ("block >= blocks count - block_group = %d, block=%d", i, j); + j = 0; + goto sync_out; + } + + ext2_debug ("allocating block %d; goal hits %d of %d", + j, goal_hits, goal_attempts); + + gdp->bg_free_blocks_count--; + record_global_poke (gdp); + + sblock->s_free_blocks_count--; + sblock_dirty = 1; + + sync_out: + spin_unlock (&global_lock); + alloc_sync (0); + + return j; +} + +unsigned long +ext2_count_free_blocks () +{ +#ifdef EXT2FS_DEBUG + unsigned long desc_count, bitmap_count, x; + struct ext2_group_desc *gdp; + int i; + + spin_lock (&global_lock); + + desc_count = 0; + bitmap_count = 0; + gdp = NULL; + for (i = 0; i < groups_count; i++) + { + gdp = group_desc (i); + desc_count += gdp->bg_free_blocks_count; + x = count_free (bptr (gdp->bg_block_bitmap), block_size); + printf ("group %d: stored = %d, counted = %lu", + i, gdp->bg_free_blocks_count, x); + bitmap_count += x; + } + printf ("ext2_count_free_blocks: stored = %lu, computed = %lu, %lu", + sblock->s_free_blocks_count, desc_count, bitmap_count); + spin_unlock (&global_lock); + return bitmap_count; +#else + return sblock->s_free_blocks_count; +#endif +} + +static inline int +block_in_use (block_t block, unsigned char *map) +{ + return test_bit ((block - sblock->s_first_data_block) % + sblock->s_blocks_per_group, map); +} + +void +ext2_check_blocks_bitmap () +{ + char *bh; + unsigned long desc_count, bitmap_count, x; + unsigned long desc_blocks; + struct ext2_group_desc *gdp; + int i, j; + + spin_lock (&global_lock); + + desc_count = 0; + bitmap_count = 0; + gdp = NULL; + + desc_blocks = (groups_count + desc_per_block - 1) / desc_per_block; + + for (i = 0; i < groups_count; i++) + { + inline int test_root (int a, int b) + { + if (a == 0) + return 1; + while (1) + { + if (a == 1) + return 1; + if (a % b) + return 0; + a = a / b; + } + } + inline int ext2_group_sparse (int group) + { + return (test_root (group, 3) || test_root (group, 5) + || test_root (group, 7)); + } + + gdp = group_desc (i); + desc_count += gdp->bg_free_blocks_count; + bh = bptr (gdp->bg_block_bitmap); + + if (!EXT2_HAS_RO_COMPAT_FEATURE (sblock, + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER) + || ext2_group_sparse (i)) + { + if (!test_bit (0, bh)) + ext2_error ("superblock in group %d is marked free", i); + + for (j = 0; j < desc_blocks; j++) + if (!test_bit (j + 1, bh)) + ext2_error ("descriptor block #%d in group %d is marked free", + j, i); + } + + if (!block_in_use (gdp->bg_block_bitmap, bh)) + ext2_error ("block bitmap for group %d is marked free", i); + + if (!block_in_use (gdp->bg_inode_bitmap, bh)) + ext2_error ("inode bitmap for group %d is marked free", i); + + for (j = 0; j < itb_per_group; j++) + if (!block_in_use (gdp->bg_inode_table + j, bh)) + ext2_error ("block #%d of the inode table in group %d is marked free", j, i); + + x = count_free (bh, block_size); + if (gdp->bg_free_blocks_count != x) + ext2_error ("wrong free blocks count for group %d," + " stored = %d, counted = %lu", + i, gdp->bg_free_blocks_count, x); + bitmap_count += x; + } + if (sblock->s_free_blocks_count != bitmap_count) + ext2_error ("wrong free blocks count in super block," + " stored = %lu, counted = %lu", + (unsigned long) sblock->s_free_blocks_count, bitmap_count); + spin_unlock (&global_lock); +} |