> 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/