summaryrefslogtreecommitdiffstats
path: root/fs/ext2
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1994-11-28 11:59:19 +0000
committer <ralf@linux-mips.org>1994-11-28 11:59:19 +0000
commit1513ff9b7899ab588401c89db0e99903dbf5f886 (patch)
treef69cc81a940a502ea23d664c3ffb2d215a479667 /fs/ext2
Import of Linus's Linux 1.1.68
Diffstat (limited to 'fs/ext2')
-rw-r--r--fs/ext2/CHANGES140
-rw-r--r--fs/ext2/Makefile31
-rw-r--r--fs/ext2/acl.c50
-rw-r--r--fs/ext2/balloc.c582
-rw-r--r--fs/ext2/bitmap.c25
-rw-r--r--fs/ext2/dir.c227
-rw-r--r--fs/ext2/file.c354
-rw-r--r--fs/ext2/fsync.c198
-rw-r--r--fs/ext2/ialloc.c554
-rw-r--r--fs/ext2/inode.c667
-rw-r--r--fs/ext2/ioctl.c75
-rw-r--r--fs/ext2/namei.c1098
-rw-r--r--fs/ext2/super.c755
-rw-r--r--fs/ext2/symlink.c127
-rw-r--r--fs/ext2/truncate.c349
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;
+}