diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1994-11-28 11:59:19 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1994-11-28 11:59:19 +0000 |
commit | 1513ff9b7899ab588401c89db0e99903dbf5f886 (patch) | |
tree | f69cc81a940a502ea23d664c3ffb2d215a479667 /fs/ext2 |
Import of Linus's Linux 1.1.68
Diffstat (limited to 'fs/ext2')
-rw-r--r-- | fs/ext2/CHANGES | 140 | ||||
-rw-r--r-- | fs/ext2/Makefile | 31 | ||||
-rw-r--r-- | fs/ext2/acl.c | 50 | ||||
-rw-r--r-- | fs/ext2/balloc.c | 582 | ||||
-rw-r--r-- | fs/ext2/bitmap.c | 25 | ||||
-rw-r--r-- | fs/ext2/dir.c | 227 | ||||
-rw-r--r-- | fs/ext2/file.c | 354 | ||||
-rw-r--r-- | fs/ext2/fsync.c | 198 | ||||
-rw-r--r-- | fs/ext2/ialloc.c | 554 | ||||
-rw-r--r-- | fs/ext2/inode.c | 667 | ||||
-rw-r--r-- | fs/ext2/ioctl.c | 75 | ||||
-rw-r--r-- | fs/ext2/namei.c | 1098 | ||||
-rw-r--r-- | fs/ext2/super.c | 755 | ||||
-rw-r--r-- | fs/ext2/symlink.c | 127 | ||||
-rw-r--r-- | fs/ext2/truncate.c | 349 |
15 files changed, 5232 insertions, 0 deletions
diff --git a/fs/ext2/CHANGES b/fs/ext2/CHANGES new file mode 100644 index 000000000..b760d18c7 --- /dev/null +++ b/fs/ext2/CHANGES @@ -0,0 +1,140 @@ +Changes from version 0.5 to version 0.5a +======================================== + - Some cleanups in the error messages (some versions of syslog contain + a bug which truncates an error message if it contains '\n'). + - Check that no data can be written to a file past the 2GB limit. + - The famous readdir() bug has been fixed by Stephen Tweedie. + - Added a revision level in the superblock. + - Full support for O_SYNC flag of the open system call. + - New mount options: `resuid=#uid' and `resgid=#gid'. `resuid' causes + ext2fs to consider user #uid like root for the reserved blocks. + `resgid' acts the same way with group #gid. New fields in the + superblock contain default values for resuid and resgid and can + be modified by tune2fs. + Idea comes from Rene Cougnenc <cougnenc@renux.frmug.fr.net>. + - New mount options: `bsddf' and `minixdf'. `bsddf' causes ext2fs + to remove the blocks used for FS structures from the total block + count in statfs. With `minixdf', ext2fs mimics Minix behavior + in statfs (i.e. it returns the total number of blocks on the + partition). This is intended to make bde happy :-) + - New file attributes: + - Immutable files cannot be modified. Data cannot be written to + these files. They cannot be removed, renamed and new links cannot + be created. Even root cannot modify the files. He has to remove + the immutable attribute first. + - Append-only files: can only be written in append-mode when writing. + They cannot be removed, renamed and new links cannot be created. + Note: files may only be added to an append-only directory. + - No-dump files: the attribute is not used by the kernel. My port + of dump uses it to avoid backing up files which are not important. + - New check in ext2_check_dir_entry: the inode number is checked. + - Support for big file systems: the copy of the FS descriptor is now + dynamically allocated (previous versions used a fixed size array). + This allows to mount 2GB+ FS. + - Reorganization of the ext2_inode structure to allow other operating + systems to create specific fields if they use ext2fs as their native + file system. Currently, ext2fs is only implemented in Linux but + will soon be part of Gnu Hurd and of Masix. + +Changes from version 0.4b to version 0.5 +======================================== + - New superblock fields: s_lastcheck and s_checkinterval added + by Uwe Ohse <uwe@tirka.gun.de> to implement timedependent checks + of the file system + - Real random numbers for secure rm added by Pierre del Perugia + <delperug@gla.ecoledoc.ibp.fr> + - The mount warnings related to the state of a fs are not printed + if the fs is mounted read-only, idea by Nick Holloway + <alfie@dcs.warwick.ac.uk> + +Changes from version 0.4a to version 0.4b +========================================= + - Copyrights changed to include the name of my laboratory. + - Clean up of balloc.c and ialloc.c. + - More consistency checks. + - Block preallocation added by Stephen Tweedie. + - Direct reads of directories disallowed. + - Readahead implemented in readdir by Stephen Tweedie. + - Bugs in block and inodes allocation fixed. + - Readahead implemented in ext2_find_entry by Chip Salzenberg. + - New mount options: + `check=none|normal|strict' + `debug' + `errors=continue|remount-ro|panic' + `grpid', `bsdgroups' + `nocheck' + `nogrpid', `sysvgroups' + - truncate() now tries to deallocate contiguous blocks in a single call + to ext2_free_blocks(). + - lots of cosmetic changes. + +Changes from version 0.4 to version 0.4a +======================================== + - the `sync' option support is now complete. Version 0.4 was not + supporting it when truncating a file. I have tested the synchronous + writes and they work but they make the system very slow :-( I have + to work again on this to make it faster. + - when detecting an error on a mounted filesystem, version 0.4 used + to try to write a flag in the super block even if the filesystem had + been mounted read-only. This is fixed. + - the `sb=#' option now causes the kernel code to use the filesystem + descriptors located at block #+1. Version 0.4 used the superblock + backup located at block # but used the main copy of the descriptors. + - a new file attribute `S' is supported. This attribute causes + synchronous writes but is applied to a file not to the entire file + system (thanks to Michael Kraehe <kraehe@bakunin.north.de> for + suggesting it). + - the directory cache is inhibited by default. The cache management + code seems to be buggy and I have to look at it carefully before + using it again. + - deleting a file with the `s' attribute (secure deletion) causes its + blocks to be overwritten with random values not with zeros (thanks to + Michael A. Griffith <grif@cs.ucr.edu> for suggesting it). + - lots of cosmetic changes have been made. + +Changes from version 0.3 to version 0.4 +======================================= + - Three new mount options are supported: `check', `sync' and `sb=#'. + `check' tells the kernel code to make more consistency checks + when the file system is mounted. Currently, the kernel code checks + that the blocks and inodes bitmaps are consistent with the free + blocks and inodes counts. More checks will be added in future + releases. + `sync' tells the kernel code to use synchronous writes when updating + an inode, a bitmap, a directory entry or an indirect block. This + can make the file system much slower but can be a big win for files + recovery in case of a crash (and we can now say to the BSD folks + that Linux also supports synchronous updates :-). + `sb=#' tells the kernel code to use an alternate super block instead + of its master copy. `#' is the number of the block (counted in + 1024 bytes blocks) which contains the alternate super block. + An ext2 file system typically contains backups of the super block + at blocks 8193, 16385, and so on. + - I have change the meaning of the valid flag used by e2fsck. it + now contains the state of the file system. If the kernel code + detects an inconsistency while the file system is mounted, it flags + it as erroneous and e2fsck will detect that on next run. + - The super block now contains a mount counter. This counter is + incremented each time the file system is mounted read/write. When + this counter becomes bigger than a maximal mount counts (also stored + in the super block), e2fsck checks the file system, even if it had + been unmounted cleanly, and resets this counter to 0. + - File attributes are now supported. One can associate a set of + attributes to a file. Three attributes are defined: + `c': the file is marked for automatic compression, + `s': the file is marked for secure deletion: when the file is + deleted, its blocks are zeroed and written back to the disk, + `u': the file is marked for undeletion: when the file is deleted, + its contents are saved to allow a future undeletion. + Currently, only the `s' attribute is implemented in the kernel + code. Support for the other attributes will be added in a future + release. + - a few bugs related to times updates have been fixed by Bruce + Evans and me. + - a bug related to the links count of deleted inodes has been fixed. + Previous versions used to keep the links count set to 1 when a file + was deleted. The new version now sets links_count to 0 when deleting + the last link. + - a race condition when deallocating an inode has been fixed by + Stephen Tweedie. + diff --git a/fs/ext2/Makefile b/fs/ext2/Makefile new file mode 100644 index 000000000..599f2ad8f --- /dev/null +++ b/fs/ext2/Makefile @@ -0,0 +1,31 @@ +# +# Makefile for the linux ext2-filesystem routines. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile... + +.c.s: + $(CC) $(CFLAGS) -S $< +.c.o: + $(CC) $(CFLAGS) -c $< +.s.o: + $(AS) -o $*.o $< + +OBJS= acl.o balloc.o bitmap.o dir.o file.o fsync.o ialloc.o \ + inode.o ioctl.o namei.o super.o symlink.o truncate.o + +ext2.o: $(OBJS) + $(LD) -r -o ext2.o $(OBJS) + +dep: + $(CPP) -M *.c > .depend + +# +# include a dependency file if one exists +# +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/fs/ext2/acl.c b/fs/ext2/acl.c new file mode 100644 index 000000000..91ef7c8cc --- /dev/null +++ b/fs/ext2/acl.c @@ -0,0 +1,50 @@ +/* + * linux/fs/ext2/acl.c + * + * Copyright (C) 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + */ + +/* + * This file will contain the Access Control Lists management for the + * second extended file system. + */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/sched.h> +#include <linux/stat.h> + +/* + * ext2_permission () + * + * Check for access rights + */ +int ext2_permission (struct inode * inode, int mask) +{ + unsigned short mode = inode->i_mode; + + /* + * Nobody gets write access to an immutable file + */ + if ((mask & S_IWOTH) && IS_IMMUTABLE(inode)) + return 0; + /* + * Special case, access is always granted for root + */ + if (fsuser()) + return 1; + /* + * If no ACL, checks using the file mode + */ + else if (current->fsuid == inode->i_uid) + mode >>= 6; + else if (in_group_p (inode->i_gid)) + mode >>= 3; + if (((mode & mask & S_IRWXO) == mask)) + return 1; + else + return 0; +} diff --git a/fs/ext2/balloc.c b/fs/ext2/balloc.c new file mode 100644 index 000000000..bc6faa7ed --- /dev/null +++ b/fs/ext2/balloc.c @@ -0,0 +1,582 @@ +/* + * linux/fs/ext2/balloc.c + * + * Copyright (C) 1992, 1993, 1994 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 + */ + +/* + * balloc.c contains the blocks allocation and deallocation routines + */ + +/* + * 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 <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/stat.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/locks.h> + +#include <asm/bitops.h> + +#define in_range(b, first, len) ((b) >= (first) && (b) <= (first) + (len) - 1) + +static struct ext2_group_desc * get_group_desc (struct super_block * sb, + unsigned int block_group, + struct buffer_head ** bh) +{ + unsigned long group_desc; + unsigned long desc; + struct ext2_group_desc * gdp; + + if (block_group >= sb->u.ext2_sb.s_groups_count) + ext2_panic (sb, "get_group_desc", + "block_group >= groups_count - " + "block_group = %d, groups_count = %lu", + block_group, sb->u.ext2_sb.s_groups_count); + + group_desc = block_group / EXT2_DESC_PER_BLOCK(sb); + desc = block_group % EXT2_DESC_PER_BLOCK(sb); + if (!sb->u.ext2_sb.s_group_desc[group_desc]) + ext2_panic (sb, "get_group_desc", + "Group descriptor not loaded - " + "block_group = %d, group_desc = %lu, desc = %lu", + block_group, group_desc, desc); + gdp = (struct ext2_group_desc *) + sb->u.ext2_sb.s_group_desc[group_desc]->b_data; + if (bh) + *bh = sb->u.ext2_sb.s_group_desc[group_desc]; + return gdp + desc; +} + +static void read_block_bitmap (struct super_block * sb, + unsigned int block_group, + unsigned long bitmap_nr) +{ + struct ext2_group_desc * gdp; + struct buffer_head * bh; + + gdp = get_group_desc (sb, block_group, NULL); + bh = bread (sb->s_dev, gdp->bg_block_bitmap, sb->s_blocksize); + if (!bh) + ext2_panic (sb, "read_block_bitmap", + "Cannot read block bitmap - " + "block_group = %d, block_bitmap = %lu", + block_group, gdp->bg_block_bitmap); + sb->u.ext2_sb.s_block_bitmap_number[bitmap_nr] = block_group; + sb->u.ext2_sb.s_block_bitmap[bitmap_nr] = bh; +} + +/* + * load_block_bitmap loads the block bitmap for a blocks group + * + * It maintains a cache for the last bitmaps loaded. This cache is managed + * with a LRU algorithm. + * + * Notes: + * 1/ There is one cache per mounted file system. + * 2/ If the file system contains less than EXT2_MAX_GROUP_LOADED groups, + * this function reads the bitmap without maintaining a LRU cache. + */ +static int load__block_bitmap (struct super_block * sb, + unsigned int block_group) +{ + int i, j; + unsigned long block_bitmap_number; + struct buffer_head * block_bitmap; + + if (block_group >= sb->u.ext2_sb.s_groups_count) + ext2_panic (sb, "load_block_bitmap", + "block_group >= groups_count - " + "block_group = %d, groups_count = %lu", + block_group, sb->u.ext2_sb.s_groups_count); + + if (sb->u.ext2_sb.s_groups_count <= EXT2_MAX_GROUP_LOADED) { + if (sb->u.ext2_sb.s_block_bitmap[block_group]) { + if (sb->u.ext2_sb.s_block_bitmap_number[block_group] != + block_group) + ext2_panic (sb, "load_block_bitmap", + "block_group != block_bitmap_number"); + else + return block_group; + } else { + read_block_bitmap (sb, block_group, block_group); + return block_group; + } + } + + for (i = 0; i < sb->u.ext2_sb.s_loaded_block_bitmaps && + sb->u.ext2_sb.s_block_bitmap_number[i] != block_group; i++) + ; + if (i < sb->u.ext2_sb.s_loaded_block_bitmaps && + sb->u.ext2_sb.s_block_bitmap_number[i] == block_group) { + block_bitmap_number = sb->u.ext2_sb.s_block_bitmap_number[i]; + block_bitmap = sb->u.ext2_sb.s_block_bitmap[i]; + for (j = i; j > 0; j--) { + sb->u.ext2_sb.s_block_bitmap_number[j] = + sb->u.ext2_sb.s_block_bitmap_number[j - 1]; + sb->u.ext2_sb.s_block_bitmap[j] = + sb->u.ext2_sb.s_block_bitmap[j - 1]; + } + sb->u.ext2_sb.s_block_bitmap_number[0] = block_bitmap_number; + sb->u.ext2_sb.s_block_bitmap[0] = block_bitmap; + } else { + if (sb->u.ext2_sb.s_loaded_block_bitmaps < EXT2_MAX_GROUP_LOADED) + sb->u.ext2_sb.s_loaded_block_bitmaps++; + else + brelse (sb->u.ext2_sb.s_block_bitmap[EXT2_MAX_GROUP_LOADED - 1]); + for (j = sb->u.ext2_sb.s_loaded_block_bitmaps - 1; j > 0; j--) { + sb->u.ext2_sb.s_block_bitmap_number[j] = + sb->u.ext2_sb.s_block_bitmap_number[j - 1]; + sb->u.ext2_sb.s_block_bitmap[j] = + sb->u.ext2_sb.s_block_bitmap[j - 1]; + } + read_block_bitmap (sb, block_group, 0); + } + return 0; +} + +static inline int load_block_bitmap (struct super_block * sb, + unsigned int block_group) +{ + if (sb->u.ext2_sb.s_loaded_block_bitmaps > 0 && + sb->u.ext2_sb.s_block_bitmap_number[0] == block_group) + return 0; + + if (sb->u.ext2_sb.s_groups_count <= EXT2_MAX_GROUP_LOADED && + sb->u.ext2_sb.s_block_bitmap_number[block_group] == block_group && + sb->u.ext2_sb.s_block_bitmap[block_group]) + return block_group; + + return load__block_bitmap (sb, block_group); +} + +void ext2_free_blocks (struct super_block * sb, unsigned long block, + unsigned long count) +{ + struct buffer_head * bh; + struct buffer_head * bh2; + unsigned long block_group; + unsigned long bit; + unsigned long i; + int bitmap_nr; + struct ext2_group_desc * gdp; + struct ext2_super_block * es; + + if (!sb) { + printk ("ext2_free_blocks: nonexistent device"); + return; + } + lock_super (sb); + es = sb->u.ext2_sb.s_es; + if (block < es->s_first_data_block || + (block + count) > es->s_blocks_count) { + ext2_error (sb, "ext2_free_blocks", + "Freeing blocks not in datazone - " + "block = %lu, count = %lu", block, count); + unlock_super (sb); + return; + } + + ext2_debug ("freeing block %lu\n", block); + + block_group = (block - es->s_first_data_block) / + EXT2_BLOCKS_PER_GROUP(sb); + bit = (block - es->s_first_data_block) % EXT2_BLOCKS_PER_GROUP(sb); + if (bit + count > EXT2_BLOCKS_PER_GROUP(sb)) + ext2_panic (sb, "ext2_free_blocks", + "Freeing blocks across group boundary - " + "Block = %lu, count = %lu", + block, count); + bitmap_nr = load_block_bitmap (sb, block_group); + bh = sb->u.ext2_sb.s_block_bitmap[bitmap_nr]; + gdp = get_group_desc (sb, block_group, &bh2); + + if (test_opt (sb, CHECK_STRICT) && + (in_range (gdp->bg_block_bitmap, block, count) || + in_range (gdp->bg_inode_bitmap, block, count) || + in_range (block, gdp->bg_inode_table, + sb->u.ext2_sb.s_itb_per_group) || + in_range (block + count - 1, gdp->bg_inode_table, + sb->u.ext2_sb.s_itb_per_group))) + ext2_panic (sb, "ext2_free_blocks", + "Freeing blocks in system zones - " + "Block = %lu, count = %lu", + block, count); + + for (i = 0; i < count; i++) { + if (!clear_bit (bit + i, bh->b_data)) + ext2_warning (sb, "ext2_free_blocks", + "bit already cleared for block %lu", + block); + else { + gdp->bg_free_blocks_count++; + es->s_free_blocks_count++; + } + } + + mark_buffer_dirty(bh2, 1); + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + + mark_buffer_dirty(bh, 1); + if (sb->s_flags & MS_SYNC) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + sb->s_dirt = 1; + unlock_super (sb); + return; +} + +/* + * 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. + */ +int ext2_new_block (struct super_block * sb, unsigned long goal, + unsigned long * prealloc_count, + unsigned long * prealloc_block) +{ + struct buffer_head * bh; + struct buffer_head * bh2; + char * p, * r; + int i, j, k, tmp; + unsigned long lmap; + int bitmap_nr; + struct ext2_group_desc * gdp; + struct ext2_super_block * es; + +#ifdef EXT2FS_DEBUG + static int goal_hits = 0, goal_attempts = 0; +#endif + if (!sb) { + printk ("ext2_new_block: nonexistent device"); + return 0; + } + lock_super (sb); + es = sb->u.ext2_sb.s_es; + if (es->s_free_blocks_count <= es->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)))) { + unlock_super (sb); + return 0; + } + + ext2_debug ("goal=%lu.\n", goal); + +repeat: + /* + * First, test whether the goal block is free. + */ + if (goal < es->s_first_data_block || goal >= es->s_blocks_count) + goal = es->s_first_data_block; + i = (goal - es->s_first_data_block) / EXT2_BLOCKS_PER_GROUP(sb); + gdp = get_group_desc (sb, i, &bh2); + if (gdp->bg_free_blocks_count > 0) { + j = ((goal - es->s_first_data_block) % EXT2_BLOCKS_PER_GROUP(sb)); +#ifdef EXT2FS_DEBUG + if (j) + goal_attempts++; +#endif + bitmap_nr = load_block_bitmap (sb, i); + bh = sb->u.ext2_sb.s_block_bitmap[bitmap_nr]; + + ext2_debug ("goal is at %d:%d.\n", i, j); + + if (!test_bit(j, bh->b_data)) { +#ifdef EXT2FS_DEBUG + goal_hits++; + ext2_debug ("goal bit allocated.\n"); +#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->b_data)[j >> 5]) >> + ((j & 31) + 1)); + if (j < EXT2_BLOCKS_PER_GROUP(sb) - 32) + lmap |= (((unsigned long *) bh->b_data)[(j >> 5) + 1]) << + (31 - (j & 31)); + else + lmap |= 0xffffffff << (31 - (j & 31)); + if (lmap != 0xffffffffl) { + k = ffz(lmap) + 1; + if ((j + k) < EXT2_BLOCKS_PER_GROUP(sb)) { + j += k; + goto got_block; + } + } + } + + ext2_debug ("Bit not found near goal\n"); + + /* + * 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->b_data) + (j >> 3); + r = memscan(p, 0, (EXT2_BLOCKS_PER_GROUP(sb) - j + 7) >> 3); + k = (r - ((char *) bh->b_data)) << 3; + if (k < EXT2_BLOCKS_PER_GROUP(sb)) { + j = k; + goto search_back; + } + k = find_next_zero_bit ((unsigned long *) bh->b_data, + EXT2_BLOCKS_PER_GROUP(sb), + j); + if (k < EXT2_BLOCKS_PER_GROUP(sb)) { + j = k; + goto got_block; + } + } + + ext2_debug ("Bit not found in block group %d.\n", 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 < sb->u.ext2_sb.s_groups_count; k++) { + i++; + if (i >= sb->u.ext2_sb.s_groups_count) + i = 0; + gdp = get_group_desc (sb, i, &bh2); + if (gdp->bg_free_blocks_count > 0) + break; + } + if (k >= sb->u.ext2_sb.s_groups_count) { + unlock_super (sb); + return 0; + } + bitmap_nr = load_block_bitmap (sb, i); + bh = sb->u.ext2_sb.s_block_bitmap[bitmap_nr]; + r = memscan(bh->b_data, 0, EXT2_BLOCKS_PER_GROUP(sb) >> 3); + j = (r - bh->b_data) << 3; + if (j < EXT2_BLOCKS_PER_GROUP(sb)) + goto search_back; + else + j = find_first_zero_bit ((unsigned long *) bh->b_data, + EXT2_BLOCKS_PER_GROUP(sb)); + if (j >= EXT2_BLOCKS_PER_GROUP(sb)) { + ext2_error (sb, "ext2_new_block", + "Free blocks count corrupted for block group %d", i); + unlock_super (sb); + 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->b_data); k++, j--); + +got_block: + + ext2_debug ("using block group %d(%d)\n", i, gdp->bg_free_blocks_count); + + tmp = j + i * EXT2_BLOCKS_PER_GROUP(sb) + es->s_first_data_block; + + if (test_opt (sb, CHECK_STRICT) && + (tmp == gdp->bg_block_bitmap || + tmp == gdp->bg_inode_bitmap || + in_range (tmp, gdp->bg_inode_table, sb->u.ext2_sb.s_itb_per_group))) + ext2_panic (sb, "ext2_new_block", + "Allocating block in system zone - " + "block = %u", tmp); + + if (set_bit (j, bh->b_data)) { + ext2_warning (sb, "ext2_new_block", + "bit already set for block %d", j); + goto repeat; + } + + ext2_debug ("found bit %d\n", j); + + /* + * Do block preallocation now if required. + */ +#ifdef EXT2_PREALLOCATE + if (prealloc_block) { + *prealloc_count = 0; + *prealloc_block = tmp + 1; + for (k = 1; + k < 8 && (j + k) < EXT2_BLOCKS_PER_GROUP(sb); k++) { + if (set_bit (j + k, bh->b_data)) + break; + (*prealloc_count)++; + } + gdp->bg_free_blocks_count -= *prealloc_count; + es->s_free_blocks_count -= *prealloc_count; + ext2_debug ("Preallocated a further %lu bits.\n", + *prealloc_count); + } +#endif + + j = tmp; + + mark_buffer_dirty(bh, 1); + if (sb->s_flags & MS_SYNC) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + + if (j >= es->s_blocks_count) { + ext2_error (sb, "ext2_new_block", + "block >= blocks count - " + "block_group = %d, block=%d", i, j); + unlock_super (sb); + return 0; + } + if (!(bh = getblk (sb->s_dev, j, sb->s_blocksize))) { + ext2_error (sb, "ext2_new_block", "cannot get block %d", j); + unlock_super (sb); + return 0; + } + memset(bh->b_data, 0, sb->s_blocksize); + bh->b_uptodate = 1; + mark_buffer_dirty(bh, 1); + brelse (bh); + + ext2_debug ("allocating block %d. " + "Goal hits %d of %d.\n", j, goal_hits, goal_attempts); + + gdp->bg_free_blocks_count--; + mark_buffer_dirty(bh2, 1); + es->s_free_blocks_count--; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + sb->s_dirt = 1; + unlock_super (sb); + return j; +} + +unsigned long ext2_count_free_blocks (struct super_block * sb) +{ +#ifdef EXT2FS_DEBUG + struct ext2_super_block * es; + unsigned long desc_count, bitmap_count, x; + int bitmap_nr; + struct ext2_group_desc * gdp; + int i; + + lock_super (sb); + es = sb->u.ext2_sb.s_es; + desc_count = 0; + bitmap_count = 0; + gdp = NULL; + for (i = 0; i < sb->u.ext2_sb.s_groups_count; i++) { + gdp = get_group_desc (sb, i, NULL); + desc_count += gdp->bg_free_blocks_count; + bitmap_nr = load_block_bitmap (sb, i); + x = ext2_count_free (sb->u.ext2_sb.s_block_bitmap[bitmap_nr], + sb->s_blocksize); + printk ("group %d: stored = %d, counted = %lu\n", + i, gdp->bg_free_blocks_count, x); + bitmap_count += x; + } + printk("ext2_count_free_blocks: stored = %lu, computed = %lu, %lu\n", + es->s_free_blocks_count, desc_count, bitmap_count); + unlock_super (sb); + return bitmap_count; +#else + return sb->u.ext2_sb.s_es->s_free_blocks_count; +#endif +} + +static inline int block_in_use (unsigned long block, + struct super_block * sb, + unsigned char * map) +{ + return test_bit ((block - sb->u.ext2_sb.s_es->s_first_data_block) % + EXT2_BLOCKS_PER_GROUP(sb), map); +} + +void ext2_check_blocks_bitmap (struct super_block * sb) +{ + struct buffer_head * bh; + struct ext2_super_block * es; + unsigned long desc_count, bitmap_count, x; + unsigned long desc_blocks; + int bitmap_nr; + struct ext2_group_desc * gdp; + int i, j; + + lock_super (sb); + es = sb->u.ext2_sb.s_es; + desc_count = 0; + bitmap_count = 0; + gdp = NULL; + desc_blocks = (sb->u.ext2_sb.s_groups_count + EXT2_DESC_PER_BLOCK(sb) - 1) / + EXT2_DESC_PER_BLOCK(sb); + for (i = 0; i < sb->u.ext2_sb.s_groups_count; i++) { + gdp = get_group_desc (sb, i, NULL); + desc_count += gdp->bg_free_blocks_count; + bitmap_nr = load_block_bitmap (sb, i); + bh = sb->u.ext2_sb.s_block_bitmap[bitmap_nr]; + + if (!test_bit (0, bh->b_data)) + ext2_error (sb, "ext2_check_blocks_bitmap", + "Superblock in group %d is marked free", i); + + for (j = 0; j < desc_blocks; j++) + if (!test_bit (j + 1, bh->b_data)) + ext2_error (sb, "ext2_check_blocks_bitmap", + "Descriptor block #%d in group " + "%d is marked free", j, i); + + if (!block_in_use (gdp->bg_block_bitmap, sb, bh->b_data)) + ext2_error (sb, "ext2_check_blocks_bitmap", + "Block bitmap for group %d is marked free", + i); + + if (!block_in_use (gdp->bg_inode_bitmap, sb, bh->b_data)) + ext2_error (sb, "ext2_check_blocks_bitmap", + "Inode bitmap for group %d is marked free", + i); + + for (j = 0; j < sb->u.ext2_sb.s_itb_per_group; j++) + if (!block_in_use (gdp->bg_inode_table + j, sb, bh->b_data)) + ext2_error (sb, "ext2_check_blocks_bitmap", + "Block #%d of the inode table in " + "group %d is marked free", j, i); + + x = ext2_count_free (bh, sb->s_blocksize); + if (gdp->bg_free_blocks_count != x) + ext2_error (sb, "ext2_check_blocks_bitmap", + "Wrong free blocks count for group %d, " + "stored = %d, counted = %lu", i, + gdp->bg_free_blocks_count, x); + bitmap_count += x; + } + if (es->s_free_blocks_count != bitmap_count) + ext2_error (sb, "ext2_check_blocks_bitmap", + "Wrong free blocks count in super block, " + "stored = %lu, counted = %lu", + es->s_free_blocks_count, bitmap_count); + unlock_super (sb); +} diff --git a/fs/ext2/bitmap.c b/fs/ext2/bitmap.c new file mode 100644 index 000000000..1084da16d --- /dev/null +++ b/fs/ext2/bitmap.c @@ -0,0 +1,25 @@ +/* + * linux/fs/ext2/bitmap.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + */ + +#include <linux/fs.h> +#include <linux/ext2_fs.h> + +static int nibblemap[] = {4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0}; + +unsigned long ext2_count_free (struct buffer_head * map, unsigned int numchars) +{ + unsigned int i; + unsigned long sum = 0; + + if (!map) + return (0); + for (i = 0; i < numchars; i++) + sum += nibblemap[map->b_data[i] & 0xf] + + nibblemap[(map->b_data[i] >> 4) & 0xf]; + return (sum); +} diff --git a/fs/ext2/dir.c b/fs/ext2/dir.c new file mode 100644 index 000000000..c98139bc6 --- /dev/null +++ b/fs/ext2/dir.c @@ -0,0 +1,227 @@ +/* + * linux/fs/ext2/dir.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/fs/minix/dir.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * ext2 directory handling functions + */ + +#include <asm/segment.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/sched.h> +#include <linux/stat.h> + +#define NAME_OFFSET(de) ((int) ((de)->d_name - (char *) (de))) +#define ROUND_UP(x) (((x)+3) & ~3) + +static int ext2_dir_read (struct inode * inode, struct file * filp, + char * buf, int count) +{ + return -EISDIR; +} + +static int ext2_readdir (struct inode *, struct file *, struct dirent *, int); + +static struct file_operations ext2_dir_operations = { + NULL, /* lseek - default */ + ext2_dir_read, /* read */ + NULL, /* write - bad */ + ext2_readdir, /* readdir */ + NULL, /* select - default */ + ext2_ioctl, /* ioctl */ + NULL, /* mmap */ + NULL, /* no special open code */ + NULL, /* no special release code */ + file_fsync, /* fsync */ + NULL, /* fasync */ + NULL, /* check_media_change */ + NULL /* revalidate */ +}; + +/* + * directories can handle most operations... + */ +struct inode_operations ext2_dir_inode_operations = { + &ext2_dir_operations, /* default directory file-ops */ + ext2_create, /* create */ + ext2_lookup, /* lookup */ + ext2_link, /* link */ + ext2_unlink, /* unlink */ + ext2_symlink, /* symlink */ + ext2_mkdir, /* mkdir */ + ext2_rmdir, /* rmdir */ + ext2_mknod, /* mknod */ + ext2_rename, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* bmap */ + ext2_truncate, /* truncate */ + ext2_permission, /* permission */ + NULL /* smap */ +}; + +int ext2_check_dir_entry (char * function, struct inode * dir, + struct ext2_dir_entry * de, struct buffer_head * bh, + unsigned long offset) +{ + char * error_msg = NULL; + + if (de->rec_len < EXT2_DIR_REC_LEN(1)) + error_msg = "rec_len is smaller than minimal"; + else if (de->rec_len % 4 != 0) + error_msg = "rec_len % 4 != 0"; + else if (de->rec_len < EXT2_DIR_REC_LEN(de->name_len)) + error_msg = "rec_len is too small for name_len"; + else if (dir && ((char *) de - bh->b_data) + de->rec_len > + dir->i_sb->s_blocksize) + error_msg = "directory entry across blocks"; + else if (dir && de->inode > dir->i_sb->u.ext2_sb.s_es->s_inodes_count) + error_msg = "inode out of bounds"; + + if (error_msg != NULL) + ext2_error (dir->i_sb, function, "bad directory entry: %s\n" + "offset=%lu, inode=%lu, rec_len=%d, name_len=%d", + error_msg, offset, de->inode, de->rec_len, + de->name_len); + return error_msg == NULL ? 1 : 0; +} + +static int ext2_readdir (struct inode * inode, struct file * filp, + struct dirent * dirent, int count) +{ + unsigned long offset, blk; + int i, num, stored, dlen; + struct buffer_head * bh, * tmp, * bha[16]; + struct ext2_dir_entry * de; + struct super_block * sb; + int err, version; + + if (!inode || !S_ISDIR(inode->i_mode)) + return -EBADF; + sb = inode->i_sb; + + stored = 0; + bh = NULL; + offset = filp->f_pos & (sb->s_blocksize - 1); + + while (count > 0 && !stored && filp->f_pos < inode->i_size) { + blk = (filp->f_pos) >> EXT2_BLOCK_SIZE_BITS(sb); + bh = ext2_bread (inode, blk, 0, &err); + if (!bh) { + filp->f_pos += sb->s_blocksize - offset; + continue; + } + + /* + * Do the readahead + */ + if (!offset) { + for (i = 16 >> (EXT2_BLOCK_SIZE_BITS(sb) - 9), num = 0; + i > 0; i--) { + tmp = ext2_getblk (inode, ++blk, 0, &err); + if (tmp && !tmp->b_uptodate && !tmp->b_lock) + bha[num++] = tmp; + else + brelse (tmp); + } + if (num) { + ll_rw_block (READA, num, bha); + for (i = 0; i < num; i++) + brelse (bha[i]); + } + } + +revalidate: + /* If the dir block has changed since the last call to + * readdir(2), then we might be pointing to an invalid + * dirent right now. Scan from the start of the block + * to make sure. */ + if (filp->f_version != inode->i_version) { + for (i = 0; i < sb->s_blocksize && i < offset; ) { + de = (struct ext2_dir_entry *) + (bh->b_data + i); + /* It's too expensive to do a full + * dirent test each time round this + * loop, but we do have to test at + * least that it is non-zero. A + * failure will be detected in the + * dirent test below. */ + if (de->rec_len < EXT2_DIR_REC_LEN(1)) + break; + i += de->rec_len; + } + offset = i; + filp->f_pos = (filp->f_pos & ~(sb->s_blocksize - 1)) + | offset; + filp->f_version = inode->i_version; + } + + while (count > 0 && filp->f_pos < inode->i_size + && offset < sb->s_blocksize) { + de = (struct ext2_dir_entry *) (bh->b_data + offset); + if (!ext2_check_dir_entry ("ext2_readdir", inode, de, + bh, offset)) { + /* On error, skip the f_pos to the + next block. */ + filp->f_pos = (filp->f_pos & (sb->s_blocksize - 1)) + + sb->s_blocksize; + brelse (bh); + return stored; + } + if (de->inode) { + dlen = ROUND_UP(NAME_OFFSET(dirent) + + de->name_len + 1); + /* Old libc libraries always use a + count of 1. */ + if (count == 1 && !stored) + count = dlen; + if (count < dlen) { + count = 0; + break; + } + + /* We might block in the next section + * if the data destination is + * currently swapped out. So, use a + * version stamp to detect whether or + * not the directory has been modified + * during the copy operation. */ + version = inode->i_version; + i = de->name_len; + memcpy_tofs (dirent->d_name, de->name, i); + put_fs_long (de->inode, &dirent->d_ino); + put_fs_byte (0, dirent->d_name + i); + put_fs_word (i, &dirent->d_reclen); + put_fs_long (dlen, &dirent->d_off); + if (version != inode->i_version) + goto revalidate; + dcache_add(inode, de->name, de->name_len, + de->inode); + + stored += dlen; + count -= dlen; + ((char *) dirent) += dlen; + } + offset += de->rec_len; + filp->f_pos += de->rec_len; + } + offset = 0; + brelse (bh); + } + if (!IS_RDONLY(inode)) { + inode->i_atime = CURRENT_TIME; + inode->i_dirt = 1; + } + return stored; +} diff --git a/fs/ext2/file.c b/fs/ext2/file.c new file mode 100644 index 000000000..20628b349 --- /dev/null +++ b/fs/ext2/file.c @@ -0,0 +1,354 @@ +/* + * linux/fs/ext2/file.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/fs/minix/file.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * ext2 fs regular file handling primitives + */ + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/locks.h> + +#define NBUF 32 + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +#include <linux/fs.h> +#include <linux/ext2_fs.h> + +static int ext2_file_read (struct inode *, struct file *, char *, int); +static int ext2_file_write (struct inode *, struct file *, char *, int); +static void ext2_release_file (struct inode *, struct file *); + +/* + * We have mostly NULL's here: the current defaults are ok for + * the ext2 filesystem. + */ +static struct file_operations ext2_file_operations = { + NULL, /* lseek - default */ + ext2_file_read, /* read */ + ext2_file_write, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + ext2_ioctl, /* ioctl */ + generic_mmap, /* mmap */ + NULL, /* no special open is needed */ + ext2_release_file, /* release */ + ext2_sync_file, /* fsync */ + NULL, /* fasync */ + NULL, /* check_media_change */ + NULL /* revalidate */ +}; + +struct inode_operations ext2_file_inode_operations = { + &ext2_file_operations,/* default file operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + ext2_bmap, /* bmap */ + ext2_truncate, /* truncate */ + ext2_permission, /* permission */ + NULL /* smap */ +}; + +static int ext2_file_read (struct inode * inode, struct file * filp, + char * buf, int count) +{ + int read, left, chars; + int block, blocks, offset; + int bhrequest, uptodate; + int clusterblocks; + struct buffer_head ** bhb, ** bhe; + struct buffer_head * bhreq[NBUF]; + struct buffer_head * buflist[NBUF]; + struct super_block * sb; + unsigned int size; + int err; + + if (!inode) { + printk ("ext2_file_read: inode = NULL\n"); + return -EINVAL; + } + sb = inode->i_sb; + if (!S_ISREG(inode->i_mode)) { + ext2_warning (sb, "ext2_file_read", "mode = %07o", + inode->i_mode); + return -EINVAL; + } + offset = filp->f_pos; + size = inode->i_size; + if (offset > size) + left = 0; + else + left = size - offset; + if (left > count) + left = count; + if (left <= 0) + return 0; + read = 0; + block = offset >> EXT2_BLOCK_SIZE_BITS(sb); + offset &= (sb->s_blocksize - 1); + size = (size + sb->s_blocksize - 1) >> EXT2_BLOCK_SIZE_BITS(sb); + blocks = (left + offset + sb->s_blocksize - 1) >> EXT2_BLOCK_SIZE_BITS(sb); + bhb = bhe = buflist; + if (filp->f_reada) { + if (blocks < read_ahead[MAJOR(inode->i_dev)] >> (EXT2_BLOCK_SIZE_BITS(sb) - 9)) + blocks = read_ahead[MAJOR(inode->i_dev)] >> (EXT2_BLOCK_SIZE_BITS(sb) - 9); + if (block + blocks > size) + blocks = size - block; + } + + /* + * We do this in a two stage process. We first try and request + * as many blocks as we can, then we wait for the first one to + * complete, and then we try and wrap up as many as are actually + * done. This routine is rather generic, in that it can be used + * in a filesystem by substituting the appropriate function in + * for getblk + * + * This routine is optimized to make maximum use of the various + * buffers and caches. + */ + + clusterblocks = 0; + + do { + bhrequest = 0; + uptodate = 1; + while (blocks) { + --blocks; +#if 1 + if(!clusterblocks) clusterblocks = ext2_getcluster(inode, block); + if(clusterblocks) clusterblocks--; +#endif + + *bhb = ext2_getblk (inode, block++, 0, &err); + if (*bhb && !(*bhb)->b_uptodate) { + uptodate = 0; + bhreq[bhrequest++] = *bhb; + } + + if (++bhb == &buflist[NBUF]) + bhb = buflist; + + /* + * If the block we have on hand is uptodate, go ahead + * and complete processing + */ + if (uptodate) + break; + + if (bhb == bhe) + break; + } + + /* + * Now request them all + */ + if (bhrequest) + ll_rw_block (READ, bhrequest, bhreq); + + do { + /* + * Finish off all I/O that has actually completed + */ + if (*bhe) { + wait_on_buffer (*bhe); + if (!(*bhe)->b_uptodate) { /* read error? */ + brelse(*bhe); + if (++bhe == &buflist[NBUF]) + bhe = buflist; + left = 0; + break; + } + } + if (left < sb->s_blocksize - offset) + chars = left; + else + chars = sb->s_blocksize - offset; + filp->f_pos += chars; + left -= chars; + read += chars; + if (*bhe) { + memcpy_tofs (buf, offset + (*bhe)->b_data, + chars); + brelse (*bhe); + buf += chars; + } else { + while (chars-- > 0) + put_fs_byte (0, buf++); + } + offset = 0; + if (++bhe == &buflist[NBUF]) + bhe = buflist; + } while (left > 0 && bhe != bhb && (!*bhe || !(*bhe)->b_lock)); + } while (left > 0); + + /* + * Release the read-ahead blocks + */ + while (bhe != bhb) { + brelse (*bhe); + if (++bhe == &buflist[NBUF]) + bhe = buflist; + } + if (!read) + return -EIO; + filp->f_reada = 1; + if (!IS_RDONLY(inode)) { + inode->i_atime = CURRENT_TIME; + inode->i_dirt = 1; + } + return read; +} + +static int ext2_file_write (struct inode * inode, struct file * filp, + char * buf, int count) +{ + const loff_t two_gb = 2147483647; + loff_t pos; + off_t pos2; + int written, c; + struct buffer_head * bh, *bufferlist[NBUF]; + char * p; + struct super_block * sb; + int err; + int i,buffercount,write_error; + + write_error = buffercount = 0; + if (!inode) { + printk("ext2_file_write: inode = NULL\n"); + return -EINVAL; + } + sb = inode->i_sb; + if (sb->s_flags & MS_RDONLY) + /* + * This fs has been automatically remounted ro because of errors + */ + return -ENOSPC; + + if (!S_ISREG(inode->i_mode)) { + ext2_warning (sb, "ext2_file_write", "mode = %07o", + inode->i_mode); + return -EINVAL; + } + down(&inode->i_sem); + if (filp->f_flags & O_APPEND) + pos = inode->i_size; + else + pos = filp->f_pos; + pos2 = (off_t) pos; + /* + * If a file has been opened in synchronous mode, we have to ensure + * that meta-data will also be written synchronously. Thus, we + * set the i_osync field. This field is tested by the allocation + * routines. + */ + if (filp->f_flags & O_SYNC) + inode->u.ext2_i.i_osync++; + written = 0; + while (written < count) { + if (pos > two_gb) { + if (!written) + written = -EFBIG; + break; + } + bh = ext2_getblk (inode, pos2 / sb->s_blocksize, 1, &err); + if (!bh) { + if (!written) + written = err; + break; + } + c = sb->s_blocksize - (pos2 % sb->s_blocksize); + if (c > count-written) + c = count - written; + if (c != sb->s_blocksize && !bh->b_uptodate) { + ll_rw_block (READ, 1, &bh); + wait_on_buffer (bh); + if (!bh->b_uptodate) { + brelse (bh); + if (!written) + written = -EIO; + break; + } + } + p = (pos2 % sb->s_blocksize) + bh->b_data; + pos2 += c; + pos += c; + written += c; + memcpy_fromfs (p, buf, c); + buf += c; + bh->b_uptodate = 1; + mark_buffer_dirty(bh, 0); + if (filp->f_flags & O_SYNC) + bufferlist[buffercount++] = bh; + else + brelse(bh); + if (buffercount == NBUF){ + ll_rw_block(WRITE, buffercount, bufferlist); + for(i=0; i<buffercount; i++){ + wait_on_buffer(bufferlist[i]); + if (!bufferlist[i]->b_uptodate) + write_error=1; + brelse(bufferlist[i]); + } + buffercount=0; + } + if(write_error) + break; + } + if ( buffercount ){ + ll_rw_block(WRITE, buffercount, bufferlist); + for(i=0; i<buffercount; i++){ + wait_on_buffer(bufferlist[i]); + if (!bufferlist[i]->b_uptodate) + write_error=1; + brelse(bufferlist[i]); + } + } + if (pos > inode->i_size) + inode->i_size = pos; + if (filp->f_flags & O_SYNC) + inode->u.ext2_i.i_osync--; + up(&inode->i_sem); + inode->i_ctime = inode->i_mtime = CURRENT_TIME; + filp->f_pos = pos; + inode->i_dirt = 1; + return written; +} + +/* + * Called when a inode is released. Note that this is different + * from ext2_open: open gets called at every open, but release + * gets called only when /all/ the files are closed. + */ +static void ext2_release_file (struct inode * inode, struct file * filp) +{ + if (filp->f_mode & 2) + ext2_discard_prealloc (inode); +} diff --git a/fs/ext2/fsync.c b/fs/ext2/fsync.c new file mode 100644 index 000000000..2f79c4749 --- /dev/null +++ b/fs/ext2/fsync.c @@ -0,0 +1,198 @@ +/* + * linux/fs/ext2/fsync.c + * + * Copyright (C) 1993 Stephen Tweedie (sct@dcs.ed.ac.uk) + * from + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * from + * linux/fs/minix/truncate.c Copyright (C) 1991, 1992 Linus Torvalds + * + * ext2fs fsync primitive + */ + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/locks.h> + + +#define blocksize (EXT2_BLOCK_SIZE(inode->i_sb)) +#define addr_per_block (EXT2_ADDR_PER_BLOCK(inode->i_sb)) + +static int sync_block (struct inode * inode, unsigned long * block, int wait) +{ + struct buffer_head * bh; + int tmp; + + if (!*block) + return 0; + tmp = *block; + bh = get_hash_table (inode->i_dev, *block, blocksize); + if (!bh) + return 0; + if (*block != tmp) { + brelse (bh); + return 1; + } + if (wait && bh->b_req && !bh->b_uptodate) { + brelse (bh); + return -1; + } + if (wait || !bh->b_uptodate || !bh->b_dirt) { + brelse (bh); + return 0; + } + ll_rw_block (WRITE, 1, &bh); + bh->b_count--; + return 0; +} + +static int sync_iblock (struct inode * inode, unsigned long * iblock, + struct buffer_head ** bh, int wait) +{ + int rc, tmp; + + *bh = NULL; + tmp = *iblock; + if (!tmp) + return 0; + rc = sync_block (inode, iblock, wait); + if (rc) + return rc; + *bh = bread (inode->i_dev, tmp, blocksize); + if (tmp != *iblock) { + brelse (*bh); + *bh = NULL; + return 1; + } + if (!*bh) + return -1; + return 0; +} + + +static int sync_direct (struct inode * inode, int wait) +{ + int i; + int rc, err = 0; + + for (i = 0; i < EXT2_NDIR_BLOCKS; i++) { + rc = sync_block (inode, inode->u.ext2_i.i_data + i, wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + return err; +} + +static int sync_indirect (struct inode * inode, unsigned long * iblock, + int wait) +{ + int i; + struct buffer_head * ind_bh; + int rc, err = 0; + + rc = sync_iblock (inode, iblock, &ind_bh, wait); + if (rc || !ind_bh) + return rc; + + for (i = 0; i < addr_per_block; i++) { + rc = sync_block (inode, + ((unsigned long *) ind_bh->b_data) + i, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse (ind_bh); + return err; +} + +static int sync_dindirect (struct inode * inode, unsigned long * diblock, + int wait) +{ + int i; + struct buffer_head * dind_bh; + int rc, err = 0; + + rc = sync_iblock (inode, diblock, &dind_bh, wait); + if (rc || !dind_bh) + return rc; + + for (i = 0; i < addr_per_block; i++) { + rc = sync_indirect (inode, + ((unsigned long *) dind_bh->b_data) + i, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse (dind_bh); + return err; +} + +static int sync_tindirect (struct inode * inode, unsigned long * tiblock, + int wait) +{ + int i; + struct buffer_head * tind_bh; + int rc, err = 0; + + rc = sync_iblock (inode, tiblock, &tind_bh, wait); + if (rc || !tind_bh) + return rc; + + for (i = 0; i < addr_per_block; i++) { + rc = sync_dindirect (inode, + ((unsigned long *) tind_bh->b_data) + i, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse (tind_bh); + return err; +} + +int ext2_sync_file (struct inode * inode, struct file * file) +{ + int wait, err = 0; + + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || + S_ISLNK(inode->i_mode))) + return -EINVAL; + if (S_ISLNK(inode->i_mode) && !(inode->i_blocks)) + /* + * Don't sync fast links! + */ + goto skip; + + for (wait=0; wait<=1; wait++) + { + err |= sync_direct (inode, wait); + err |= sync_indirect (inode, + inode->u.ext2_i.i_data+EXT2_IND_BLOCK, + wait); + err |= sync_dindirect (inode, + inode->u.ext2_i.i_data+EXT2_DIND_BLOCK, + wait); + err |= sync_tindirect (inode, + inode->u.ext2_i.i_data+EXT2_TIND_BLOCK, + wait); + } +skip: + err |= ext2_sync_inode (inode); + return (err < 0) ? -EIO : 0; +} diff --git a/fs/ext2/ialloc.c b/fs/ext2/ialloc.c new file mode 100644 index 000000000..69c9e2224 --- /dev/null +++ b/fs/ext2/ialloc.c @@ -0,0 +1,554 @@ +/* + * linux/fs/ext2/ialloc.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * BSD ufs-inspired inode and directory allocation by + * Stephen Tweedie (sct@dcs.ed.ac.uk), 1993 + */ + +/* + * ialloc.c contains the inodes allocation and deallocation routines + */ + +/* + * The free inodes 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 <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/locks.h> + +#include <asm/bitops.h> + +static struct ext2_group_desc * get_group_desc (struct super_block * sb, + unsigned int block_group, + struct buffer_head ** bh) +{ + unsigned long group_desc; + unsigned long desc; + struct ext2_group_desc * gdp; + + if (block_group >= sb->u.ext2_sb.s_groups_count) + ext2_panic (sb, "get_group_desc", + "block_group >= groups_count - " + "block_group = %d, groups_count = %lu", + block_group, sb->u.ext2_sb.s_groups_count); + + group_desc = block_group / EXT2_DESC_PER_BLOCK(sb); + desc = block_group % EXT2_DESC_PER_BLOCK(sb); + if (!sb->u.ext2_sb.s_group_desc[group_desc]) + ext2_panic (sb, "get_group_desc", + "Group descriptor not loaded - " + "block_group = %d, group_desc = %lu, desc = %lu", + block_group, group_desc, desc); + gdp = (struct ext2_group_desc *) + sb->u.ext2_sb.s_group_desc[group_desc]->b_data; + if (bh) + *bh = sb->u.ext2_sb.s_group_desc[group_desc]; + return gdp + desc; +} + +static void read_inode_bitmap (struct super_block * sb, + unsigned long block_group, + unsigned int bitmap_nr) +{ + struct ext2_group_desc * gdp; + struct buffer_head * bh; + + gdp = get_group_desc (sb, block_group, NULL); + bh = bread (sb->s_dev, gdp->bg_inode_bitmap, sb->s_blocksize); + if (!bh) + ext2_panic (sb, "read_inode_bitmap", + "Cannot read inode bitmap - " + "block_group = %lu, inode_bitmap = %lu", + block_group, gdp->bg_inode_bitmap); + sb->u.ext2_sb.s_inode_bitmap_number[bitmap_nr] = block_group; + sb->u.ext2_sb.s_inode_bitmap[bitmap_nr] = bh; +} + +/* + * load_inode_bitmap loads the inode bitmap for a blocks group + * + * It maintains a cache for the last bitmaps loaded. This cache is managed + * with a LRU algorithm. + * + * Notes: + * 1/ There is one cache per mounted file system. + * 2/ If the file system contains less than EXT2_MAX_GROUP_LOADED groups, + * this function reads the bitmap without maintaining a LRU cache. + */ +static int load_inode_bitmap (struct super_block * sb, + unsigned int block_group) +{ + int i, j; + unsigned long inode_bitmap_number; + struct buffer_head * inode_bitmap; + + if (block_group >= sb->u.ext2_sb.s_groups_count) + ext2_panic (sb, "load_inode_bitmap", + "block_group >= groups_count - " + "block_group = %d, groups_count = %lu", + block_group, sb->u.ext2_sb.s_groups_count); + if (sb->u.ext2_sb.s_loaded_inode_bitmaps > 0 && + sb->u.ext2_sb.s_inode_bitmap_number[0] == block_group) + return 0; + if (sb->u.ext2_sb.s_groups_count <= EXT2_MAX_GROUP_LOADED) { + if (sb->u.ext2_sb.s_inode_bitmap[block_group]) { + if (sb->u.ext2_sb.s_inode_bitmap_number[block_group] != block_group) + ext2_panic (sb, "load_inode_bitmap", + "block_group != inode_bitmap_number"); + else + return block_group; + } else { + read_inode_bitmap (sb, block_group, block_group); + return block_group; + } + } + + for (i = 0; i < sb->u.ext2_sb.s_loaded_inode_bitmaps && + sb->u.ext2_sb.s_inode_bitmap_number[i] != block_group; + i++) + ; + if (i < sb->u.ext2_sb.s_loaded_inode_bitmaps && + sb->u.ext2_sb.s_inode_bitmap_number[i] == block_group) { + inode_bitmap_number = sb->u.ext2_sb.s_inode_bitmap_number[i]; + inode_bitmap = sb->u.ext2_sb.s_inode_bitmap[i]; + for (j = i; j > 0; j--) { + sb->u.ext2_sb.s_inode_bitmap_number[j] = + sb->u.ext2_sb.s_inode_bitmap_number[j - 1]; + sb->u.ext2_sb.s_inode_bitmap[j] = + sb->u.ext2_sb.s_inode_bitmap[j - 1]; + } + sb->u.ext2_sb.s_inode_bitmap_number[0] = inode_bitmap_number; + sb->u.ext2_sb.s_inode_bitmap[0] = inode_bitmap; + } else { + if (sb->u.ext2_sb.s_loaded_inode_bitmaps < EXT2_MAX_GROUP_LOADED) + sb->u.ext2_sb.s_loaded_inode_bitmaps++; + else + brelse (sb->u.ext2_sb.s_inode_bitmap[EXT2_MAX_GROUP_LOADED - 1]); + for (j = sb->u.ext2_sb.s_loaded_inode_bitmaps - 1; j > 0; j--) { + sb->u.ext2_sb.s_inode_bitmap_number[j] = + sb->u.ext2_sb.s_inode_bitmap_number[j - 1]; + sb->u.ext2_sb.s_inode_bitmap[j] = + sb->u.ext2_sb.s_inode_bitmap[j - 1]; + } + read_inode_bitmap (sb, block_group, 0); + } + return 0; +} + +/* + * This function sets the deletion time for the inode + * + * This may be used one day by an 'undelete' program + */ +static void set_inode_dtime (struct inode * inode, + struct ext2_group_desc * gdp) +{ + unsigned long inode_block; + struct buffer_head * bh; + struct ext2_inode * raw_inode; + + inode_block = gdp->bg_inode_table + (((inode->i_ino - 1) % + EXT2_INODES_PER_GROUP(inode->i_sb)) / + EXT2_INODES_PER_BLOCK(inode->i_sb)); + bh = bread (inode->i_sb->s_dev, inode_block, inode->i_sb->s_blocksize); + if (!bh) + ext2_panic (inode->i_sb, "set_inode_dtime", + "Cannot load inode table block - " + "inode=%lu, inode_block=%lu", + inode->i_ino, inode_block); + raw_inode = ((struct ext2_inode *) bh->b_data) + + (((inode->i_ino - 1) % + EXT2_INODES_PER_GROUP(inode->i_sb)) % + EXT2_INODES_PER_BLOCK(inode->i_sb)); + raw_inode->i_links_count = 0; + raw_inode->i_dtime = CURRENT_TIME; + mark_buffer_dirty(bh, 1); + if (IS_SYNC(inode)) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + brelse (bh); +} + +void ext2_free_inode (struct inode * inode) +{ + struct super_block * sb; + struct buffer_head * bh; + struct buffer_head * bh2; + unsigned long block_group; + unsigned long bit; + int bitmap_nr; + struct ext2_group_desc * gdp; + struct ext2_super_block * es; + + if (!inode) + return; + if (!inode->i_dev) { + printk ("ext2_free_inode: inode has no device\n"); + return; + } + if (inode->i_count > 1) { + printk ("ext2_free_inode: inode has count=%d\n", + inode->i_count); + return; + } + if (inode->i_nlink) { + printk ("ext2_free_inode: inode has nlink=%d\n", + inode->i_nlink); + return; + } + if (!inode->i_sb) { + printk("ext2_free_inode: inode on nonexistent device\n"); + return; + } + + ext2_debug ("freeing inode %lu\n", inode->i_ino); + + sb = inode->i_sb; + lock_super (sb); + if (inode->i_ino < EXT2_FIRST_INO || + inode->i_ino > sb->u.ext2_sb.s_es->s_inodes_count) { + ext2_error (sb, "free_inode", + "reserved inode or nonexistent inode"); + unlock_super (sb); + return; + } + es = sb->u.ext2_sb.s_es; + block_group = (inode->i_ino - 1) / EXT2_INODES_PER_GROUP(sb); + bit = (inode->i_ino - 1) % EXT2_INODES_PER_GROUP(sb); + bitmap_nr = load_inode_bitmap (sb, block_group); + bh = sb->u.ext2_sb.s_inode_bitmap[bitmap_nr]; + if (!clear_bit (bit, bh->b_data)) + ext2_warning (sb, "ext2_free_inode", + "bit already cleared for inode %lu", inode->i_ino); + else { + gdp = get_group_desc (sb, block_group, &bh2); + gdp->bg_free_inodes_count++; + if (S_ISDIR(inode->i_mode)) + gdp->bg_used_dirs_count--; + mark_buffer_dirty(bh2, 1); + es->s_free_inodes_count++; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + set_inode_dtime (inode, gdp); + } + mark_buffer_dirty(bh, 1); + if (sb->s_flags & MS_SYNC) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + + sb->s_dirt = 1; + clear_inode (inode); + unlock_super (sb); +} + +/* + * This function increments the inode version number + * + * This may be used one day by the NFS server + */ +static void inc_inode_version (struct inode * inode, + struct ext2_group_desc *gdp, + int mode) +{ + unsigned long inode_block; + struct buffer_head * bh; + struct ext2_inode * raw_inode; + + inode_block = gdp->bg_inode_table + (((inode->i_ino - 1) % + EXT2_INODES_PER_GROUP(inode->i_sb)) / + EXT2_INODES_PER_BLOCK(inode->i_sb)); + bh = bread (inode->i_sb->s_dev, inode_block, inode->i_sb->s_blocksize); + if (!bh) { + ext2_error (inode->i_sb, "inc_inode_version", + "Cannot load inode table block - " + "inode=%lu, inode_block=%lu\n", + inode->i_ino, inode_block); + inode->u.ext2_i.i_version = 1; + return; + } + raw_inode = ((struct ext2_inode *) bh->b_data) + + (((inode->i_ino - 1) % + EXT2_INODES_PER_GROUP(inode->i_sb)) % + EXT2_INODES_PER_BLOCK(inode->i_sb)); + raw_inode->i_version++; + inode->u.ext2_i.i_version = raw_inode->i_version; + mark_buffer_dirty(bh, 1); + brelse (bh); +} + +/* + * There are two policies for allocating an inode. If the new inode is + * a directory, then a forward search is made for a block group with both + * free space and a low directory-to-inode ratio; if that fails, then of + * the groups with above-average free space, that group with the fewest + * directories already is chosen. + * + * For other inodes, search forward from the parent directory\'s block + * group to find a free inode. + */ +struct inode * ext2_new_inode (const struct inode * dir, int mode) +{ + struct super_block * sb; + struct buffer_head * bh; + struct buffer_head * bh2; + int i, j, avefreei; + struct inode * inode; + int bitmap_nr; + struct ext2_group_desc * gdp; + struct ext2_group_desc * tmp; + struct ext2_super_block * es; + + if (!dir || !(inode = get_empty_inode ())) + return NULL; + sb = dir->i_sb; + inode->i_sb = sb; + inode->i_flags = sb->s_flags; + lock_super (sb); + es = sb->u.ext2_sb.s_es; +repeat: + gdp = NULL; i=0; + + if (S_ISDIR(mode)) { + avefreei = es->s_free_inodes_count / + sb->u.ext2_sb.s_groups_count; +/* I am not yet convinced that this next bit is necessary. + i = dir->u.ext2_i.i_block_group; + for (j = 0; j < sb->u.ext2_sb.s_groups_count; j++) { + tmp = get_group_desc (sb, i, &bh2); + if ((tmp->bg_used_dirs_count << 8) < + tmp->bg_free_inodes_count) { + gdp = tmp; + break; + } + else + i = ++i % sb->u.ext2_sb.s_groups_count; + } +*/ + if (!gdp) { + for (j = 0; j < sb->u.ext2_sb.s_groups_count; j++) { + tmp = get_group_desc (sb, j, &bh2); + if (tmp->bg_free_inodes_count && + tmp->bg_free_inodes_count >= avefreei) { + if (!gdp || + (tmp->bg_free_blocks_count > + gdp->bg_free_blocks_count)) { + i = j; + gdp = tmp; + } + } + } + } + } + else + { + /* + * Try to place the inode in it's parent directory + */ + i = dir->u.ext2_i.i_block_group; + tmp = get_group_desc (sb, i, &bh2); + if (tmp->bg_free_inodes_count) + gdp = tmp; + else + { + /* + * Use a quadratic hash to find a group with a + * free inode + */ + for (j = 1; j < sb->u.ext2_sb.s_groups_count; j <<= 1) { + i += j; + if (i >= sb->u.ext2_sb.s_groups_count) + i -= sb->u.ext2_sb.s_groups_count; + tmp = get_group_desc (sb, i, &bh2); + if (tmp->bg_free_inodes_count) { + gdp = tmp; + break; + } + } + } + if (!gdp) { + /* + * That failed: try linear search for a free inode + */ + i = dir->u.ext2_i.i_block_group + 1; + for (j = 2; j < sb->u.ext2_sb.s_groups_count; j++) { + if (++i >= sb->u.ext2_sb.s_groups_count) + i = 0; + tmp = get_group_desc (sb, i, &bh2); + if (tmp->bg_free_inodes_count) { + gdp = tmp; + break; + } + } + } + } + + if (!gdp) { + unlock_super (sb); + iput(inode); + return NULL; + } + bitmap_nr = load_inode_bitmap (sb, i); + bh = sb->u.ext2_sb.s_inode_bitmap[bitmap_nr]; + if ((j = find_first_zero_bit ((unsigned long *) bh->b_data, + EXT2_INODES_PER_GROUP(sb))) < + EXT2_INODES_PER_GROUP(sb)) { + if (set_bit (j, bh->b_data)) { + ext2_warning (sb, "ext2_new_inode", + "bit already set for inode %d", j); + goto repeat; + } + mark_buffer_dirty(bh, 1); + if (sb->s_flags & MS_SYNC) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + } else { + if (gdp->bg_free_inodes_count != 0) { + ext2_error (sb, "ext2_new_inode", + "Free inodes count corrupted in group %d", + i); + unlock_super (sb); + iput (inode); + return NULL; + } + goto repeat; + } + j += i * EXT2_INODES_PER_GROUP(sb) + 1; + if (j < EXT2_FIRST_INO || j > es->s_inodes_count) { + ext2_error (sb, "ext2_new_inode", + "reserved inode or inode > inodes count - " + "block_group = %d,inode=%d", i, j); + unlock_super (sb); + iput (inode); + return NULL; + } + gdp->bg_free_inodes_count--; + if (S_ISDIR(mode)) + gdp->bg_used_dirs_count++; + mark_buffer_dirty(bh2, 1); + es->s_free_inodes_count--; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + sb->s_dirt = 1; + inode->i_mode = mode; + inode->i_sb = sb; + inode->i_count = 1; + inode->i_nlink = 1; + inode->i_dev = sb->s_dev; + inode->i_uid = current->fsuid; + if (test_opt (sb, GRPID)) + inode->i_gid = dir->i_gid; + else if (dir->i_mode & S_ISGID) { + inode->i_gid = dir->i_gid; + if (S_ISDIR(mode)) + mode |= S_ISGID; + } else + inode->i_gid = current->fsgid; + inode->i_dirt = 1; + inode->i_ino = j; + inode->i_blksize = sb->s_blocksize; + inode->i_blocks = 0; + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; + inode->u.ext2_i.i_flags = dir->u.ext2_i.i_flags; + if (S_ISLNK(mode)) + inode->u.ext2_i.i_flags &= ~(EXT2_IMMUTABLE_FL | EXT2_APPEND_FL); + inode->u.ext2_i.i_faddr = 0; + inode->u.ext2_i.i_frag_no = 0; + inode->u.ext2_i.i_frag_size = 0; + inode->u.ext2_i.i_file_acl = 0; + inode->u.ext2_i.i_dir_acl = 0; + inode->u.ext2_i.i_dtime = 0; + inode->u.ext2_i.i_block_group = i; + inode->i_op = NULL; + if (inode->u.ext2_i.i_flags & EXT2_SYNC_FL) + inode->i_flags |= MS_SYNC; + insert_inode_hash(inode); + inc_inode_version (inode, gdp, mode); + + ext2_debug ("allocating inode %lu\n", inode->i_ino); + + unlock_super (sb); + return inode; +} + +unsigned long ext2_count_free_inodes (struct super_block * sb) +{ +#ifdef EXT2FS_DEBUG + struct ext2_super_block * es; + unsigned long desc_count, bitmap_count, x; + int bitmap_nr; + struct ext2_group_desc * gdp; + int i; + + lock_super (sb); + es = sb->u.ext2_sb.s_es; + desc_count = 0; + bitmap_count = 0; + gdp = NULL; + for (i = 0; i < sb->u.ext2_sb.s_groups_count; i++) { + gdp = get_group_desc (sb, i, NULL); + desc_count += gdp->bg_free_inodes_count; + bitmap_nr = load_inode_bitmap (sb, i); + x = ext2_count_free (sb->u.ext2_sb.s_inode_bitmap[bitmap_nr], + EXT2_INODES_PER_GROUP(sb) / 8); + printk ("group %d: stored = %d, counted = %lu\n", + i, gdp->bg_free_inodes_count, x); + bitmap_count += x; + } + printk("ext2_count_free_inodes: stored = %lu, computed = %lu, %lu\n", + es->s_free_inodes_count, desc_count, bitmap_count); + unlock_super (sb); + return desc_count; +#else + return sb->u.ext2_sb.s_es->s_free_inodes_count; +#endif +} + +void ext2_check_inodes_bitmap (struct super_block * sb) +{ + struct ext2_super_block * es; + unsigned long desc_count, bitmap_count, x; + int bitmap_nr; + struct ext2_group_desc * gdp; + int i; + + lock_super (sb); + es = sb->u.ext2_sb.s_es; + desc_count = 0; + bitmap_count = 0; + gdp = NULL; + for (i = 0; i < sb->u.ext2_sb.s_groups_count; i++) { + gdp = get_group_desc (sb, i, NULL); + desc_count += gdp->bg_free_inodes_count; + bitmap_nr = load_inode_bitmap (sb, i); + x = ext2_count_free (sb->u.ext2_sb.s_inode_bitmap[bitmap_nr], + EXT2_INODES_PER_GROUP(sb) / 8); + if (gdp->bg_free_inodes_count != x) + ext2_error (sb, "ext2_check_inodes_bitmap", + "Wrong free inodes count in group %d, " + "stored = %d, counted = %lu", i, + gdp->bg_free_inodes_count, x); + bitmap_count += x; + } + if (es->s_free_inodes_count != bitmap_count) + ext2_error (sb, "ext2_check_inodes_bitmap", + "Wrong free inodes count in super block, " + "stored = %lu, counted = %lu", + es->s_free_inodes_count, bitmap_count); + unlock_super (sb); +} diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c new file mode 100644 index 000000000..633c33e4f --- /dev/null +++ b/fs/ext2/inode.c @@ -0,0 +1,667 @@ +/* + * linux/fs/ext2/inode.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/fs/minix/inode.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * Goal-directed block allocation by Stephen Tweedie (sct@dcs.ed.ac.uk), 1993 + */ + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/locks.h> + +void ext2_put_inode (struct inode * inode) +{ + ext2_discard_prealloc (inode); + if (inode->i_nlink || inode->i_ino == EXT2_ACL_IDX_INO || + inode->i_ino == EXT2_ACL_DATA_INO) + return; + inode->i_size = 0; + if (inode->i_blocks) + ext2_truncate (inode); + ext2_free_inode (inode); +} + +#define inode_bmap(inode, nr) ((inode)->u.ext2_i.i_data[(nr)]) + +static int block_bmap (struct buffer_head * bh, int nr) +{ + int tmp; + + if (!bh) + return 0; + tmp = ((unsigned long *) bh->b_data)[nr]; + brelse (bh); + return tmp; +} + +/* + * ext2_discard_prealloc and ext2_alloc_block are atomic wrt. the + * superblock in the same manner as are ext2_free_blocks and + * ext2_new_block. We just wait on the super rather than locking it + * here, since ext2_new_block will do the necessary locking and we + * can't block until then. + */ +void ext2_discard_prealloc (struct inode * inode) +{ +#ifdef EXT2_PREALLOCATE + if (inode->u.ext2_i.i_prealloc_count) { + int i = inode->u.ext2_i.i_prealloc_count; + inode->u.ext2_i.i_prealloc_count = 0; + ext2_free_blocks (inode->i_sb, + inode->u.ext2_i.i_prealloc_block, + i); + } +#endif +} + +static int ext2_alloc_block (struct inode * inode, unsigned long goal) +{ +#ifdef EXT2FS_DEBUG + static unsigned long alloc_hits = 0, alloc_attempts = 0; +#endif + unsigned long result; + struct buffer_head * bh; + + wait_on_super (inode->i_sb); + +#ifdef EXT2_PREALLOCATE + if (inode->u.ext2_i.i_prealloc_count && + (goal == inode->u.ext2_i.i_prealloc_block || + goal + 1 == inode->u.ext2_i.i_prealloc_block)) + { + result = inode->u.ext2_i.i_prealloc_block++; + inode->u.ext2_i.i_prealloc_count--; + ext2_debug ("preallocation hit (%lu/%lu).\n", + ++alloc_hits, ++alloc_attempts); + + /* It doesn't matter if we block in getblk() since + we have already atomically allocated the block, and + are only clearing it now. */ + if (!(bh = getblk (inode->i_sb->s_dev, result, + inode->i_sb->s_blocksize))) { + ext2_error (inode->i_sb, "ext2_alloc_block", + "cannot get block %lu", result); + return 0; + } + memset(bh->b_data, 0, inode->i_sb->s_blocksize); + bh->b_uptodate = 1; + mark_buffer_dirty(bh, 1); + brelse (bh); + } else { + ext2_discard_prealloc (inode); + ext2_debug ("preallocation miss (%lu/%lu).\n", + alloc_hits, ++alloc_attempts); + if (S_ISREG(inode->i_mode)) + result = ext2_new_block + (inode->i_sb, goal, + &inode->u.ext2_i.i_prealloc_count, + &inode->u.ext2_i.i_prealloc_block); + else + result = ext2_new_block (inode->i_sb, goal, 0, 0); + } +#else + result = ext2_new_block (inode->i_sb, goal, 0, 0); +#endif + + return result; +} + + +int ext2_bmap (struct inode * inode, int block) +{ + int i; + int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb); + + if (block < 0) { + ext2_warning (inode->i_sb, "ext2_bmap", "block < 0"); + return 0; + } + if (block >= EXT2_NDIR_BLOCKS + addr_per_block + + addr_per_block * addr_per_block + + addr_per_block * addr_per_block * addr_per_block) { + ext2_warning (inode->i_sb, "ext2_bmap", "block > big"); + return 0; + } + if (block < EXT2_NDIR_BLOCKS) + return inode_bmap (inode, block); + block -= EXT2_NDIR_BLOCKS; + if (block < addr_per_block) { + i = inode_bmap (inode, EXT2_IND_BLOCK); + if (!i) + return 0; + return block_bmap (bread (inode->i_dev, i, + inode->i_sb->s_blocksize), block); + } + block -= addr_per_block; + if (block < addr_per_block * addr_per_block) { + i = inode_bmap (inode, EXT2_DIND_BLOCK); + if (!i) + return 0; + i = block_bmap (bread (inode->i_dev, i, + inode->i_sb->s_blocksize), + block / addr_per_block); + if (!i) + return 0; + return block_bmap (bread (inode->i_dev, i, + inode->i_sb->s_blocksize), + block & (addr_per_block - 1)); + } + block -= addr_per_block * addr_per_block; + i = inode_bmap (inode, EXT2_TIND_BLOCK); + if (!i) + return 0; + i = block_bmap (bread (inode->i_dev, i, inode->i_sb->s_blocksize), + block / (addr_per_block * addr_per_block)); + if (!i) + return 0; + i = block_bmap (bread (inode->i_dev, i, inode->i_sb->s_blocksize), + (block / addr_per_block) & (addr_per_block - 1)); + if (!i) + return 0; + return block_bmap (bread (inode->i_dev, i, inode->i_sb->s_blocksize), + block & (addr_per_block - 1)); +} + +static struct buffer_head * inode_getblk (struct inode * inode, int nr, + int create, int new_block, int * err) +{ + int tmp, goal = 0; + unsigned long * p; + struct buffer_head * result; + int blocks = inode->i_sb->s_blocksize / 512; + + p = inode->u.ext2_i.i_data + nr; +repeat: + tmp = *p; + if (tmp) { + result = getblk (inode->i_dev, tmp, inode->i_sb->s_blocksize); + if (tmp == *p) + return result; + brelse (result); + goto repeat; + } + if (!create || new_block >= + (current->rlim[RLIMIT_FSIZE].rlim_cur >> + EXT2_BLOCK_SIZE_BITS(inode->i_sb))) { + *err = -EFBIG; + return NULL; + } + if (inode->u.ext2_i.i_next_alloc_block == new_block) + goal = inode->u.ext2_i.i_next_alloc_goal; + + ext2_debug ("hint = %d,", goal); + + if (!goal) { + for (tmp = nr - 1; tmp >= 0; tmp--) { + if (inode->u.ext2_i.i_data[tmp]) { + goal = inode->u.ext2_i.i_data[tmp]; + break; + } + } + if (!goal) + goal = (inode->u.ext2_i.i_block_group * + EXT2_BLOCKS_PER_GROUP(inode->i_sb)) + + inode->i_sb->u.ext2_sb.s_es->s_first_data_block; + } + + ext2_debug ("goal = %d.\n", goal); + + tmp = ext2_alloc_block (inode, goal); + if (!tmp) + return NULL; + result = getblk (inode->i_dev, tmp, inode->i_sb->s_blocksize); + if (*p) { + ext2_free_blocks (inode->i_sb, tmp, 1); + brelse (result); + goto repeat; + } + *p = tmp; + inode->u.ext2_i.i_next_alloc_block = new_block; + inode->u.ext2_i.i_next_alloc_goal = tmp; + inode->i_ctime = CURRENT_TIME; + inode->i_blocks += blocks; + if (IS_SYNC(inode) || inode->u.ext2_i.i_osync) + ext2_sync_inode (inode); + else + inode->i_dirt = 1; + return result; +} + +static struct buffer_head * block_getblk (struct inode * inode, + struct buffer_head * bh, int nr, + int create, int blocksize, + int new_block, int * err) +{ + int tmp, goal = 0; + unsigned long * p; + struct buffer_head * result; + int blocks = inode->i_sb->s_blocksize / 512; + + if (!bh) + return NULL; + if (!bh->b_uptodate) { + ll_rw_block (READ, 1, &bh); + wait_on_buffer (bh); + if (!bh->b_uptodate) { + brelse (bh); + return NULL; + } + } + p = (unsigned long *) bh->b_data + nr; +repeat: + tmp = *p; + if (tmp) { + result = getblk (bh->b_dev, tmp, blocksize); + if (tmp == *p) { + brelse (bh); + return result; + } + brelse (result); + goto repeat; + } + if (!create || new_block >= + (current->rlim[RLIMIT_FSIZE].rlim_cur >> + EXT2_BLOCK_SIZE_BITS(inode->i_sb))) { + brelse (bh); + *err = -EFBIG; + return NULL; + } + if (inode->u.ext2_i.i_next_alloc_block == new_block) + goal = inode->u.ext2_i.i_next_alloc_goal; + if (!goal) { + for (tmp = nr - 1; tmp >= 0; tmp--) { + if (((unsigned long *) bh->b_data)[tmp]) { + goal = ((unsigned long *)bh->b_data)[tmp]; + break; + } + } + if (!goal) + goal = bh->b_blocknr; + } + tmp = ext2_alloc_block (inode, goal); + if (!tmp) { + brelse (bh); + return NULL; + } + result = getblk (bh->b_dev, tmp, blocksize); + if (*p) { + ext2_free_blocks (inode->i_sb, tmp, 1); + brelse (result); + goto repeat; + } + *p = tmp; + mark_buffer_dirty(bh, 1); + if (IS_SYNC(inode) || inode->u.ext2_i.i_osync) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + inode->i_ctime = CURRENT_TIME; + inode->i_blocks += blocks; + inode->i_dirt = 1; + inode->u.ext2_i.i_next_alloc_block = new_block; + inode->u.ext2_i.i_next_alloc_goal = tmp; + brelse (bh); + return result; +} + +static int block_getcluster (struct inode * inode, struct buffer_head * bh, + int nr, + int blocksize) +{ + unsigned long * p; + int firstblock = 0; + int result = 0; + int i; + + /* Check to see if clustering possible here. */ + + if(!bh) return 0; + + if(nr % (PAGE_SIZE / inode->i_sb->s_blocksize) != 0) goto out; + if(nr + 3 > EXT2_ADDR_PER_BLOCK(inode->i_sb)) goto out; + + for(i=0; i< (PAGE_SIZE / inode->i_sb->s_blocksize); i++) { + p = (unsigned long *) bh->b_data + nr + i; + + /* All blocks in cluster must already be allocated */ + if(*p == 0) goto out; + + /* See if aligned correctly */ + if(i==0) firstblock = *p; + else if(*p != firstblock + i) goto out; + }; + + p = (unsigned long *) bh->b_data + nr; + result = generate_cluster(bh->b_dev, (int *) p, blocksize); + + out: + brelse(bh); + return result; +} + +struct buffer_head * ext2_getblk (struct inode * inode, long block, + int create, int * err) +{ + struct buffer_head * bh; + unsigned long b; + unsigned long addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb); + + *err = -EIO; + if (block < 0) { + ext2_warning (inode->i_sb, "ext2_getblk", "block < 0"); + return NULL; + } + if (block > EXT2_NDIR_BLOCKS + addr_per_block + + addr_per_block * addr_per_block + + addr_per_block * addr_per_block * addr_per_block) { + ext2_warning (inode->i_sb, "ext2_getblk", "block > big"); + return NULL; + } + /* + * If this is a sequential block allocation, set the next_alloc_block + * to this block now so that all the indblock and data block + * allocations use the same goal zone + */ + + ext2_debug ("block %lu, next %lu, goal %lu.\n", block, + inode->u.ext2_i.i_next_alloc_block, + inode->u.ext2_i.i_next_alloc_goal); + + if (block == inode->u.ext2_i.i_next_alloc_block + 1) { + inode->u.ext2_i.i_next_alloc_block++; + inode->u.ext2_i.i_next_alloc_goal++; + } + + *err = -ENOSPC; + b = block; + if (block < EXT2_NDIR_BLOCKS) + return inode_getblk (inode, block, create, b, err); + block -= EXT2_NDIR_BLOCKS; + if (block < addr_per_block) { + bh = inode_getblk (inode, EXT2_IND_BLOCK, create, b, err); + return block_getblk (inode, bh, block, create, + inode->i_sb->s_blocksize, b, err); + } + block -= addr_per_block; + if (block < addr_per_block * addr_per_block) { + bh = inode_getblk (inode, EXT2_DIND_BLOCK, create, b, err); + bh = block_getblk (inode, bh, block / addr_per_block, create, + inode->i_sb->s_blocksize, b, err); + return block_getblk (inode, bh, block & (addr_per_block - 1), + create, inode->i_sb->s_blocksize, b, err); + } + block -= addr_per_block * addr_per_block; + bh = inode_getblk (inode, EXT2_TIND_BLOCK, create, b, err); + bh = block_getblk (inode, bh, block/(addr_per_block * addr_per_block), + create, inode->i_sb->s_blocksize, b, err); + bh = block_getblk (inode, bh, (block/addr_per_block) & (addr_per_block - 1), + create, inode->i_sb->s_blocksize, b, err); + return block_getblk (inode, bh, block & (addr_per_block - 1), create, + inode->i_sb->s_blocksize, b, err); +} + +int ext2_getcluster (struct inode * inode, long block) +{ + struct buffer_head * bh; + int err, create; + unsigned long b; + unsigned long addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb); + + create = 0; + err = -EIO; + if (block < 0) { + ext2_warning (inode->i_sb, "ext2_getblk", "block < 0"); + return 0; + } + if (block > EXT2_NDIR_BLOCKS + addr_per_block + + addr_per_block * addr_per_block + + addr_per_block * addr_per_block * addr_per_block) { + ext2_warning (inode->i_sb, "ext2_getblk", "block > big"); + return 0; + } + + err = -ENOSPC; + b = block; + if (block < EXT2_NDIR_BLOCKS) return 0; + + block -= EXT2_NDIR_BLOCKS; + + if (block < addr_per_block) { + bh = inode_getblk (inode, EXT2_IND_BLOCK, create, b, &err); + return block_getcluster (inode, bh, block, + inode->i_sb->s_blocksize); + } + block -= addr_per_block; + if (block < addr_per_block * addr_per_block) { + bh = inode_getblk (inode, EXT2_DIND_BLOCK, create, b, &err); + bh = block_getblk (inode, bh, block / addr_per_block, create, + inode->i_sb->s_blocksize, b, &err); + return block_getcluster (inode, bh, block & (addr_per_block - 1), + inode->i_sb->s_blocksize); + } + block -= addr_per_block * addr_per_block; + bh = inode_getblk (inode, EXT2_TIND_BLOCK, create, b, &err); + bh = block_getblk (inode, bh, block/(addr_per_block * addr_per_block), + create, inode->i_sb->s_blocksize, b, &err); + bh = block_getblk (inode, bh, (block/addr_per_block) & (addr_per_block - 1), + create, inode->i_sb->s_blocksize, b, &err); + return block_getcluster (inode, bh, block & (addr_per_block - 1), + inode->i_sb->s_blocksize); +} + +struct buffer_head * ext2_bread (struct inode * inode, int block, + int create, int *err) +{ + struct buffer_head * bh; + + bh = ext2_getblk (inode, block, create, err); + if (!bh || bh->b_uptodate) + return bh; + ll_rw_block (READ, 1, &bh); + wait_on_buffer (bh); + if (bh->b_uptodate) + return bh; + brelse (bh); + *err = -EIO; + return NULL; +} + +void ext2_read_inode (struct inode * inode) +{ + struct buffer_head * bh; + struct ext2_inode * raw_inode; + unsigned long block_group; + unsigned long group_desc; + unsigned long desc; + unsigned long block; + struct ext2_group_desc * gdp; + + if ((inode->i_ino != EXT2_ROOT_INO && inode->i_ino != EXT2_ACL_IDX_INO && + inode->i_ino != EXT2_ACL_DATA_INO && inode->i_ino < EXT2_FIRST_INO) || + inode->i_ino > inode->i_sb->u.ext2_sb.s_es->s_inodes_count) { + ext2_error (inode->i_sb, "ext2_read_inode", + "bad inode number: %lu", inode->i_ino); + return; + } + block_group = (inode->i_ino - 1) / EXT2_INODES_PER_GROUP(inode->i_sb); + if (block_group >= inode->i_sb->u.ext2_sb.s_groups_count) + ext2_panic (inode->i_sb, "ext2_read_inode", + "group >= groups count"); + group_desc = block_group / EXT2_DESC_PER_BLOCK(inode->i_sb); + desc = block_group % EXT2_DESC_PER_BLOCK(inode->i_sb); + bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc]; + if (!bh) + ext2_panic (inode->i_sb, "ext2_read_inode", + "Descriptor not loaded"); + gdp = (struct ext2_group_desc *) bh->b_data; + block = gdp[desc].bg_inode_table + + (((inode->i_ino - 1) % EXT2_INODES_PER_GROUP(inode->i_sb)) + / EXT2_INODES_PER_BLOCK(inode->i_sb)); + if (!(bh = bread (inode->i_dev, block, inode->i_sb->s_blocksize))) + ext2_panic (inode->i_sb, "ext2_read_inode", + "unable to read i-node block - " + "inode=%lu, block=%lu", inode->i_ino, block); + raw_inode = ((struct ext2_inode *) bh->b_data) + + (inode->i_ino - 1) % EXT2_INODES_PER_BLOCK(inode->i_sb); + inode->i_mode = raw_inode->i_mode; + inode->i_uid = raw_inode->i_uid; + inode->i_gid = raw_inode->i_gid; + inode->i_nlink = raw_inode->i_links_count; + inode->i_size = raw_inode->i_size; + inode->i_atime = raw_inode->i_atime; + inode->i_ctime = raw_inode->i_ctime; + inode->i_mtime = raw_inode->i_mtime; + inode->u.ext2_i.i_dtime = raw_inode->i_dtime; + inode->i_blksize = inode->i_sb->s_blocksize; + inode->i_blocks = raw_inode->i_blocks; + inode->i_version = ++event; + inode->u.ext2_i.i_flags = raw_inode->i_flags; + inode->u.ext2_i.i_faddr = raw_inode->i_faddr; + inode->u.ext2_i.i_frag_no = raw_inode->i_frag; + inode->u.ext2_i.i_frag_size = raw_inode->i_fsize; + inode->u.ext2_i.i_osync = 0; + inode->u.ext2_i.i_file_acl = raw_inode->i_file_acl; + inode->u.ext2_i.i_dir_acl = raw_inode->i_dir_acl; + inode->u.ext2_i.i_version = raw_inode->i_version; + inode->u.ext2_i.i_block_group = block_group; + inode->u.ext2_i.i_next_alloc_block = 0; + inode->u.ext2_i.i_next_alloc_goal = 0; + if (inode->u.ext2_i.i_prealloc_count) + ext2_error (inode->i_sb, "ext2_read_inode", + "New inode has non-zero prealloc count!"); + if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) + inode->i_rdev = raw_inode->i_block[0]; + else for (block = 0; block < EXT2_N_BLOCKS; block++) + inode->u.ext2_i.i_data[block] = raw_inode->i_block[block]; + brelse (bh); + inode->i_op = NULL; + if (inode->i_ino == EXT2_ACL_IDX_INO || + inode->i_ino == EXT2_ACL_DATA_INO) + /* Nothing to do */ ; + else if (S_ISREG(inode->i_mode)) + inode->i_op = &ext2_file_inode_operations; + else if (S_ISDIR(inode->i_mode)) + inode->i_op = &ext2_dir_inode_operations; + else if (S_ISLNK(inode->i_mode)) + inode->i_op = &ext2_symlink_inode_operations; + else if (S_ISCHR(inode->i_mode)) + inode->i_op = &chrdev_inode_operations; + else if (S_ISBLK(inode->i_mode)) + inode->i_op = &blkdev_inode_operations; + else if (S_ISFIFO(inode->i_mode)) + init_fifo(inode); + if (inode->u.ext2_i.i_flags & EXT2_SYNC_FL) + inode->i_flags |= MS_SYNC; + if (inode->u.ext2_i.i_flags & EXT2_APPEND_FL) + inode->i_flags |= S_APPEND; + if (inode->u.ext2_i.i_flags & EXT2_IMMUTABLE_FL) + inode->i_flags |= S_IMMUTABLE; +} + +static struct buffer_head * ext2_update_inode (struct inode * inode) +{ + struct buffer_head * bh; + struct ext2_inode * raw_inode; + unsigned long block_group; + unsigned long group_desc; + unsigned long desc; + unsigned long block; + struct ext2_group_desc * gdp; + + if ((inode->i_ino != EXT2_ROOT_INO && inode->i_ino < EXT2_FIRST_INO) || + inode->i_ino > inode->i_sb->u.ext2_sb.s_es->s_inodes_count) { + ext2_error (inode->i_sb, "ext2_write_inode", + "bad inode number: %lu", inode->i_ino); + return 0; + } + block_group = (inode->i_ino - 1) / EXT2_INODES_PER_GROUP(inode->i_sb); + if (block_group >= inode->i_sb->u.ext2_sb.s_groups_count) + ext2_panic (inode->i_sb, "ext2_write_inode", + "group >= groups count"); + group_desc = block_group / EXT2_DESC_PER_BLOCK(inode->i_sb); + desc = block_group % EXT2_DESC_PER_BLOCK(inode->i_sb); + bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc]; + if (!bh) + ext2_panic (inode->i_sb, "ext2_write_inode", + "Descriptor not loaded"); + gdp = (struct ext2_group_desc *) bh->b_data; + block = gdp[desc].bg_inode_table + + (((inode->i_ino - 1) % EXT2_INODES_PER_GROUP(inode->i_sb)) + / EXT2_INODES_PER_BLOCK(inode->i_sb)); + if (!(bh = bread (inode->i_dev, block, inode->i_sb->s_blocksize))) + ext2_panic (inode->i_sb, "ext2_write_inode", + "unable to read i-node block - " + "inode=%lu, block=%lu", inode->i_ino, block); + raw_inode = ((struct ext2_inode *)bh->b_data) + + (inode->i_ino - 1) % EXT2_INODES_PER_BLOCK(inode->i_sb); + raw_inode->i_mode = inode->i_mode; + raw_inode->i_uid = inode->i_uid; + raw_inode->i_gid = inode->i_gid; + raw_inode->i_links_count = inode->i_nlink; + raw_inode->i_size = inode->i_size; + raw_inode->i_atime = inode->i_atime; + raw_inode->i_ctime = inode->i_ctime; + raw_inode->i_mtime = inode->i_mtime; + raw_inode->i_blocks = inode->i_blocks; + raw_inode->i_dtime = inode->u.ext2_i.i_dtime; + raw_inode->i_flags = inode->u.ext2_i.i_flags; + raw_inode->i_faddr = inode->u.ext2_i.i_faddr; + raw_inode->i_frag = inode->u.ext2_i.i_frag_no; + raw_inode->i_fsize = inode->u.ext2_i.i_frag_size; + raw_inode->i_file_acl = inode->u.ext2_i.i_file_acl; + raw_inode->i_dir_acl = inode->u.ext2_i.i_dir_acl; + raw_inode->i_version = inode->u.ext2_i.i_version; + if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) + raw_inode->i_block[0] = inode->i_rdev; + else for (block = 0; block < EXT2_N_BLOCKS; block++) + raw_inode->i_block[block] = inode->u.ext2_i.i_data[block]; + mark_buffer_dirty(bh, 1); + inode->i_dirt = 0; + return bh; +} + +void ext2_write_inode (struct inode * inode) +{ + struct buffer_head * bh; + bh = ext2_update_inode (inode); + brelse (bh); +} + +int ext2_sync_inode (struct inode *inode) +{ + int err = 0; + struct buffer_head *bh; + + bh = ext2_update_inode (inode); + if (bh && bh->b_dirt) + { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + if (bh->b_req && !bh->b_uptodate) + { + printk ("IO error syncing ext2 inode [%04x:%08lx]\n", + inode->i_dev, inode->i_ino); + err = -1; + } + } + else if (!bh) + err = -1; + brelse (bh); + return err; +} diff --git a/fs/ext2/ioctl.c b/fs/ext2/ioctl.c new file mode 100644 index 000000000..447968ef0 --- /dev/null +++ b/fs/ext2/ioctl.c @@ -0,0 +1,75 @@ +/* + * linux/fs/ext2/ioctl.c + * + * Copyright (C) 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + */ + +#include <asm/segment.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/ioctl.h> +#include <linux/sched.h> + +int ext2_ioctl (struct inode * inode, struct file * filp, unsigned int cmd, + unsigned long arg) +{ + int err; + unsigned long flags; + + ext2_debug ("cmd = %u, arg = %lu\n", cmd, arg); + + switch (cmd) { + case EXT2_IOC_GETFLAGS: + if ((err = verify_area (VERIFY_WRITE, (long *) arg, sizeof(long)))) + return err; + put_fs_long (inode->u.ext2_i.i_flags, (long *) arg); + return 0; + case EXT2_IOC_SETFLAGS: + flags = get_fs_long ((long *) arg); + /* + * Only the super-user can change the IMMUTABLE flag + */ + if ((flags & EXT2_IMMUTABLE_FL) ^ + (inode->u.ext2_i.i_flags & EXT2_IMMUTABLE_FL)) { + /* This test looks nicer. Thanks to Pauline Middelink */ + if (!fsuser()) + return -EPERM; + } else + if ((current->fsuid != inode->i_uid) && !fsuser()) + return -EPERM; + if (IS_RDONLY(inode)) + return -EROFS; + inode->u.ext2_i.i_flags = flags; + if (flags & EXT2_APPEND_FL) + inode->i_flags |= S_APPEND; + else + inode->i_flags &= ~S_APPEND; + if (flags & EXT2_IMMUTABLE_FL) + inode->i_flags |= S_IMMUTABLE; + else + inode->i_flags &= ~S_IMMUTABLE; + inode->i_ctime = CURRENT_TIME; + inode->i_dirt = 1; + return 0; + case EXT2_IOC_GETVERSION: + if ((err = verify_area (VERIFY_WRITE, (long *) arg, sizeof(long)))) + return err; + put_fs_long (inode->u.ext2_i.i_version, (long *) arg); + return 0; + case EXT2_IOC_SETVERSION: + if ((current->fsuid != inode->i_uid) && !fsuser()) + return -EPERM; + if (IS_RDONLY(inode)) + return -EROFS; + inode->u.ext2_i.i_version = get_fs_long ((long *) arg); + inode->i_ctime = CURRENT_TIME; + inode->i_dirt = 1; + return 0; + default: + return -EINVAL; + } +} diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c new file mode 100644 index 000000000..f56c5404e --- /dev/null +++ b/fs/ext2/namei.c @@ -0,0 +1,1098 @@ +/* + * linux/fs/ext2/namei.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/fs/minix/namei.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#include <asm/segment.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/locks.h> + +/* + * comment out this line if you want names > EXT2_NAME_LEN chars to be + * truncated. Else they will be disallowed. + */ +/* #define NO_TRUNCATE */ + +/* + * define how far ahead to read directories while searching them. + */ +#define NAMEI_RA_CHUNKS 2 +#define NAMEI_RA_BLOCKS 4 +#define NAMEI_RA_SIZE (NAMEI_RA_CHUNKS * NAMEI_RA_BLOCKS) +#define NAMEI_RA_INDEX(c,b) (((c) * NAMEI_RA_BLOCKS) + (b)) + +/* + * NOTE! unlike strncmp, ext2_match returns 1 for success, 0 for failure. + */ +static int ext2_match (int len, const char * const name, + struct ext2_dir_entry * de) +{ + if (!de || !de->inode || len > EXT2_NAME_LEN) + return 0; + /* + * "" means "." ---> so paths like "/usr/lib//libc.a" work + */ + if (!len && de->name_len == 1 && (de->name[0] == '.') && + (de->name[1] == '\0')) + return 1; + if (len != de->name_len) + return 0; + return !memcmp(name, de->name, len); +} + +/* + * ext2_find_entry() + * + * finds an entry in the specified directory with the wanted name. It + * returns the cache buffer in which the entry was found, and the entry + * itself (as a parameter - res_dir). It does NOT read the inode of the + * entry - you'll have to do that yourself if you want to. + */ +static struct buffer_head * ext2_find_entry (struct inode * dir, + const char * const name, int namelen, + struct ext2_dir_entry ** res_dir) +{ + struct super_block * sb; + struct buffer_head * bh_use[NAMEI_RA_SIZE]; + struct buffer_head * bh_read[NAMEI_RA_SIZE]; + unsigned long offset; + int block, toread, i, err; + + *res_dir = NULL; + if (!dir) + return NULL; + sb = dir->i_sb; + +#ifdef NO_TRUNCATE + if (namelen > EXT2_NAME_LEN) + return NULL; +#else + if (namelen > EXT2_NAME_LEN) + namelen = EXT2_NAME_LEN; +#endif + + memset (bh_use, 0, sizeof (bh_use)); + toread = 0; + for (block = 0; block < NAMEI_RA_SIZE; ++block) { + struct buffer_head * bh; + + if ((block << EXT2_BLOCK_SIZE_BITS (sb)) >= dir->i_size) + break; + bh = ext2_getblk (dir, block, 0, &err); + bh_use[block] = bh; + if (bh && !bh->b_uptodate) + bh_read[toread++] = bh; + } + + block = 0; + offset = 0; + while (offset < dir->i_size) { + struct buffer_head * bh; + struct ext2_dir_entry * de; + char * dlimit; + + if ((block % NAMEI_RA_BLOCKS) == 0 && toread) { + ll_rw_block (READ, toread, bh_read); + toread = 0; + } + bh = bh_use[block % NAMEI_RA_SIZE]; + if (!bh) + ext2_panic (sb, "ext2_find_entry", + "buffer head pointer is NULL"); + wait_on_buffer (bh); + if (!bh->b_uptodate) { + /* + * read error: all bets are off + */ + break; + } + + de = (struct ext2_dir_entry *) bh->b_data; + dlimit = bh->b_data + sb->s_blocksize; + while ((char *) de < dlimit) { + if (!ext2_check_dir_entry ("ext2_find_entry", dir, + de, bh, offset)) + goto failure; + if (de->inode != 0 && ext2_match (namelen, name, de)) { + for (i = 0; i < NAMEI_RA_SIZE; ++i) { + if (bh_use[i] != bh) + brelse (bh_use[i]); + } + *res_dir = de; + return bh; + } + offset += de->rec_len; + de = (struct ext2_dir_entry *) + ((char *) de + de->rec_len); + } + + brelse (bh); + if (((block + NAMEI_RA_SIZE) << EXT2_BLOCK_SIZE_BITS (sb)) >= + dir->i_size) + bh = NULL; + else + bh = ext2_getblk (dir, block + NAMEI_RA_SIZE, 0, &err); + bh_use[block++ % NAMEI_RA_SIZE] = bh; + if (bh && !bh->b_uptodate) + bh_read[toread++] = bh; + } + +failure: + for (i = 0; i < NAMEI_RA_SIZE; ++i) + brelse (bh_use[i]); + return NULL; +} + +int ext2_lookup (struct inode * dir, const char * name, int len, + struct inode ** result) +{ + unsigned long ino; + struct ext2_dir_entry * de; + struct buffer_head * bh; + + *result = NULL; + if (!dir) + return -ENOENT; + if (!S_ISDIR(dir->i_mode)) { + iput (dir); + return -ENOENT; + } + if (dcache_lookup(dir, name, len, &ino)) { + if (!ino) { + iput(dir); + return -ENOENT; + } + if (!(*result = iget (dir->i_sb, ino))) { + iput (dir); + return -EACCES; + } + iput (dir); + return 0; + } + ino = dir->i_version; + if (!(bh = ext2_find_entry (dir, name, len, &de))) { + if (ino == dir->i_version) + dcache_add(dir, name, len, 0); + iput (dir); + return -ENOENT; + } + ino = de->inode; + dcache_add(dir, name, len, ino); + brelse (bh); + if (!(*result = iget (dir->i_sb, ino))) { + iput (dir); + return -EACCES; + } + iput (dir); + return 0; +} + +/* + * ext2_add_entry() + * + * adds a file entry to the specified directory, using the same + * semantics as ext2_find_entry(). It returns NULL if it failed. + * + * NOTE!! The inode part of 'de' is left at 0 - which means you + * may not sleep between calling this and putting something into + * the entry, as someone else might have used it while you slept. + */ +static struct buffer_head * ext2_add_entry (struct inode * dir, + const char * name, int namelen, + struct ext2_dir_entry ** res_dir, + int *err) +{ + unsigned long offset; + unsigned short rec_len; + struct buffer_head * bh; + struct ext2_dir_entry * de, * de1; + struct super_block * sb; + + *err = -EINVAL; + *res_dir = NULL; + if (!dir) + return NULL; + sb = dir->i_sb; +#ifdef NO_TRUNCATE + if (namelen > EXT2_NAME_LEN) + return NULL; +#else + if (namelen > EXT2_NAME_LEN) + namelen = EXT2_NAME_LEN; +#endif + if (!namelen) + return NULL; + /* + * Is this a busy deleted directory? Can't create new files if so + */ + if (dir->i_size == 0) + { + *err = -ENOENT; + return NULL; + } + bh = ext2_bread (dir, 0, 0, err); + if (!bh) + return NULL; + rec_len = EXT2_DIR_REC_LEN(namelen); + offset = 0; + de = (struct ext2_dir_entry *) bh->b_data; + *err = -ENOSPC; + while (1) { + if ((char *)de >= sb->s_blocksize + bh->b_data) { + brelse (bh); + bh = NULL; + bh = ext2_bread (dir, offset >> EXT2_BLOCK_SIZE_BITS(sb), 1, err); + if (!bh) + return NULL; + if (dir->i_size <= offset) { + if (dir->i_size == 0) { + *err = -ENOENT; + return NULL; + } + + ext2_debug ("creating next block\n"); + + de = (struct ext2_dir_entry *) bh->b_data; + de->inode = 0; + de->rec_len = sb->s_blocksize; + dir->i_size = offset + sb->s_blocksize; + dir->i_dirt = 1; + } else { + + ext2_debug ("skipping to next block\n"); + + de = (struct ext2_dir_entry *) bh->b_data; + } + } + if (!ext2_check_dir_entry ("ext2_add_entry", dir, de, bh, + offset)) { + *err = -ENOENT; + brelse (bh); + return NULL; + } + if (de->inode != 0 && ext2_match (namelen, name, de)) { + *err = -EEXIST; + brelse (bh); + return NULL; + } + if ((de->inode == 0 && de->rec_len >= rec_len) || + (de->rec_len >= EXT2_DIR_REC_LEN(de->name_len) + rec_len)) { + offset += de->rec_len; + if (de->inode) { + de1 = (struct ext2_dir_entry *) ((char *) de + + EXT2_DIR_REC_LEN(de->name_len)); + de1->rec_len = de->rec_len - + EXT2_DIR_REC_LEN(de->name_len); + de->rec_len = EXT2_DIR_REC_LEN(de->name_len); + de = de1; + } + de->inode = 0; + de->name_len = namelen; + memcpy (de->name, name, namelen); + /* + * XXX shouldn't update any times until successful + * completion of syscall, but too many callers depend + * on this. + * + * XXX similarly, too many callers depend on + * ext2_new_inode() setting the times, but error + * recovery deletes the inode, so the worst that can + * happen is that the times are slightly out of date + * and/or different from the directory change time. + */ + dir->i_mtime = dir->i_ctime = CURRENT_TIME; + dir->i_dirt = 1; + dir->i_version = ++event; + mark_buffer_dirty(bh, 1); + *res_dir = de; + *err = 0; + return bh; + } + offset += de->rec_len; + de = (struct ext2_dir_entry *) ((char *) de + de->rec_len); + } + brelse (bh); + return NULL; +} + +/* + * ext2_delete_entry deletes a directory entry by merging it with the + * previous entry + */ +static int ext2_delete_entry (struct ext2_dir_entry * dir, + struct buffer_head * bh) +{ + struct ext2_dir_entry * de, * pde; + int i; + + i = 0; + pde = NULL; + de = (struct ext2_dir_entry *) bh->b_data; + while (i < bh->b_size) { + if (!ext2_check_dir_entry ("ext2_delete_entry", NULL, + de, bh, i)) + return -EIO; + if (de == dir) { + if (pde) + pde->rec_len += dir->rec_len; + dir->inode = 0; + return 0; + } + i += de->rec_len; + pde = de; + de = (struct ext2_dir_entry *) ((char *) de + de->rec_len); + } + return -ENOENT; +} + +int ext2_create (struct inode * dir,const char * name, int len, int mode, + struct inode ** result) +{ + struct inode * inode; + struct buffer_head * bh; + struct ext2_dir_entry * de; + int err; + + *result = NULL; + if (!dir) + return -ENOENT; + inode = ext2_new_inode (dir, mode); + if (!inode) { + iput (dir); + return -ENOSPC; + } + inode->i_op = &ext2_file_inode_operations; + inode->i_mode = mode; + inode->i_dirt = 1; + bh = ext2_add_entry (dir, name, len, &de, &err); + if (!bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput (inode); + iput (dir); + return err; + } + de->inode = inode->i_ino; + dir->i_version = ++event; + dcache_add(dir, de->name, de->name_len, de->inode); + mark_buffer_dirty(bh, 1); + if (IS_SYNC(dir)) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + brelse (bh); + iput (dir); + *result = inode; + return 0; +} + +int ext2_mknod (struct inode * dir, const char * name, int len, int mode, + int rdev) +{ + struct inode * inode; + struct buffer_head * bh; + struct ext2_dir_entry * de; + int err; + + if (!dir) + return -ENOENT; + bh = ext2_find_entry (dir, name, len, &de); + if (bh) { + brelse (bh); + iput (dir); + return -EEXIST; + } + inode = ext2_new_inode (dir, mode); + if (!inode) { + iput (dir); + return -ENOSPC; + } + inode->i_uid = current->fsuid; + inode->i_mode = mode; + inode->i_op = NULL; + if (S_ISREG(inode->i_mode)) + inode->i_op = &ext2_file_inode_operations; + else if (S_ISDIR(inode->i_mode)) { + inode->i_op = &ext2_dir_inode_operations; + if (dir->i_mode & S_ISGID) + inode->i_mode |= S_ISGID; + } + else if (S_ISLNK(inode->i_mode)) + inode->i_op = &ext2_symlink_inode_operations; + else if (S_ISCHR(inode->i_mode)) + inode->i_op = &chrdev_inode_operations; + else if (S_ISBLK(inode->i_mode)) + inode->i_op = &blkdev_inode_operations; + else if (S_ISFIFO(inode->i_mode)) + init_fifo(inode); + if (S_ISBLK(mode) || S_ISCHR(mode)) + inode->i_rdev = rdev; + inode->i_dirt = 1; + bh = ext2_add_entry (dir, name, len, &de, &err); + if (!bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput (inode); + iput (dir); + return err; + } + de->inode = inode->i_ino; + dir->i_version = ++event; + dcache_add(dir, de->name, de->name_len, de->inode); + mark_buffer_dirty(bh, 1); + if (IS_SYNC(dir)) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + brelse (bh); + iput (dir); + iput (inode); + return 0; +} + +int ext2_mkdir (struct inode * dir, const char * name, int len, int mode) +{ + struct inode * inode; + struct buffer_head * bh, * dir_block; + struct ext2_dir_entry * de; + int err; + + if (!dir) + return -ENOENT; + bh = ext2_find_entry (dir, name, len, &de); + if (bh) { + brelse (bh); + iput (dir); + return -EEXIST; + } + if (dir->i_nlink >= EXT2_LINK_MAX) { + iput (dir); + return -EMLINK; + } + inode = ext2_new_inode (dir, S_IFDIR); + if (!inode) { + iput (dir); + return -ENOSPC; + } + inode->i_op = &ext2_dir_inode_operations; + inode->i_size = inode->i_sb->s_blocksize; + dir_block = ext2_bread (inode, 0, 1, &err); + if (!dir_block) { + iput (dir); + inode->i_nlink--; + inode->i_dirt = 1; + iput (inode); + return err; + } + inode->i_blocks = inode->i_sb->s_blocksize / 512; + de = (struct ext2_dir_entry *) dir_block->b_data; + de->inode = inode->i_ino; + de->name_len = 1; + de->rec_len = EXT2_DIR_REC_LEN(de->name_len); + strcpy (de->name, "."); + de = (struct ext2_dir_entry *) ((char *) de + de->rec_len); + de->inode = dir->i_ino; + de->rec_len = inode->i_sb->s_blocksize - EXT2_DIR_REC_LEN(1); + de->name_len = 2; + strcpy (de->name, ".."); + inode->i_nlink = 2; + mark_buffer_dirty(dir_block, 1); + brelse (dir_block); + inode->i_mode = S_IFDIR | (mode & S_IRWXUGO & ~current->fs->umask); + if (dir->i_mode & S_ISGID) + inode->i_mode |= S_ISGID; + inode->i_dirt = 1; + bh = ext2_add_entry (dir, name, len, &de, &err); + if (!bh) { + iput (dir); + inode->i_nlink = 0; + inode->i_dirt = 1; + iput (inode); + return err; + } + de->inode = inode->i_ino; + dir->i_version = ++event; + dcache_add(dir, de->name, de->name_len, de->inode); + mark_buffer_dirty(bh, 1); + if (IS_SYNC(dir)) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + dir->i_nlink++; + dir->i_dirt = 1; + iput (dir); + iput (inode); + brelse (bh); + return 0; +} + +/* + * routine to check that the specified directory is empty (for rmdir) + */ +static int empty_dir (struct inode * inode) +{ + unsigned long offset; + struct buffer_head * bh; + struct ext2_dir_entry * de, * de1; + struct super_block * sb; + int err; + + sb = inode->i_sb; + if (inode->i_size < EXT2_DIR_REC_LEN(1) + EXT2_DIR_REC_LEN(2) || + !(bh = ext2_bread (inode, 0, 0, &err))) { + ext2_warning (inode->i_sb, "empty_dir", + "bad directory (dir %lu)", inode->i_ino); + return 1; + } + de = (struct ext2_dir_entry *) bh->b_data; + de1 = (struct ext2_dir_entry *) ((char *) de + de->rec_len); + if (de->inode != inode->i_ino || !de1->inode || + strcmp (".", de->name) || strcmp ("..", de1->name)) { + ext2_warning (inode->i_sb, "empty_dir", + "bad directory (dir %lu)", inode->i_ino); + return 1; + } + offset = de->rec_len + de1->rec_len; + de = (struct ext2_dir_entry *) ((char *) de1 + de1->rec_len); + while (offset < inode->i_size ) { + if ((void *) de >= (void *) (bh->b_data + sb->s_blocksize)) { + brelse (bh); + bh = ext2_bread (inode, offset >> EXT2_BLOCK_SIZE_BITS(sb), 1, &err); + if (!bh) { + offset += sb->s_blocksize; + continue; + } + de = (struct ext2_dir_entry *) bh->b_data; + } + if (!ext2_check_dir_entry ("empty_dir", inode, de, bh, + offset)) { + brelse (bh); + return 1; + } + if (de->inode) { + brelse (bh); + return 0; + } + offset += de->rec_len; + de = (struct ext2_dir_entry *) ((char *) de + de->rec_len); + } + brelse (bh); + return 1; +} + +int ext2_rmdir (struct inode * dir, const char * name, int len) +{ + int retval; + struct inode * inode; + struct buffer_head * bh; + struct ext2_dir_entry * de; + +repeat: + if (!dir) + return -ENOENT; + inode = NULL; + bh = ext2_find_entry (dir, name, len, &de); + retval = -ENOENT; + if (!bh) + goto end_rmdir; + retval = -EPERM; + if (!(inode = iget (dir->i_sb, de->inode))) + goto end_rmdir; + if (inode->i_dev != dir->i_dev) + goto end_rmdir; + if (de->inode != inode->i_ino) { + iput(inode); + brelse(bh); + current->counter = 0; + schedule(); + goto repeat; + } + if ((dir->i_mode & S_ISVTX) && !fsuser() && + current->fsuid != inode->i_uid && + current->fsuid != dir->i_uid) + goto end_rmdir; + if (inode == dir) /* we may not delete ".", but "../dir" is ok */ + goto end_rmdir; + if (!S_ISDIR(inode->i_mode)) { + retval = -ENOTDIR; + goto end_rmdir; + } + down(&inode->i_sem); + if (!empty_dir (inode)) + retval = -ENOTEMPTY; + else if (de->inode != inode->i_ino) + retval = -ENOENT; + else { + if (inode->i_count > 1) { + /* + * Are we deleting the last instance of a busy directory? + * Better clean up if so. + * + * Make directory empty (it will be truncated when finally + * dereferenced). This also inhibits ext2_add_entry. + */ + inode->i_size = 0; + } + retval = ext2_delete_entry (de, bh); + dir->i_version = ++event; + } + up(&inode->i_sem); + if (retval) + goto end_rmdir; + mark_buffer_dirty(bh, 1); + if (IS_SYNC(dir)) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + if (inode->i_nlink != 2) + ext2_warning (inode->i_sb, "ext2_rmdir", + "empty directory has nlink!=2 (%d)", + inode->i_nlink); + inode->i_version = ++event; + inode->i_nlink = 0; + inode->i_dirt = 1; + dir->i_nlink--; + inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->i_dirt = 1; +end_rmdir: + iput (dir); + iput (inode); + brelse (bh); + return retval; +} + +int ext2_unlink (struct inode * dir, const char * name, int len) +{ + int retval; + struct inode * inode; + struct buffer_head * bh; + struct ext2_dir_entry * de; + +repeat: + if (!dir) + return -ENOENT; + retval = -ENOENT; + inode = NULL; + bh = ext2_find_entry (dir, name, len, &de); + if (!bh) + goto end_unlink; + if (!(inode = iget (dir->i_sb, de->inode))) + goto end_unlink; + retval = -EPERM; + if (S_ISDIR(inode->i_mode)) + goto end_unlink; + if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) + goto end_unlink; + if (de->inode != inode->i_ino) { + iput(inode); + brelse(bh); + current->counter = 0; + schedule(); + goto repeat; + } + if ((dir->i_mode & S_ISVTX) && !fsuser() && + current->fsuid != inode->i_uid && + current->fsuid != dir->i_uid) + goto end_unlink; + if (!inode->i_nlink) { + ext2_warning (inode->i_sb, "ext2_unlink", + "Deleting nonexistent file (%lu), %d", + inode->i_ino, inode->i_nlink); + inode->i_nlink = 1; + } + retval = ext2_delete_entry (de, bh); + if (retval) + goto end_unlink; + dir->i_version = ++event; + mark_buffer_dirty(bh, 1); + if (IS_SYNC(dir)) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->i_dirt = 1; + inode->i_nlink--; + inode->i_dirt = 1; + inode->i_ctime = dir->i_ctime; + retval = 0; +end_unlink: + brelse (bh); + iput (inode); + iput (dir); + return retval; +} + +int ext2_symlink (struct inode * dir, const char * name, int len, + const char * symname) +{ + struct ext2_dir_entry * de; + struct inode * inode = NULL; + struct buffer_head * bh = NULL, * name_block = NULL; + char * link; + int i, err; + int l; + char c; + + if (!(inode = ext2_new_inode (dir, S_IFLNK))) { + iput (dir); + return -ENOSPC; + } + inode->i_mode = S_IFLNK | S_IRWXUGO; + inode->i_op = &ext2_symlink_inode_operations; + for (l = 0; l < inode->i_sb->s_blocksize - 1 && + symname [l]; l++) + ; + if (l >= EXT2_N_BLOCKS * sizeof (unsigned long)) { + + ext2_debug ("l=%d, normal symlink\n", l); + + name_block = ext2_bread (inode, 0, 1, &err); + if (!name_block) { + iput (dir); + inode->i_nlink--; + inode->i_dirt = 1; + iput (inode); + return err; + } + link = name_block->b_data; + } else { + link = (char *) inode->u.ext2_i.i_data; + + ext2_debug ("l=%d, fast symlink\n", l); + + } + i = 0; + while (i < inode->i_sb->s_blocksize - 1 && (c = *(symname++))) + link[i++] = c; + link[i] = 0; + if (name_block) { + mark_buffer_dirty(name_block, 1); + brelse (name_block); + } + inode->i_size = i; + inode->i_dirt = 1; + bh = ext2_find_entry (dir, name, len, &de); + if (bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput (inode); + brelse (bh); + iput (dir); + return -EEXIST; + } + bh = ext2_add_entry (dir, name, len, &de, &err); + if (!bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput (inode); + iput (dir); + return err; + } + de->inode = inode->i_ino; + dir->i_version = ++event; + dcache_add(dir, de->name, de->name_len, de->inode); + mark_buffer_dirty(bh, 1); + if (IS_SYNC(dir)) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + brelse (bh); + iput (dir); + iput (inode); + return 0; +} + +int ext2_link (struct inode * oldinode, struct inode * dir, + const char * name, int len) +{ + struct ext2_dir_entry * de; + struct buffer_head * bh; + int err; + + if (S_ISDIR(oldinode->i_mode)) { + iput (oldinode); + iput (dir); + return -EPERM; + } + if (IS_APPEND(oldinode) || IS_IMMUTABLE(oldinode)) { + iput (oldinode); + iput (dir); + return -EPERM; + } + if (oldinode->i_nlink >= EXT2_LINK_MAX) { + iput (oldinode); + iput (dir); + return -EMLINK; + } + bh = ext2_find_entry (dir, name, len, &de); + if (bh) { + brelse (bh); + iput (dir); + iput (oldinode); + return -EEXIST; + } + bh = ext2_add_entry (dir, name, len, &de, &err); + if (!bh) { + iput (dir); + iput (oldinode); + return err; + } + de->inode = oldinode->i_ino; + dir->i_version = ++event; + dcache_add(dir, de->name, de->name_len, de->inode); + mark_buffer_dirty(bh, 1); + if (IS_SYNC(dir)) { + ll_rw_block (WRITE, 1, &bh); + wait_on_buffer (bh); + } + brelse (bh); + iput (dir); + oldinode->i_nlink++; + oldinode->i_ctime = CURRENT_TIME; + oldinode->i_dirt = 1; + iput (oldinode); + return 0; +} + +static int subdir (struct inode * new_inode, struct inode * old_inode) +{ + int ino; + int result; + + new_inode->i_count++; + result = 0; + for (;;) { + if (new_inode == old_inode) { + result = 1; + break; + } + if (new_inode->i_dev != old_inode->i_dev) + break; + ino = new_inode->i_ino; + if (ext2_lookup (new_inode, "..", 2, &new_inode)) + break; + if (new_inode->i_ino == ino) + break; + } + iput (new_inode); + return result; +} + +#define PARENT_INO(buffer) \ + ((struct ext2_dir_entry *) ((char *) buffer + \ + ((struct ext2_dir_entry *) buffer)->rec_len))->inode + +#define PARENT_NAME(buffer) \ + ((struct ext2_dir_entry *) ((char *) buffer + \ + ((struct ext2_dir_entry *) buffer)->rec_len))->name + +/* + * rename uses retrying to avoid race-conditions: at least they should be + * minimal. + * it tries to allocate all the blocks, then sanity-checks, and if the sanity- + * checks fail, it tries to restart itself again. Very practical - no changes + * are done until we know everything works ok.. and then all the changes can be + * done in one fell swoop when we have claimed all the buffers needed. + * + * Anybody can rename anything with this: the permission checks are left to the + * higher-level routines. + */ +static int do_ext2_rename (struct inode * old_dir, const char * old_name, + int old_len, struct inode * new_dir, + const char * new_name, int new_len) +{ + struct inode * old_inode, * new_inode; + struct buffer_head * old_bh, * new_bh, * dir_bh; + struct ext2_dir_entry * old_de, * new_de; + int retval; + + goto start_up; +try_again: + if (new_bh && new_de) { + ext2_delete_entry(new_de, new_bh); + new_dir->i_version = ++event; + } + brelse (old_bh); + brelse (new_bh); + brelse (dir_bh); + iput (old_inode); + iput (new_inode); + current->counter = 0; + schedule (); +start_up: + old_inode = new_inode = NULL; + old_bh = new_bh = dir_bh = NULL; + new_de = NULL; + old_bh = ext2_find_entry (old_dir, old_name, old_len, &old_de); + retval = -ENOENT; + if (!old_bh) + goto end_rename; + old_inode = __iget (old_dir->i_sb, old_de->inode, 0); /* don't cross mnt-points */ + if (!old_inode) + goto end_rename; + retval = -EPERM; + if ((old_dir->i_mode & S_ISVTX) && + current->fsuid != old_inode->i_uid && + current->fsuid != old_dir->i_uid && !fsuser()) + goto end_rename; + if (IS_APPEND(old_inode) || IS_IMMUTABLE(old_inode)) + goto end_rename; + new_bh = ext2_find_entry (new_dir, new_name, new_len, &new_de); + if (new_bh) { + new_inode = __iget (new_dir->i_sb, new_de->inode, 0); /* no mntp cross */ + if (!new_inode) { + brelse (new_bh); + new_bh = NULL; + } + } + if (new_inode == old_inode) { + retval = 0; + goto end_rename; + } + if (new_inode && S_ISDIR(new_inode->i_mode)) { + retval = -EISDIR; + if (!S_ISDIR(old_inode->i_mode)) + goto end_rename; + retval = -EINVAL; + if (subdir (new_dir, old_inode)) + goto end_rename; + retval = -ENOTEMPTY; + if (!empty_dir (new_inode)) + goto end_rename; + retval = -EBUSY; + if (new_inode->i_count > 1) + goto end_rename; + } + retval = -EPERM; + if (new_inode && (new_dir->i_mode & S_ISVTX) && + current->fsuid != new_inode->i_uid && + current->fsuid != new_dir->i_uid && !fsuser()) + goto end_rename; + if (S_ISDIR(old_inode->i_mode)) { + retval = -ENOTDIR; + if (new_inode && !S_ISDIR(new_inode->i_mode)) + goto end_rename; + retval = -EINVAL; + if (subdir (new_dir, old_inode)) + goto end_rename; + dir_bh = ext2_bread (old_inode, 0, 0, &retval); + if (!dir_bh) + goto end_rename; + if (PARENT_INO(dir_bh->b_data) != old_dir->i_ino) + goto end_rename; + retval = -EMLINK; + if (!new_inode && new_dir->i_nlink >= EXT2_LINK_MAX) + goto end_rename; + } + if (!new_bh) + new_bh = ext2_add_entry (new_dir, new_name, new_len, &new_de, + &retval); + if (!new_bh) + goto end_rename; + new_dir->i_version = ++event; + /* + * sanity checking before doing the rename - avoid races + */ + if (new_inode && (new_de->inode != new_inode->i_ino)) + goto try_again; + if (new_de->inode && !new_inode) + goto try_again; + if (old_de->inode != old_inode->i_ino) + goto try_again; + /* + * ok, that's it + */ + new_de->inode = old_inode->i_ino; + dcache_add(new_dir, new_de->name, new_de->name_len, new_de->inode); + retval = ext2_delete_entry (old_de, old_bh); + if (retval == -ENOENT) + goto try_again; + if (retval) + goto end_rename; + old_dir->i_version = ++event; + if (new_inode) { + new_inode->i_nlink--; + new_inode->i_ctime = CURRENT_TIME; + new_inode->i_dirt = 1; + } + old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME; + old_dir->i_dirt = 1; + if (dir_bh) { + PARENT_INO(dir_bh->b_data) = new_dir->i_ino; + dcache_add(old_inode, "..", 2, new_dir->i_ino); + mark_buffer_dirty(dir_bh, 1); + old_dir->i_nlink--; + old_dir->i_dirt = 1; + if (new_inode) { + new_inode->i_nlink--; + new_inode->i_dirt = 1; + } else { + new_dir->i_nlink++; + new_dir->i_dirt = 1; + } + } + mark_buffer_dirty(old_bh, 1); + if (IS_SYNC(old_dir)) { + ll_rw_block (WRITE, 1, &old_bh); + wait_on_buffer (old_bh); + } + mark_buffer_dirty(new_bh, 1); + if (IS_SYNC(new_dir)) { + ll_rw_block (WRITE, 1, &new_bh); + wait_on_buffer (new_bh); + } + retval = 0; +end_rename: + brelse (dir_bh); + brelse (old_bh); + brelse (new_bh); + iput (old_inode); + iput (new_inode); + iput (old_dir); + iput (new_dir); + return retval; +} + +/* + * Ok, rename also locks out other renames, as they can change the parent of + * a directory, and we don't want any races. Other races are checked for by + * "do_rename()", which restarts if there are inconsistencies. + * + * Note that there is no race between different filesystems: it's only within + * the same device that races occur: many renames can happen at once, as long + * as they are on different partitions. + * + * In the second extended file system, we use a lock flag stored in the memory + * super-block. This way, we really lock other renames only if they occur + * on the same file system + */ +int ext2_rename (struct inode * old_dir, const char * old_name, int old_len, + struct inode * new_dir, const char * new_name, int new_len) +{ + int result; + + while (old_dir->i_sb->u.ext2_sb.s_rename_lock) + sleep_on (&old_dir->i_sb->u.ext2_sb.s_rename_wait); + old_dir->i_sb->u.ext2_sb.s_rename_lock = 1; + result = do_ext2_rename (old_dir, old_name, old_len, new_dir, + new_name, new_len); + old_dir->i_sb->u.ext2_sb.s_rename_lock = 0; + wake_up (&old_dir->i_sb->u.ext2_sb.s_rename_wait); + return result; +} diff --git a/fs/ext2/super.c b/fs/ext2/super.c new file mode 100644 index 000000000..37fae41ad --- /dev/null +++ b/fs/ext2/super.c @@ -0,0 +1,755 @@ +/* + * linux/fs/ext2/super.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/fs/minix/inode.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#include <stdarg.h> + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/malloc.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/locks.h> + +void ext2_error (struct super_block * sb, const char * function, + const char * fmt, ...) +{ + char buf[1024]; + va_list args; + + if (!(sb->s_flags & MS_RDONLY)) { + sb->u.ext2_sb.s_mount_state |= EXT2_ERROR_FS; + sb->u.ext2_sb.s_es->s_state |= EXT2_ERROR_FS; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + sb->s_dirt = 1; + } + va_start (args, fmt); + vsprintf (buf, fmt, args); + va_end (args); + if (test_opt (sb, ERRORS_PANIC) || + (sb->u.ext2_sb.s_es->s_errors == EXT2_ERRORS_PANIC && + !test_opt (sb, ERRORS_CONT) && !test_opt (sb, ERRORS_RO))) + panic ("EXT2-fs panic (device %d/%d): %s: %s\n", + MAJOR(sb->s_dev), MINOR(sb->s_dev), function, buf); + printk (KERN_CRIT "EXT2-fs error (device %d/%d): %s: %s\n", + MAJOR(sb->s_dev), MINOR(sb->s_dev), function, buf); + if (test_opt (sb, ERRORS_RO) || + (sb->u.ext2_sb.s_es->s_errors == EXT2_ERRORS_RO && + !test_opt (sb, ERRORS_CONT) && !test_opt (sb, ERRORS_PANIC))) { + printk ("Remounting filesystem read-only\n"); + sb->s_flags |= MS_RDONLY; + } +} + +NORET_TYPE void ext2_panic (struct super_block * sb, const char * function, + const char * fmt, ...) +{ + char buf[1024]; + va_list args; + + if (!(sb->s_flags & MS_RDONLY)) { + sb->u.ext2_sb.s_mount_state |= EXT2_ERROR_FS; + sb->u.ext2_sb.s_es->s_state |= EXT2_ERROR_FS; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + sb->s_dirt = 1; + } + va_start (args, fmt); + vsprintf (buf, fmt, args); + va_end (args); + panic ("EXT2-fs panic (device %d/%d): %s: %s\n", + MAJOR(sb->s_dev), MINOR(sb->s_dev), function, buf); +} + +void ext2_warning (struct super_block * sb, const char * function, + const char * fmt, ...) +{ + char buf[1024]; + va_list args; + + va_start (args, fmt); + vsprintf (buf, fmt, args); + va_end (args); + printk (KERN_WARNING "EXT2-fs warning (device %d/%d): %s: %s\n", + MAJOR(sb->s_dev), MINOR(sb->s_dev), function, buf); +} + +void ext2_put_super (struct super_block * sb) +{ + int db_count; + int i; + + lock_super (sb); + if (!(sb->s_flags & MS_RDONLY)) { + sb->u.ext2_sb.s_es->s_state = sb->u.ext2_sb.s_mount_state; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + } + sb->s_dev = 0; + db_count = sb->u.ext2_sb.s_db_per_group; + for (i = 0; i < db_count; i++) + if (sb->u.ext2_sb.s_group_desc[i]) + brelse (sb->u.ext2_sb.s_group_desc[i]); + kfree_s (sb->u.ext2_sb.s_group_desc, + db_count * sizeof (struct buffer_head *)); + for (i = 0; i < EXT2_MAX_GROUP_LOADED; i++) + if (sb->u.ext2_sb.s_inode_bitmap[i]) + brelse (sb->u.ext2_sb.s_inode_bitmap[i]); + for (i = 0; i < EXT2_MAX_GROUP_LOADED; i++) + if (sb->u.ext2_sb.s_block_bitmap[i]) + brelse (sb->u.ext2_sb.s_block_bitmap[i]); + brelse (sb->u.ext2_sb.s_sbh); + unlock_super (sb); + return; +} + +static struct super_operations ext2_sops = { + ext2_read_inode, + NULL, + ext2_write_inode, + ext2_put_inode, + ext2_put_super, + ext2_write_super, + ext2_statfs, + ext2_remount +}; + +#ifdef EXT2FS_PRE_02B_COMPAT + +static int convert_pre_02b_fs (struct super_block * sb, + struct buffer_head * bh) +{ + struct ext2_super_block * es; + struct ext2_old_group_desc old_group_desc [BLOCK_SIZE / sizeof (struct ext2_old_group_desc)]; + struct ext2_group_desc * gdp; + struct buffer_head * bh2; + int groups_count; + int i; + + es = (struct ext2_super_block *) bh->b_data; + bh2 = bread (sb->s_dev, 2, BLOCK_SIZE); + if (!bh2) { + printk ("Cannot read descriptor blocks while converting !\n"); + return 0; + } + memcpy (old_group_desc, bh2->b_data, BLOCK_SIZE); + groups_count = (sb->u.ext2_sb.s_blocks_count - + sb->u.ext2_sb.s_first_data_block + + (EXT2_BLOCK_SIZE(sb) * 8) - 1) / + (EXT2_BLOCK_SIZE(sb) * 8); + memset (bh2->b_data, 0, BLOCK_SIZE); + gdp = (struct ext2_group_desc *) bh2->b_data; + for (i = 0; i < groups_count; i++) { + gdp[i].bg_block_bitmap = old_group_desc[i].bg_block_bitmap; + gdp[i].bg_inode_bitmap = old_group_desc[i].bg_inode_bitmap; + gdp[i].bg_inode_table = old_group_desc[i].bg_inode_table; + gdp[i].bg_free_blocks_count = old_group_desc[i].bg_free_blocks_count; + gdp[i].bg_free_inodes_count = old_group_desc[i].bg_free_inodes_count; + } + mark_buffer_dirty(bh2, 1); + brelse (bh2); + es->s_magic = EXT2_SUPER_MAGIC; + mark_buffer_dirty(bh, 1); + sb->s_magic = EXT2_SUPER_MAGIC; + return 1; +} + +#endif + +/* + * This function has been shamelessly adapted from the msdos fs + */ +static int parse_options (char * options, unsigned long * sb_block, + unsigned short *resuid, unsigned short * resgid, + unsigned long * mount_options) +{ + char * this_char; + char * value; + + if (!options) + return 1; + for (this_char = strtok (options, ","); + this_char != NULL; + this_char = strtok (NULL, ",")) { + if ((value = strchr (this_char, '=')) != NULL) + *value++ = 0; + if (!strcmp (this_char, "bsddf")) + clear_opt (*mount_options, MINIX_DF); + else if (!strcmp (this_char, "check")) { + if (!value || !*value) + set_opt (*mount_options, CHECK_NORMAL); + else if (!strcmp (value, "none")) { + clear_opt (*mount_options, CHECK_NORMAL); + clear_opt (*mount_options, CHECK_STRICT); + } + else if (strcmp (value, "normal")) + set_opt (*mount_options, CHECK_NORMAL); + else if (strcmp (value, "strict")) { + set_opt (*mount_options, CHECK_NORMAL); + set_opt (*mount_options, CHECK_STRICT); + } + else { + printk ("EXT2-fs: Invalid check option: %s\n", + value); + return 0; + } + } + else if (!strcmp (this_char, "debug")) + set_opt (*mount_options, DEBUG); + else if (!strcmp (this_char, "errors")) { + if (!value || !*value) { + printk ("EXT2-fs: the errors option requires " + "an argument"); + return 0; + } + if (!strcmp (value, "continue")) { + clear_opt (*mount_options, ERRORS_RO); + clear_opt (*mount_options, ERRORS_PANIC); + set_opt (*mount_options, ERRORS_CONT); + } + else if (!strcmp (value, "remount-ro")) { + clear_opt (*mount_options, ERRORS_CONT); + clear_opt (*mount_options, ERRORS_PANIC); + set_opt (*mount_options, ERRORS_RO); + } + else if (!strcmp (value, "panic")) { + clear_opt (*mount_options, ERRORS_CONT); + clear_opt (*mount_options, ERRORS_RO); + set_opt (*mount_options, ERRORS_PANIC); + } + else { + printk ("EXT2-fs: Invalid errors option: %s\n", + value); + return 0; + } + } + else if (!strcmp (this_char, "grpid") || + !strcmp (this_char, "bsdgroups")) + set_opt (*mount_options, GRPID); + else if (!strcmp (this_char, "minixdf")) + set_opt (*mount_options, MINIX_DF); + else if (!strcmp (this_char, "nocheck")) { + clear_opt (*mount_options, CHECK_NORMAL); + clear_opt (*mount_options, CHECK_STRICT); + } + else if (!strcmp (this_char, "nogrpid") || + !strcmp (this_char, "sysvgroups")) + clear_opt (*mount_options, GRPID); + else if (!strcmp (this_char, "resgid")) { + if (!value || !*value) { + printk ("EXT2-fs: the resgid option requires " + "an argument"); + return 0; + } + *resgid = simple_strtoul (value, &value, 0); + if (*value) { + printk ("EXT2-fs: Invalid resgid option: %s\n", + value); + return 0; + } + } + else if (!strcmp (this_char, "resuid")) { + if (!value || !*value) { + printk ("EXT2-fs: the resuid option requires " + "an argument"); + return 0; + } + *resuid = simple_strtoul (value, &value, 0); + if (*value) { + printk ("EXT2-fs: Invalid resuid option: %s\n", + value); + return 0; + } + } + else if (!strcmp (this_char, "sb")) { + if (!value || !*value) { + printk ("EXT2-fs: the sb option requires " + "an argument"); + return 0; + } + *sb_block = simple_strtoul (value, &value, 0); + if (*value) { + printk ("EXT2-fs: Invalid sb option: %s\n", + value); + return 0; + } + } + else { + printk ("EXT2-fs: Unrecognized mount option %s\n", this_char); + return 0; + } + } + return 1; +} + +static void ext2_setup_super (struct super_block * sb, + struct ext2_super_block * es) +{ + if (es->s_rev_level > EXT2_CURRENT_REV) { + printk ("EXT2-fs warning: revision level too high, " + "forcing read/only mode\n"); + sb->s_flags |= MS_RDONLY; + } + if (!(sb->s_flags & MS_RDONLY)) { + if (!(sb->u.ext2_sb.s_mount_state & EXT2_VALID_FS)) + printk ("EXT2-fs warning: mounting unchecked fs, " + "running e2fsck is recommended\n"); + else if ((sb->u.ext2_sb.s_mount_state & EXT2_ERROR_FS)) + printk ("EXT2-fs warning: mounting fs with errors, " + "running e2fsck is recommended\n"); + else if (es->s_max_mnt_count >= 0 && + es->s_mnt_count >= (unsigned short) es->s_max_mnt_count) + printk ("EXT2-fs warning: maximal mount count reached, " + "running e2fsck is recommended\n"); + else if (es->s_checkinterval && + (es->s_lastcheck + es->s_checkinterval <= CURRENT_TIME)) + printk ("EXT2-fs warning: checktime reached, " + "running e2fsck is recommended\n"); + es->s_state &= ~EXT2_VALID_FS; + if (!es->s_max_mnt_count) + es->s_max_mnt_count = EXT2_DFL_MAX_MNT_COUNT; + es->s_mnt_count++; + es->s_mtime = CURRENT_TIME; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + sb->s_dirt = 1; + if (test_opt (sb, DEBUG)) + printk ("[EXT II FS %s, %s, bs=%lu, fs=%lu, gc=%lu, " + "bpg=%lu, ipg=%lu, mo=%04lx]\n", + EXT2FS_VERSION, EXT2FS_DATE, sb->s_blocksize, + sb->u.ext2_sb.s_frag_size, + sb->u.ext2_sb.s_groups_count, + EXT2_BLOCKS_PER_GROUP(sb), + EXT2_INODES_PER_GROUP(sb), + sb->u.ext2_sb.s_mount_opt); + if (test_opt (sb, CHECK)) { + ext2_check_blocks_bitmap (sb); + ext2_check_inodes_bitmap (sb); + } + } +} + +static int ext2_check_descriptors (struct super_block * sb) +{ + int i; + int desc_block = 0; + unsigned long block = sb->u.ext2_sb.s_es->s_first_data_block; + struct ext2_group_desc * gdp = NULL; + + ext2_debug ("Checking group descriptors"); + + for (i = 0; i < sb->u.ext2_sb.s_groups_count; i++) + { + if ((i % EXT2_DESC_PER_BLOCK(sb)) == 0) + gdp = (struct ext2_group_desc *) sb->u.ext2_sb.s_group_desc[desc_block++]->b_data; + if (gdp->bg_block_bitmap < block || + gdp->bg_block_bitmap >= block + EXT2_BLOCKS_PER_GROUP(sb)) + { + ext2_error (sb, "ext2_check_descriptors", + "Block bitmap for group %d" + " not in group (block %lu)!", + i, gdp->bg_block_bitmap); + return 0; + } + if (gdp->bg_inode_bitmap < block || + gdp->bg_inode_bitmap >= block + EXT2_BLOCKS_PER_GROUP(sb)) + { + ext2_error (sb, "ext2_check_descriptors", + "Inode bitmap for group %d" + " not in group (block %lu)!", + i, gdp->bg_inode_bitmap); + return 0; + } + if (gdp->bg_inode_table < block || + gdp->bg_inode_table + sb->u.ext2_sb.s_itb_per_group >= + block + EXT2_BLOCKS_PER_GROUP(sb)) + { + ext2_error (sb, "ext2_check_descriptors", + "Inode table for group %d" + " not in group (block %lu)!", + i, gdp->bg_inode_table); + return 0; + } + block += EXT2_BLOCKS_PER_GROUP(sb); + gdp++; + } + return 1; +} + +struct super_block * ext2_read_super (struct super_block * sb, void * data, + int silent) +{ + struct buffer_head * bh; + struct ext2_super_block * es; + unsigned long sb_block = 1; + unsigned short resuid = EXT2_DEF_RESUID; + unsigned short resgid = EXT2_DEF_RESGID; + unsigned long logic_sb_block = 1; + int dev = sb->s_dev; + int db_count; + int i, j; +#ifdef EXT2FS_PRE_02B_COMPAT + int fs_converted = 0; +#endif + + set_opt (sb->u.ext2_sb.s_mount_opt, CHECK_NORMAL); + if (!parse_options ((char *) data, &sb_block, &resuid, &resgid, + &sb->u.ext2_sb.s_mount_opt)) { + sb->s_dev = 0; + return NULL; + } + + lock_super (sb); + set_blocksize (dev, BLOCK_SIZE); + if (!(bh = bread (dev, sb_block, BLOCK_SIZE))) { + sb->s_dev = 0; + unlock_super (sb); + printk ("EXT2-fs: unable to read superblock\n"); + return NULL; + } + /* + * Note: s_es must be initialized s_es as soon as possible because + * some ext2 macro-instructions depend on its value + */ + es = (struct ext2_super_block *) bh->b_data; + sb->u.ext2_sb.s_es = es; + sb->s_magic = es->s_magic; + if (sb->s_magic != EXT2_SUPER_MAGIC +#ifdef EXT2FS_PRE_02B_COMPAT + && sb->s_magic != EXT2_PRE_02B_MAGIC +#endif + ) { + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + if (!silent) + printk ("VFS: Can't find an ext2 filesystem on dev %d/%d.\n", + MAJOR(dev), MINOR(dev)); + return NULL; + } + sb->s_blocksize = EXT2_MIN_BLOCK_SIZE << es->s_log_block_size; + sb->s_blocksize_bits = EXT2_BLOCK_SIZE_BITS(sb); + if (sb->s_blocksize != BLOCK_SIZE && + (sb->s_blocksize == 1024 || sb->s_blocksize == 2048 || + sb->s_blocksize == 4096)) { + unsigned long offset; + + brelse (bh); + set_blocksize (dev, sb->s_blocksize); + logic_sb_block = (sb_block*BLOCK_SIZE) / sb->s_blocksize; + offset = (sb_block*BLOCK_SIZE) % sb->s_blocksize; + bh = bread (dev, logic_sb_block, sb->s_blocksize); + if(!bh) + return NULL; + es = (struct ext2_super_block *) (((char *)bh->b_data) + offset); + sb->u.ext2_sb.s_es = es; + if (es->s_magic != EXT2_SUPER_MAGIC) { + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + printk ("EXT2-fs: Magic mismatch, very weird !\n"); + return NULL; + } + } + sb->u.ext2_sb.s_frag_size = EXT2_MIN_FRAG_SIZE << + es->s_log_frag_size; + if (sb->u.ext2_sb.s_frag_size) + sb->u.ext2_sb.s_frags_per_block = sb->s_blocksize / + sb->u.ext2_sb.s_frag_size; + else + sb->s_magic = 0; + sb->u.ext2_sb.s_blocks_per_group = es->s_blocks_per_group; + sb->u.ext2_sb.s_frags_per_group = es->s_frags_per_group; + sb->u.ext2_sb.s_inodes_per_group = es->s_inodes_per_group; + sb->u.ext2_sb.s_inodes_per_block = sb->s_blocksize / + sizeof (struct ext2_inode); + sb->u.ext2_sb.s_itb_per_group = sb->u.ext2_sb.s_inodes_per_group / + sb->u.ext2_sb.s_inodes_per_block; + sb->u.ext2_sb.s_desc_per_block = sb->s_blocksize / + sizeof (struct ext2_group_desc); + sb->u.ext2_sb.s_sbh = bh; + sb->u.ext2_sb.s_es = es; + if (resuid != EXT2_DEF_RESUID) + sb->u.ext2_sb.s_resuid = resuid; + else + sb->u.ext2_sb.s_resuid = es->s_def_resuid; + if (resgid != EXT2_DEF_RESGID) + sb->u.ext2_sb.s_resgid = resgid; + else + sb->u.ext2_sb.s_resgid = es->s_def_resgid; + sb->u.ext2_sb.s_mount_state = es->s_state; + sb->u.ext2_sb.s_rename_lock = 0; + sb->u.ext2_sb.s_rename_wait = NULL; +#ifdef EXT2FS_PRE_02B_COMPAT + if (sb->s_magic == EXT2_PRE_02B_MAGIC) { + if (es->s_blocks_count > 262144) { + /* + * fs > 256 MB can't be converted + */ + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + printk ("EXT2-fs: trying to mount a pre-0.2b file" + "system which cannot be converted\n"); + return NULL; + } + printk ("EXT2-fs: mounting a pre 0.2b file system, " + "will try to convert the structure\n"); + if (!(sb->s_flags & MS_RDONLY)) { + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + printk ("EXT2-fs: cannot convert a read-only fs\n"); + return NULL; + } + if (!convert_pre_02b_fs (sb, bh)) { + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + printk ("EXT2-fs: conversion failed !!!\n"); + return NULL; + } + printk ("EXT2-fs: conversion succeeded !!!\n"); + fs_converted = 1; + } +#endif + if (sb->s_magic != EXT2_SUPER_MAGIC) { + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + if (!silent) + printk ("VFS: Can't find an ext2 filesystem on dev %d/%d.\n", + MAJOR(dev), MINOR(dev)); + return NULL; + } + if (sb->s_blocksize != bh->b_size) { + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + if (!silent) + printk ("VFS: Unsupported blocksize on dev 0x%04x.\n", + dev); + return NULL; + } + + if (sb->s_blocksize != sb->u.ext2_sb.s_frag_size) { + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + printk ("EXT2-fs: fragsize %lu != blocksize %lu (not supported yet)\n", + sb->u.ext2_sb.s_frag_size, sb->s_blocksize); + return NULL; + } + + sb->u.ext2_sb.s_groups_count = (es->s_blocks_count - + es->s_first_data_block + + EXT2_BLOCKS_PER_GROUP(sb) - 1) / + EXT2_BLOCKS_PER_GROUP(sb); + db_count = (sb->u.ext2_sb.s_groups_count + EXT2_DESC_PER_BLOCK(sb) - 1) / + EXT2_DESC_PER_BLOCK(sb); + sb->u.ext2_sb.s_group_desc = kmalloc (db_count * sizeof (struct buffer_head *), GFP_KERNEL); + if (sb->u.ext2_sb.s_group_desc == NULL) { + sb->s_dev = 0; + unlock_super (sb); + brelse (bh); + printk ("EXT2-fs: not enough memory\n"); + return NULL; + } + for (i = 0; i < db_count; i++) { + sb->u.ext2_sb.s_group_desc[i] = bread (dev, logic_sb_block + i + 1, + sb->s_blocksize); + if (!sb->u.ext2_sb.s_group_desc[i]) { + sb->s_dev = 0; + unlock_super (sb); + for (j = 0; j < i; j++) + brelse (sb->u.ext2_sb.s_group_desc[j]); + kfree_s (sb->u.ext2_sb.s_group_desc, + db_count * sizeof (struct buffer_head *)); + brelse (bh); + printk ("EXT2-fs: unable to read group descriptors\n"); + return NULL; + } + } + if (!ext2_check_descriptors (sb)) { + sb->s_dev = 0; + unlock_super (sb); + for (j = 0; j < db_count; j++) + brelse (sb->u.ext2_sb.s_group_desc[j]); + kfree_s (sb->u.ext2_sb.s_group_desc, + db_count * sizeof (struct buffer_head *)); + brelse (bh); + printk ("EXT2-fs: group descriptors corrupted !\n"); + return NULL; + } + for (i = 0; i < EXT2_MAX_GROUP_LOADED; i++) { + sb->u.ext2_sb.s_inode_bitmap_number[i] = 0; + sb->u.ext2_sb.s_inode_bitmap[i] = NULL; + sb->u.ext2_sb.s_block_bitmap_number[i] = 0; + sb->u.ext2_sb.s_block_bitmap[i] = NULL; + } + sb->u.ext2_sb.s_loaded_inode_bitmaps = 0; + sb->u.ext2_sb.s_loaded_block_bitmaps = 0; + sb->u.ext2_sb.s_db_per_group = db_count; + unlock_super (sb); + /* + * set up enough so that it can read an inode + */ + sb->s_dev = dev; + sb->s_op = &ext2_sops; + if (!(sb->s_mounted = iget (sb, EXT2_ROOT_INO))) { + sb->s_dev = 0; + for (i = 0; i < db_count; i++) + if (sb->u.ext2_sb.s_group_desc[i]) + brelse (sb->u.ext2_sb.s_group_desc[i]); + kfree_s (sb->u.ext2_sb.s_group_desc, + db_count * sizeof (struct buffer_head *)); + brelse (bh); + printk ("EXT2-fs: get root inode failed\n"); + return NULL; + } +#ifdef EXT2FS_PRE_02B_COMPAT + if (fs_converted) { + for (i = 0; i < db_count; i++) + mark_buffer_dirty(sb->u.ext2_sb.s_group_desc[i], 1); + sb->s_dirt = 1; + } +#endif + ext2_setup_super (sb, es); + return sb; +} + +static void ext2_commit_super (struct super_block * sb, + struct ext2_super_block * es) +{ + es->s_wtime = CURRENT_TIME; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + sb->s_dirt = 0; +} + +/* + * In the second extended file system, it is not necessary to + * write the super block since we use a mapping of the + * disk super block in a buffer. + * + * However, this function is still used to set the fs valid + * flags to 0. We need to set this flag to 0 since the fs + * may have been checked while mounted and e2fsck may have + * set s_state to EXT2_VALID_FS after some corrections. + */ + +void ext2_write_super (struct super_block * sb) +{ + struct ext2_super_block * es; + + if (!(sb->s_flags & MS_RDONLY)) { + es = sb->u.ext2_sb.s_es; + + ext2_debug ("setting valid to 0\n"); + + if (es->s_state & EXT2_VALID_FS) { + es->s_state &= ~EXT2_VALID_FS; + es->s_mtime = CURRENT_TIME; + } + ext2_commit_super (sb, es); + } + sb->s_dirt = 0; +} + +int ext2_remount (struct super_block * sb, int * flags, char * data) +{ + struct ext2_super_block * es; + unsigned short resuid = sb->u.ext2_sb.s_resuid; + unsigned short resgid = sb->u.ext2_sb.s_resgid; + unsigned long new_mount_opt; + unsigned long tmp; + + /* + * Allow the "check" option to be passed as a remount option. + */ + set_opt (sb->u.ext2_sb.s_mount_opt, CHECK_NORMAL); + if (!parse_options (data, &tmp, &resuid, &resgid, + &new_mount_opt)) + return -EINVAL; + + sb->u.ext2_sb.s_mount_opt = new_mount_opt; + sb->u.ext2_sb.s_resuid = resuid; + sb->u.ext2_sb.s_resgid = resgid; + es = sb->u.ext2_sb.s_es; + if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY)) + return 0; + if (*flags & MS_RDONLY) { + if (es->s_state & EXT2_VALID_FS || + !(sb->u.ext2_sb.s_mount_state & EXT2_VALID_FS)) + return 0; + /* + * OK, we are remounting a valid rw partition rdonly, so set + * the rdonly flag and then mark the partition as valid again. + */ + es->s_state = sb->u.ext2_sb.s_mount_state; + es->s_mtime = CURRENT_TIME; + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + sb->s_dirt = 1; + ext2_commit_super (sb, es); + } + else { + /* + * Mounting a RDONLY partition read-write, so reread and + * store the current valid flag. (It may have been changed + * by e2fsck since we originally mounted the partition.) + */ + sb->u.ext2_sb.s_mount_state = es->s_state; + sb->s_flags &= ~MS_RDONLY; + ext2_setup_super (sb, es); + } + return 0; +} + +void ext2_statfs (struct super_block * sb, struct statfs * buf) +{ + long tmp; + unsigned long overhead; + unsigned long overhead_per_group; + + if (test_opt (sb, MINIX_DF)) + overhead = 0; + else { + /* + * Compute the overhead (FS structures) + */ + overhead_per_group = 1 /* super block */ + + sb->u.ext2_sb.s_db_per_group /* descriptors */ + + 1 /* block bitmap */ + + 1 /* inode bitmap */ + + sb->u.ext2_sb.s_itb_per_group /* inode table */; + overhead = sb->u.ext2_sb.s_es->s_first_data_block + + sb->u.ext2_sb.s_groups_count * overhead_per_group; + } + + put_fs_long (EXT2_SUPER_MAGIC, &buf->f_type); + put_fs_long (sb->s_blocksize, &buf->f_bsize); + put_fs_long (sb->u.ext2_sb.s_es->s_blocks_count - overhead, + &buf->f_blocks); + tmp = ext2_count_free_blocks (sb); + put_fs_long (tmp, &buf->f_bfree); + if (tmp >= sb->u.ext2_sb.s_es->s_r_blocks_count) + put_fs_long (tmp - sb->u.ext2_sb.s_es->s_r_blocks_count, + &buf->f_bavail); + else + put_fs_long (0, &buf->f_bavail); + put_fs_long (sb->u.ext2_sb.s_es->s_inodes_count, &buf->f_files); + put_fs_long (ext2_count_free_inodes (sb), &buf->f_ffree); + put_fs_long (EXT2_NAME_LEN, &buf->f_namelen); + /* Don't know what value to put in buf->f_fsid */ +} diff --git a/fs/ext2/symlink.c b/fs/ext2/symlink.c new file mode 100644 index 000000000..7d85ed74c --- /dev/null +++ b/fs/ext2/symlink.c @@ -0,0 +1,127 @@ +/* + * linux/fs/ext2/symlink.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/fs/minix/symlink.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * ext2 symlink handling code + */ + +#include <asm/segment.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/sched.h> +#include <linux/stat.h> + +static int ext2_readlink (struct inode *, char *, int); +static int ext2_follow_link (struct inode *, struct inode *, int, int, + struct inode **); + +/* + * symlinks can't do much... + */ +struct inode_operations ext2_symlink_inode_operations = { + NULL, /* no file-operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + ext2_readlink, /* readlink */ + ext2_follow_link, /* follow_link */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL, /* permission */ + NULL /* smap */ +}; + +static int ext2_follow_link(struct inode * dir, struct inode * inode, + int flag, int mode, struct inode ** res_inode) +{ + int error; + struct buffer_head * bh = NULL; + char * link; + + *res_inode = NULL; + if (!dir) { + dir = current->fs->root; + dir->i_count++; + } + if (!inode) { + iput (dir); + return -ENOENT; + } + if (!S_ISLNK(inode->i_mode)) { + iput (dir); + *res_inode = inode; + return 0; + } + if (current->link_count > 5) { + iput (dir); + iput (inode); + return -ELOOP; + } + if (inode->i_blocks) { + if (!(bh = ext2_bread (inode, 0, 0, &error))) { + iput (dir); + iput (inode); + return -EIO; + } + link = bh->b_data; + } else + link = (char *) inode->u.ext2_i.i_data; + current->link_count++; + error = open_namei (link, flag, mode, res_inode, dir); + current->link_count--; + iput (inode); + if (bh) + brelse (bh); + return error; +} + +static int ext2_readlink (struct inode * inode, char * buffer, int buflen) +{ + struct buffer_head * bh = NULL; + char * link; + int i, err; + char c; + + if (!S_ISLNK(inode->i_mode)) { + iput (inode); + return -EINVAL; + } + if (buflen > inode->i_sb->s_blocksize - 1) + buflen = inode->i_sb->s_blocksize - 1; + if (inode->i_blocks) { + bh = ext2_bread (inode, 0, 0, &err); + if (!bh) { + iput (inode); + return 0; + } + link = bh->b_data; + } + else + link = (char *) inode->u.ext2_i.i_data; + i = 0; + while (i < buflen && (c = link[i])) { + i++; + put_fs_byte (c, buffer++); + } + iput (inode); + if (bh) + brelse (bh); + return i; +} diff --git a/fs/ext2/truncate.c b/fs/ext2/truncate.c new file mode 100644 index 000000000..10a1fd236 --- /dev/null +++ b/fs/ext2/truncate.c @@ -0,0 +1,349 @@ +/* + * linux/fs/ext2/truncate.c + * + * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/fs/minix/truncate.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +/* + * Real random numbers for secure rm added 94/02/18 + * Idea from Pierre del Perugia <delperug@gla.ecoledoc.ibp.fr> + */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/ext2_fs.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/locks.h> +#include <linux/string.h> + +static int ext2_secrm_seed = 152; /* Random generator base */ + +#define RANDOM_INT (ext2_secrm_seed = ext2_secrm_seed * 69069l +1) + +/* + * Truncate has the most races in the whole filesystem: coding it is + * a pain in the a**. Especially as I don't do any locking... + * + * The code may look a bit weird, but that's just because I've tried to + * handle things like file-size changes in a somewhat graceful manner. + * Anyway, truncating a file at the same time somebody else writes to it + * is likely to result in pretty weird behaviour... + * + * The new code handles normal truncates (size = 0) as well as the more + * general case (size = XXX). I hope. + */ + +static int trunc_direct (struct inode * inode) +{ + int i, tmp; + unsigned long * p; + struct buffer_head * bh; + unsigned long block_to_free = 0; + unsigned long free_count = 0; + int retry = 0; + int blocks = inode->i_sb->s_blocksize / 512; +#define DIRECT_BLOCK ((inode->i_size + inode->i_sb->s_blocksize - 1) / \ + inode->i_sb->s_blocksize) + int direct_block = DIRECT_BLOCK; + +repeat: + for (i = direct_block ; i < EXT2_NDIR_BLOCKS ; i++) { + p = inode->u.ext2_i.i_data + i; + tmp = *p; + if (!tmp) + continue; + if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL) + bh = getblk (inode->i_dev, tmp, + inode->i_sb->s_blocksize); + else + bh = get_hash_table (inode->i_dev, tmp, + inode->i_sb->s_blocksize); + if (i < direct_block) { + brelse (bh); + goto repeat; + } + if ((bh && bh->b_count != 1) || tmp != *p) { + retry = 1; + brelse (bh); + continue; + } + *p = 0; + inode->i_blocks -= blocks; + inode->i_dirt = 1; + if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL) { + memset(bh->b_data, RANDOM_INT, inode->i_sb->s_blocksize); + mark_buffer_dirty(bh, 1); + } + brelse (bh); + if (free_count == 0) { + block_to_free = tmp; + free_count++; + } else if (free_count > 0 && block_to_free == tmp - free_count) + free_count++; + else { + ext2_free_blocks (inode->i_sb, block_to_free, free_count); + block_to_free = tmp; + free_count = 1; + } +/* ext2_free_blocks (inode->i_sb, tmp, 1); */ + } + if (free_count > 0) + ext2_free_blocks (inode->i_sb, block_to_free, free_count); + return retry; +} + +static int trunc_indirect (struct inode * inode, int offset, unsigned long * p) +{ + int i, tmp; + struct buffer_head * bh; + struct buffer_head * ind_bh; + unsigned long * ind; + unsigned long block_to_free = 0; + unsigned long free_count = 0; + int retry = 0; + int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb); + int blocks = inode->i_sb->s_blocksize / 512; +#define INDIRECT_BLOCK ((int)DIRECT_BLOCK - offset) + int indirect_block = INDIRECT_BLOCK; + + tmp = *p; + if (!tmp) + return 0; + ind_bh = bread (inode->i_dev, tmp, inode->i_sb->s_blocksize); + if (tmp != *p) { + brelse (ind_bh); + return 1; + } + if (!ind_bh) { + *p = 0; + return 0; + } +repeat: + for (i = indirect_block ; i < addr_per_block ; i++) { + if (i < 0) + i = 0; + if (i < indirect_block) + goto repeat; + ind = i + (unsigned long *) ind_bh->b_data; + tmp = *ind; + if (!tmp) + continue; + if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL) + bh = getblk (inode->i_dev, tmp, + inode->i_sb->s_blocksize); + else + bh = get_hash_table (inode->i_dev, tmp, + inode->i_sb->s_blocksize); + if (i < indirect_block) { + brelse (bh); + goto repeat; + } + if ((bh && bh->b_count != 1) || tmp != *ind) { + retry = 1; + brelse (bh); + continue; + } + *ind = 0; + mark_buffer_dirty(ind_bh, 1); + if (inode->u.ext2_i.i_flags & EXT2_SECRM_FL) { + memset(bh->b_data, RANDOM_INT, inode->i_sb->s_blocksize); + mark_buffer_dirty(bh, 1); + } + brelse (bh); + if (free_count == 0) { + block_to_free = tmp; + free_count++; + } else if (free_count > 0 && block_to_free == tmp - free_count) + free_count++; + else { + ext2_free_blocks (inode->i_sb, block_to_free, free_count); + block_to_free = tmp; + free_count = 1; + } +/* ext2_free_blocks (inode->i_sb, tmp, 1); */ + inode->i_blocks -= blocks; + inode->i_dirt = 1; + } + if (free_count > 0) + ext2_free_blocks (inode->i_sb, block_to_free, free_count); + ind = (unsigned long *) ind_bh->b_data; + for (i = 0; i < addr_per_block; i++) + if (*(ind++)) + break; + if (i >= addr_per_block) + if (ind_bh->b_count != 1) + retry = 1; + else { + tmp = *p; + *p = 0; + inode->i_blocks -= blocks; + inode->i_dirt = 1; + ext2_free_blocks (inode->i_sb, tmp, 1); + } + if (IS_SYNC(inode) && ind_bh->b_dirt) { + ll_rw_block (WRITE, 1, &ind_bh); + wait_on_buffer (ind_bh); + } + brelse (ind_bh); + return retry; +} + +static int trunc_dindirect (struct inode * inode, int offset, + unsigned long * p) +{ + int i, tmp; + struct buffer_head * dind_bh; + unsigned long * dind; + int retry = 0; + int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb); + int blocks = inode->i_sb->s_blocksize / 512; +#define DINDIRECT_BLOCK (((int)DIRECT_BLOCK - offset) / addr_per_block) + int dindirect_block = DINDIRECT_BLOCK; + + tmp = *p; + if (!tmp) + return 0; + dind_bh = bread (inode->i_dev, tmp, inode->i_sb->s_blocksize); + if (tmp != *p) { + brelse (dind_bh); + return 1; + } + if (!dind_bh) { + *p = 0; + return 0; + } +repeat: + for (i = dindirect_block ; i < addr_per_block ; i++) { + if (i < 0) + i = 0; + if (i < dindirect_block) + goto repeat; + dind = i + (unsigned long *) dind_bh->b_data; + tmp = *dind; + if (!tmp) + continue; + retry |= trunc_indirect (inode, offset + (i * addr_per_block), + dind); + mark_buffer_dirty(dind_bh, 1); + } + dind = (unsigned long *) dind_bh->b_data; + for (i = 0; i < addr_per_block; i++) + if (*(dind++)) + break; + if (i >= addr_per_block) + if (dind_bh->b_count != 1) + retry = 1; + else { + tmp = *p; + *p = 0; + inode->i_blocks -= blocks; + inode->i_dirt = 1; + ext2_free_blocks (inode->i_sb, tmp, 1); + } + if (IS_SYNC(inode) && dind_bh->b_dirt) { + ll_rw_block (WRITE, 1, &dind_bh); + wait_on_buffer (dind_bh); + } + brelse (dind_bh); + return retry; +} + +static int trunc_tindirect (struct inode * inode) +{ + int i, tmp; + struct buffer_head * tind_bh; + unsigned long * tind, * p; + int retry = 0; + int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb); + int blocks = inode->i_sb->s_blocksize / 512; +#define TINDIRECT_BLOCK (((int)DIRECT_BLOCK - (addr_per_block * addr_per_block + \ + addr_per_block + EXT2_NDIR_BLOCKS)) / \ + (addr_per_block * addr_per_block)) + int tindirect_block = TINDIRECT_BLOCK; + + p = inode->u.ext2_i.i_data + EXT2_TIND_BLOCK; + if (!(tmp = *p)) + return 0; + tind_bh = bread (inode->i_dev, tmp, inode->i_sb->s_blocksize); + if (tmp != *p) { + brelse (tind_bh); + return 1; + } + if (!tind_bh) { + *p = 0; + return 0; + } +repeat: + for (i = tindirect_block ; i < addr_per_block ; i++) { + if (i < 0) + i = 0; + if (i < tindirect_block) + goto repeat; + tind = i + (unsigned long *) tind_bh->b_data; + retry |= trunc_dindirect(inode, EXT2_NDIR_BLOCKS + + addr_per_block + (i + 1) * addr_per_block * addr_per_block, + tind); + mark_buffer_dirty(tind_bh, 1); + } + tind = (unsigned long *) tind_bh->b_data; + for (i = 0; i < addr_per_block; i++) + if (*(tind++)) + break; + if (i >= addr_per_block) + if (tind_bh->b_count != 1) + retry = 1; + else { + tmp = *p; + *p = 0; + inode->i_blocks -= blocks; + inode->i_dirt = 1; + ext2_free_blocks (inode->i_sb, tmp, 1); + } + if (IS_SYNC(inode) && tind_bh->b_dirt) { + ll_rw_block (WRITE, 1, &tind_bh); + wait_on_buffer (tind_bh); + } + brelse (tind_bh); + return retry; +} + +void ext2_truncate (struct inode * inode) +{ + int retry; + + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || + S_ISLNK(inode->i_mode))) + return; + if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) + return; + ext2_discard_prealloc(inode); + while (1) { + down(&inode->i_sem); + retry = trunc_direct(inode); + retry |= trunc_indirect (inode, EXT2_IND_BLOCK, + (unsigned long *) &inode->u.ext2_i.i_data[EXT2_IND_BLOCK]); + retry |= trunc_dindirect (inode, EXT2_IND_BLOCK + + EXT2_ADDR_PER_BLOCK(inode->i_sb), + (unsigned long *) &inode->u.ext2_i.i_data[EXT2_DIND_BLOCK]); + retry |= trunc_tindirect (inode); + up(&inode->i_sem); + if (!retry) + break; + if (IS_SYNC(inode) && inode->i_dirt) + ext2_sync_inode (inode); + current->counter = 0; + schedule (); + } + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + inode->i_dirt = 1; +} |