/* Block allocation routines Copyright (C) 1995,99,2000 Free Software Foundation, Inc. Converted to work under the hurd by Miles Bader 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 #include "ext2fs.h" #include "bitmap.c" /* Returns a pointer to the first occurrence 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; pthread_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); pthread_spin_unlock (&global_lock); return; } ext2_debug ("freeing block %u[%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 = disk_cache_block_ref (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); disk_cache_block_ref_ptr (gdp); record_global_poke (gdp); block += gcount; count -= gcount; } while (count > 0); sblock_dirty = 1; pthread_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 = NULL; 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 pthread_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)))) { pthread_spin_unlock (&global_lock); return 0; } #endif ext2_debug ("goal=%u", goal); repeat: assert (bh == NULL); /* * 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 = disk_cache_block_ref (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 */ if ((j & 31) == 31) lmap = 0; else 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; } disk_cache_block_deref (bh); bh = NULL; } 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) { pthread_spin_unlock (&global_lock); return 0; } assert (bh == NULL); bh = disk_cache_block_ref (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) { disk_cache_block_deref (bh); bh = NULL; ext2_error ("free blocks count corrupted for block group %d", i); pthread_spin_unlock (&global_lock); return 0; } search_back: assert (bh != NULL); /* * 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: assert (bh != NULL); 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); disk_cache_block_deref (bh); bh = NULL; 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) { pthread_spin_lock (&modified_global_blocks_lock); clear_bit (tmp, modified_global_blocks); pthread_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) { pthread_spin_lock (&modified_global_blocks_lock); clear_bit (tmp + k, modified_global_blocks); pthread_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 %u bits", *prealloc_count); } #endif j = tmp; record_global_poke (bh); bh = NULL; 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--; disk_cache_block_ref_ptr (gdp); record_global_poke (gdp); sblock->s_free_blocks_count--; sblock_dirty = 1; sync_out: assert (bh == NULL); pthread_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; pthread_spin_lock (&global_lock); desc_count = 0; bitmap_count = 0; gdp = NULL; for (i = 0; i < groups_count; i++) { void *bh; gdp = group_desc (i); desc_count += gdp->bg_free_blocks_count; bh = disk_cache_block_ref (gdp->bg_block_bitmap); x = count_free (bh, block_size); disk_cache_block_deref (bh); printf ("group %d: stored = %d, counted = %lu", i, gdp->bg_free_blocks_count, x); bitmap_count += x; } printf ("ext2_count_free_blocks: stored = %u, computed = %lu, %lu", sblock->s_free_blocks_count, desc_count, bitmap_count); pthread_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; pthread_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 = disk_cache_block_ref (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); disk_cache_block_deref (bh); 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); pthread_spin_unlock (&global_lock); }