[PATCH(es)] Re: ramfs problem... (unlink of sparse file in "D" state)

Alexander Viro (viro@math.psu.edu)
Mon, 8 Jan 2001 03:41:13 -0500 (EST)


On Mon, 8 Jan 2001, Chris Wedgwood wrote:

> On Mon, Jan 08, 2001 at 02:56:10AM -0500, Alexander Viro wrote:
>
> Plenty. ext2, for one - e.g. with 4Kb blocks you have limit
> at 0x4010040c000 for files and 0x100000000 for directories. With
> 1Kb blocks the limit for files is 0x404043000. Notice that the
> latter is not a multiple of page size on Alpha.
>
> There is code to support this in 2.4.0-ac4 -- initially I didn't like
> the way Alan had done things (I was think -EFBIG should be used only
> for LFS violations) but after some thought has decided that what he
> has makes a lot of sense.
>
> The FS calculates the largest suitable byte-size for a file in put it
> in the superblock; when the VFS checks in the generic_file_* paths.
> The only time this won't work is if some complex criteria allows some
> files to be larger than others, in which case -- we add a callback to
> the fs.

It's still not enough. get_block() can fail for a lot of reasons and
generic_file_write() should be prepared to deal with that. -EFBIG
is nothing special in that respect. We don't need new callbacks, get_block()
is quite enough.

IOW, the following is still needed:

diff -urN S0/fs/buffer.c S0-get_block_fail/fs/buffer.c
--- S0/fs/buffer.c Wed Jan 3 23:45:26 2001
+++ S0-get_block_fail/fs/buffer.c Mon Jan 8 03:12:07 2001
@@ -1494,6 +1494,7 @@
int err, i;
unsigned long block;
struct buffer_head *bh, *head;
+ int need_unlock = 1;

if (!PageLocked(page))
BUG();
@@ -1539,18 +1540,39 @@
} while (bh != head);

/* Stage 3: submit the IO */
+ SetPageUptodate(page);
do {
submit_bh(WRITE, bh);
bh = bh->b_this_page;
} while (bh != head);

/* Done - end_buffer_io_async will unlock */
- SetPageUptodate(page);
return 0;

out:
ClearPageUptodate(page);
- UnlockPage(page);
+ bh = head;
+ need_unlock = 1;
+ /* Recovery: lock and submit the mapped buffers */
+ do {
+ if (buffer_mapped(bh)) {
+ lock_buffer(bh);
+ need_unlock = 0;
+ }
+ bh = bh->b_this_page;
+ } while (bh != head);
+ do {
+ if (buffer_mapped(bh)) {
+ bh->b_end_io = end_buffer_io_async;
+ atomic_inc(&bh->b_count);
+ set_bit(BH_Uptodate, &bh->b_state);
+ set_bit(BH_Dirty, &bh->b_state);
+ submit_bh(WRITE, bh);
+ }
+ bh = bh->b_this_page;
+ }
+ if (need_unlock)
+ UnlockPage(page);
return err;
}

@@ -1621,6 +1643,15 @@
}
return 0;
out:
+ bh = head;
+ do {
+ if (buffer_new(bh) && !buffer_uptodate(bh)) {
+ memset(bh->b_data, 0, bh->b_size);
+ set_bit(BH_Uptodate, &bh->b_state);
+ mark_buffer_dirty(bh);
+ }
+ bh = bh->b_this_page;
+ } while (bh != head);
return err;
}

diff -urN S0/mm/filemap.c S0-get_block_fail/mm/filemap.c
--- S0/mm/filemap.c Tue Jan 2 21:59:45 2001
+++ S0-get_block_fail/mm/filemap.c Mon Jan 8 03:13:37 2001
@@ -2449,6 +2449,7 @@
unsigned long written;
long status;
int err;
+ unsigned bytes;

cached_page = NULL;

@@ -2493,7 +2494,7 @@
}

while (count) {
- unsigned long bytes, index, offset;
+ unsigned long index, offset;
char *kaddr;
int deactivate = 1;

@@ -2532,7 +2533,7 @@

status = mapping->a_ops->prepare_write(file, page, offset, offset+bytes);
if (status)
- goto unlock;
+ goto sync_failure;
kaddr = page_address(page);
status = copy_from_user(kaddr+offset, buf, bytes);
flush_dcache_page(page);
@@ -2558,6 +2559,7 @@
if (status < 0)
break;
}
+done:
*ppos = pos;

if (cached_page)
@@ -2578,6 +2580,13 @@
ClearPageUptodate(page);
kunmap(page);
goto unlock;
+sync_failure:
+ UnlockPage(page);
+ deactivate_page(page);
+ page_cache_release(page);
+ if (pos + bytes > inode->i_size)
+ vmtruncate(inode, inode->i_size);
+ goto done;
}

void __init page_cache_init(unsigned long mempages)

and with that patch the rest of filesize limits goes into filesystems. For
ext2 it would be

diff -urN S0/fs/ext2/inode.c S0-ext2_get_block-EFBIG/fs/ext2/inode.c
--- S0/fs/ext2/inode.c Fri Dec 29 17:36:44 2000
+++ S0-ext2_get_block-EFBIG/fs/ext2/inode.c Mon Jan 8 03:22:58 2001
@@ -153,11 +153,13 @@
* This function translates the block number into path in that tree -
* return value is the path length and @offsets[n] is the offset of
* pointer to (n+1)th node in the nth one. If @block is out of range
- * (negative or too large) warning is printed and zero returned.
+ * (negative or too large) we return zero. Warning is printed if @block
+ * is negative - that should never happen. Too large value is OK, it
+ * just means that ext2_get_block() should return -%EFBIG.
*
* Note: function doesn't find node addresses, so no IO is needed. All
* we need to know is the capacity of indirect blocks (taken from the
- * inode->i_sb).
+ * @inode->i_sb).
*/

/*
@@ -196,7 +198,7 @@
offsets[n++] = (i_block >> ptrs_bits) & (ptrs - 1);
offsets[n++] = i_block & (ptrs - 1);
} else {
- ext2_warning (inode->i_sb, "ext2_block_to_path", "block > big");
+ /* Too large, nothing to do here */
}
return n;
}
@@ -505,7 +507,7 @@

static int ext2_get_block(struct inode *inode, long iblock, struct buffer_head *bh_result, int create)
{
- int err = -EIO;
+ int err = -EFBIG;
int offsets[4];
Indirect chain[4];
Indirect *partial;

plus

diff -urN S0/fs/ext2/inode.c S0-ext2-LFS/fs/ext2/inode.c
--- S0/fs/ext2/inode.c Fri Dec 29 17:36:44 2000
+++ S0-ext2-LFS/fs/ext2/inode.c Fri Jan 5 21:11:31 2001
@@ -1188,7 +1188,7 @@
raw_inode->i_dir_acl = cpu_to_le32(inode->u.ext2_i.i_dir_acl);
else {
raw_inode->i_size_high = cpu_to_le32(inode->i_size >> 32);
- if (raw_inode->i_size_high) {
+ if (raw_inode->i_size_high || (inode->i_size & (1<<31))) {
struct super_block *sb = inode->i_sb;
if (!EXT2_HAS_RO_COMPAT_FEATURE(sb,
EXT2_FEATURE_RO_COMPAT_LARGE_FILE) ||

plus

diff -urN S0/fs/ext2/file.c S0-ext2_max_size/fs/ext2/file.c
--- S0/fs/ext2/file.c Wed Sep 27 16:41:33 2000
+++ S0-ext2_max_size/fs/ext2/file.c Mon Jan 8 03:29:41 2001
@@ -25,17 +25,6 @@
static loff_t ext2_file_lseek(struct file *, loff_t, int);
static int ext2_open_file (struct inode *, struct file *);

-#define EXT2_MAX_SIZE(bits) \
- (((EXT2_NDIR_BLOCKS + (1LL << (bits - 2)) + \
- (1LL << (bits - 2)) * (1LL << (bits - 2)) + \
- (1LL << (bits - 2)) * (1LL << (bits - 2)) * (1LL << (bits - 2))) * \
- (1LL << bits)) - 1)
-
-static long long ext2_max_sizes[] = {
-0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-EXT2_MAX_SIZE(10), EXT2_MAX_SIZE(11), EXT2_MAX_SIZE(12), EXT2_MAX_SIZE(13)
-};
-
/*
* Make sure the offset never goes beyond the 32-bit mark..
*/
@@ -56,7 +45,7 @@
if (offset<0)
return -EINVAL;
if (((unsigned long long) offset >> 32) != 0) {
- if (offset > ext2_max_sizes[EXT2_BLOCK_SIZE_BITS(inode->i_sb)])
+ if (offset >= inode->i_sb->u.ext2_sb.s_max_size)
return -EINVAL;
}
if (offset != file->f_pos) {
diff -urN S0/fs/ext2/super.c S0-ext2_max_size/fs/ext2/super.c
--- S0/fs/ext2/super.c Fri Dec 29 17:36:44 2000
+++ S0-ext2_max_size/fs/ext2/super.c Mon Jan 8 03:30:49 2001
@@ -380,6 +380,18 @@
}

#define log2(n) ffz(~(n))
+
+/*
+ * maximal file size.
+ */
+static loff_t ext2_max_size(int bits)
+{
+ loff_t res = EXT2_NDIR_BLOCKS;
+ res += 1LL << (bits-2);
+ res += 1LL << (2*(bits-2));
+ res += 1LL << (3*(bits-2));
+ return res << bits;
+}

struct super_block * ext2_read_super (struct super_block * sb, void * data,
int silent)
@@ -549,6 +561,7 @@
log2 (EXT2_ADDR_PER_BLOCK(sb));
sb->u.ext2_sb.s_desc_per_block_bits =
log2 (EXT2_DESC_PER_BLOCK(sb));
+ sb->u.ext2_sb.s_max_size = ext2_max_size(sb->s_blocksize_bits);
if (sb->s_magic != EXT2_SUPER_MAGIC) {
if (!silent)
printk ("VFS: Can't find an ext2 filesystem on dev "
diff -urN S0/include/linux/ext2_fs_sb.h S0-ext2_max_size/include/linux/ext2_fs_sb.h
--- S0/include/linux/ext2_fs_sb.h Fri Dec 29 17:36:44 2000
+++ S0-ext2_max_size/include/linux/ext2_fs_sb.h Mon Jan 8 03:31:19 2001
@@ -56,6 +56,7 @@
int s_desc_per_block_bits;
int s_inode_size;
int s_first_ino;
+ loff_t s_max_size;
};

#endif /* _LINUX_EXT2_FS_SB */

plus

diff -urN S0-ext2_max_size/fs/ext2/file.c S0-ext2-LFS-full/fs/ext2/file.c
--- S0-ext2_max_size/fs/ext2/file.c Mon Jan 8 03:29:41 2001
+++ S0-ext2-LFS-full/fs/ext2/file.c Mon Jan 8 03:37:41 2001
@@ -99,4 +99,5 @@

struct inode_operations ext2_file_inode_operations = {
truncate: ext2_truncate,
+ setattr: ext2_notify_change,
};
diff -urN S0-ext2_max_size/fs/ext2/inode.c S0-ext2-LFS-full/fs/ext2/inode.c
--- S0-ext2_max_size/fs/ext2/inode.c Fri Dec 29 17:36:44 2000
+++ S0-ext2-LFS-full/fs/ext2/inode.c Mon Jan 8 03:37:41 2001
@@ -880,8 +880,6 @@
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);

@@ -1239,11 +1237,17 @@
return ext2_update_inode (inode, 1);
}

+static struct {unsigned attr, flag, ext2;} ext2_attr[] = {
+ {ATTR_FLAG_SYNCRONOUS, S_SYNC, EXT2_SYNC_FL},
+ {ATTR_FLAG_NOATIME, S_NOATIME, EXT2_NOATIME_FL},
+ {ATTR_FLAG_APPEND, S_APPEND, EXT2_APPEND_FL},
+ {ATTR_FLAG_IMMUTABLE, S_IMMUTABLE, EXT2_IMMUTABLE_FL}
+};
+
int ext2_notify_change(struct dentry *dentry, struct iattr *iattr)
{
struct inode *inode = dentry->d_inode;
int retval;
- unsigned int flags;

retval = -EPERM;
if (iattr->ia_valid & ATTR_ATTR_FLAG &&
@@ -1260,36 +1264,27 @@
if (retval != 0)
goto out;

+ if (iattr->ia_valid & ATTR_SIZE) {
+ if (iattr->ia_size > inode->i_sb->u.ext2_sb.s_max_size) {
+ retval = -EFBIG;
+ goto out;
+ }
+ }
+
inode_setattr(inode, iattr);

- flags = iattr->ia_attr_flags;
- if (flags & ATTR_FLAG_SYNCRONOUS) {
- inode->i_flags |= S_SYNC;
- inode->u.ext2_i.i_flags |= EXT2_SYNC_FL;
- } else {
- inode->i_flags &= ~S_SYNC;
- inode->u.ext2_i.i_flags &= ~EXT2_SYNC_FL;
- }
- if (flags & ATTR_FLAG_NOATIME) {
- inode->i_flags |= S_NOATIME;
- inode->u.ext2_i.i_flags |= EXT2_NOATIME_FL;
- } else {
- inode->i_flags &= ~S_NOATIME;
- inode->u.ext2_i.i_flags &= ~EXT2_NOATIME_FL;
- }
- if (flags & ATTR_FLAG_APPEND) {
- inode->i_flags |= S_APPEND;
- inode->u.ext2_i.i_flags |= EXT2_APPEND_FL;
- } else {
- inode->i_flags &= ~S_APPEND;
- inode->u.ext2_i.i_flags &= ~EXT2_APPEND_FL;
- }
- if (flags & ATTR_FLAG_IMMUTABLE) {
- inode->i_flags |= S_IMMUTABLE;
- inode->u.ext2_i.i_flags |= EXT2_IMMUTABLE_FL;
- } else {
- inode->i_flags &= ~S_IMMUTABLE;
- inode->u.ext2_i.i_flags &= ~EXT2_IMMUTABLE_FL;
+ if (iattr->ia_valid & ATTR_ATTR_FLAG) {
+ unsigned flags = iattr->ia_attr_flags;
+ int i;
+ for (i=0; i<sizeof(ext2_attr)/sizeof(ext2_attr[0]); i++) {
+ if (flags & ext2_attr[i].attr) {
+ inode->i_flags |= ext2_attr[i].flag;
+ inode->u.ext2_i.i_flags |= ext2_attr[i].ext2;
+ } else {
+ inode->i_flags &= ~ext2_attr[i].flag;
+ inode->u.ext2_i.i_flags &= ~ext2_attr[i].ext2;
+ }
+ }
}
mark_inode_dirty(inode);
out:
diff -urN S0-ext2_max_size/include/linux/ext2_fs.h S0-ext2-LFS-full/include/linux/ext2_fs.h
--- S0-ext2_max_size/include/linux/ext2_fs.h Thu Jan 4 17:50:47 2001
+++ S0-ext2-LFS-full/include/linux/ext2_fs.h Mon Jan 8 03:37:41 2001
@@ -582,6 +582,8 @@
extern int ext2_sync_inode (struct inode *);
extern void ext2_discard_prealloc (struct inode *);

+extern int ext2_notify_change (struct dentry *, struct iattr *);
+
/* ioctl.c */
extern int ext2_ioctl (struct inode *, struct file *, unsigned int,
unsigned long);

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
Please read the FAQ at http://www.tux.org/lkml/