Messages in this thread Patch in this message |  | | | Date | Thu, 2 Apr 1998 14:45:01 -0500 | | Subject | Ext2 patches | | From | tytso@mit ... |
| |
Hi Linus,
I submitted to you ext2 patches just before 2.1.90, and I assumed that you did't get to them before 2.1.91 was released. So I shipped you new ext2 patches right after 2.1.91, and was very disturbed to find that 2.1.92 was released without the patches.
Here is a new set of patches versus 2.1.92. They consist of the 2.1.91 patches plus a few additions. I've taken Stephen's patches that have already been applied to 2.0.x so that disk errors in reading the inode table and block/inode bitmaps won't cause the kernel to panic, but to rather to call ext2_error() instead. (Users can configure whether ext2_errors cause a panic, remounting the filesystem r/o, or ignoring the error.) I also removed a duplicated static function in ialloc.c and balloc.c by making it a global function and removing one of the duplicated functions.
Linus, could we *please* get these patches into the mainline kernel branch? Is there something I can do to help you get them into the kernel? Many thanks.... - Ted
Patch generated: on Thu Apr 2 13:44:11 EST 1998 by tytso@rsts-11.mit.edu against Linux version 2.1.92
=================================================================== RCS file: include/linux/RCS/ext2_fs.h,v retrieving revision 1.1 diff -u -r1.1 include/linux/ext2_fs.h --- include/linux/ext2_fs.h 1998/04/02 15:42:16 1.1 +++ include/linux/ext2_fs.h 1998/04/02 17:10:59 @@ -31,6 +31,7 @@ * Define EXT2_PREALLOCATE to preallocate data blocks for expanding files */ #define EXT2_PREALLOCATE +#define EXT2_DEFAULT_PREALLOC_BLOCKS 8 /* * The second extended file system version @@ -190,8 +191,18 @@ #define EXT2_APPEND_FL 0x00000020 /* writes to file may only append */ #define EXT2_NODUMP_FL 0x00000040 /* do not dump file */ #define EXT2_NOATIME_FL 0x00000080 /* do not update atime */ +/* Reserved for compression usage... */ +#define EXT2_DIRTY_FL 0x00000100 +#define EXT2_COMPRBLK_FL 0x00000200 /* One or more compressed clusters */ +#define EXT2_NOCOMP_FL 0x00000400 /* Don't compress */ +#define EXT2_ECOMPR_FL 0x00000800 /* Compression error */ +/* End compression flags --- maybe not all used */ +#define EXT2_BTREE_FL 0x00001000 /* btree format dir */ #define EXT2_RESERVED_FL 0x80000000 /* reserved for ext2 lib */ - + +#define EXT2_FL_USER_VISIBLE 0x00001FFF /* User visible flags */ +#define EXT2_FL_USER_MODIFIABLE 0x000000FF /* User modifiable flags */ + /* * ioctl commands */ @@ -255,6 +266,8 @@ } osd2; /* OS dependent 2 */ }; +#define i_size_high i_dir_acl + #if defined(__KERNEL__) || defined(__linux__) #define i_reserved1 osd1.linux1.l_i_reserved1 #define i_frag osd2.linux2.l_i_frag @@ -367,9 +380,26 @@ __u8 s_uuid[16]; /* 128-bit uuid for volume */ char s_volume_name[16]; /* volume name */ char s_last_mounted[64]; /* directory where last mounted */ - __u32 s_reserved[206]; /* Padding to the end of the block */ + __u32 s_algorithm_usage_bitmap; /* For compression */ + /* + * Performance hints. Directory preallocation should only + * happen if the EXT2_COMPAT_PREALLOC flag is on. + */ + __u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/ + __u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */ + __u16 s_padding1; + __u32 s_reserved[204]; /* Padding to the end of the block */ }; +#ifdef __KERNEL__ +#define EXT2_SB(sb) (&((sb)->u.ext2_sb)) +#else +/* Assume that user mode programs are passing in an ext2fs superblock, not + * a kernel struct super_block. This will allow us to call the feature-test + * macros from user land. */ +#define EXT2_SB(sb) (sb) +#endif + /* * Codes for operating systems */ @@ -394,11 +424,27 @@ * Feature set definitions */ +#define EXT2_HAS_COMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_compat & (mask) ) +#define EXT2_HAS_RO_COMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_ro_compat & (mask) ) +#define EXT2_HAS_INCOMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_incompat & (mask) ) + +#define EXT2_FEATURE_COMPAT_DIR_PREALLOC 0x0001 + #define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 +#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 +#define EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 + +#define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001 +#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002 #define EXT2_FEATURE_COMPAT_SUPP 0 -#define EXT2_FEATURE_INCOMPAT_SUPP 0 -#define EXT2_FEATURE_RO_COMPAT_SUPP EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER +#define EXT2_FEATURE_INCOMPAT_SUPP EXT2_FEATURE_INCOMPAT_FILETYPE +#define EXT2_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \ + EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \ + EXT2_FEATURE_RO_COMPAT_BTREE_DIR) /* * Default values for user and/or group using reserved blocks @@ -419,6 +465,35 @@ }; /* + * The new version of the directory entry. Since EXT2 structures are + * stored in intel byte order, and the name_len field could never be + * bigger than 255 chars, it's safe to reclaim the extra byte for the + * file_type field. + */ +struct ext2_dir_entry_2 { + __u32 inode; /* Inode number */ + __u16 rec_len; /* Directory entry length */ + __u8 name_len; /* Name length */ + __u8 file_type; + char name[EXT2_NAME_LEN]; /* File name */ +}; + +/* + * Ext2 directory file types. Only the low 3 bits are used. The + * other bits are reserved for now. + */ +#define EXT2_FT_UNKNOWN 0 +#define EXT2_FT_REG_FILE 1 +#define EXT2_FT_DIR 2 +#define EXT2_FT_CHRDEV 3 +#define EXT2_FT_BLKDEV 4 +#define EXT2_FT_FIFO 5 +#define EXT2_FT_SOCK 6 +#define EXT2_FT_SYMLINK 7 + +#define EXT2_FT_MAX 8 + +/* * EXT2_DIR_PAD defines the directory entries boundaries * * NOTE: It must be a multiple of 4 @@ -451,13 +526,16 @@ unsigned long); extern unsigned long ext2_count_free_blocks (struct super_block *); extern void ext2_check_blocks_bitmap (struct super_block *); +extern struct ext2_group_desc * ext2_get_group_desc(struct super_block * sb, + unsigned int block_group, + struct buffer_head ** bh); /* bitmap.c */ extern unsigned long ext2_count_free (struct buffer_head *, unsigned); /* dir.c */ extern int ext2_check_dir_entry (const char *, struct inode *, - struct ext2_dir_entry *, struct buffer_head *, + struct ext2_dir_entry_2 *, struct buffer_head *, unsigned long); /* file.c */ =================================================================== RCS file: include/linux/RCS/ext2_fs_i.h,v retrieving revision 1.1 diff -u -r1.1 include/linux/ext2_fs_i.h --- include/linux/ext2_fs_i.h 1998/04/02 15:42:17 1.1 +++ include/linux/ext2_fs_i.h 1998/04/02 15:42:24 @@ -35,6 +35,7 @@ __u32 i_next_alloc_goal; __u32 i_prealloc_block; __u32 i_prealloc_count; + __u32 i_high_size; int i_new_inode:1; /* Is a freshly allocated inode */ }; =================================================================== RCS file: fs/ext2/RCS/dir.c,v retrieving revision 1.1 diff -u -r1.1 fs/ext2/dir.c --- fs/ext2/dir.c 1998/04/02 15:42:17 1.1 +++ fs/ext2/dir.c 1998/04/02 15:42:24 @@ -75,7 +75,8 @@ }; int ext2_check_dir_entry (const char * function, struct inode * dir, - struct ext2_dir_entry * de, struct buffer_head * bh, + struct ext2_dir_entry_2 * de, + struct buffer_head * bh, unsigned long offset) { const char * error_msg = NULL; @@ -84,7 +85,7 @@ error_msg = "rec_len is smaller than minimal"; else if (le16_to_cpu(de->rec_len) % 4 != 0) error_msg = "rec_len % 4 != 0"; - else if (le16_to_cpu(de->rec_len) < EXT2_DIR_REC_LEN(le16_to_cpu(de->name_len))) + else if (le16_to_cpu(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) + le16_to_cpu(de->rec_len) > dir->i_sb->s_blocksize) @@ -97,7 +98,7 @@ "offset=%lu, inode=%lu, rec_len=%d, name_len=%d", dir->i_ino, error_msg, offset, (unsigned long) le32_to_cpu(de->inode), - le16_to_cpu(de->rec_len), le16_to_cpu(de->name_len)); + le16_to_cpu(de->rec_len), de->name_len); return error_msg == NULL ? 1 : 0; } @@ -108,7 +109,7 @@ unsigned long offset, blk; int i, num, stored; struct buffer_head * bh, * tmp, * bha[16]; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; struct super_block * sb; int err; struct inode *inode = filp->f_dentry->d_inode; @@ -158,7 +159,7 @@ * to make sure. */ if (filp->f_version != inode->i_version) { for (i = 0; i < sb->s_blocksize && i < offset; ) { - de = (struct ext2_dir_entry *) + de = (struct ext2_dir_entry_2 *) (bh->b_data + i); /* It's too expensive to do a full * dirent test each time round this @@ -178,7 +179,7 @@ while (!error && filp->f_pos < inode->i_size && offset < sb->s_blocksize) { - de = (struct ext2_dir_entry *) (bh->b_data + offset); + de = (struct ext2_dir_entry_2 *) (bh->b_data + offset); if (!ext2_check_dir_entry ("ext2_readdir", inode, de, bh, offset)) { /* On error, skip the f_pos to the @@ -200,7 +201,7 @@ unsigned long version = inode->i_version; error = filldir(dirent, de->name, - le16_to_cpu(de->name_len), + de->name_len, filp->f_pos, le32_to_cpu(de->inode)); if (error) break; =================================================================== RCS file: fs/ext2/RCS/namei.c,v retrieving revision 1.1 diff -u -r1.1 fs/ext2/namei.c --- fs/ext2/namei.c 1998/04/02 15:42:17 1.1 +++ fs/ext2/namei.c 1998/04/02 15:42:24 @@ -14,6 +14,8 @@ * * Big-endian to little-endian byte-swapping/bitmaps by * David S. Miller (davem@caip.rutgers.edu), 1995 + * Directory entry file type support and forward compatibility hooks + * for B-tree directories by Theodore Ts'o (tytso@mit.edu), 1998 */ #include <asm/uaccess.h> @@ -39,18 +41,12 @@ /* * 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) +static inline int ext2_match (int len, const char * const name, + struct ext2_dir_entry_2 * de) { if (!de || !le32_to_cpu(de->inode) || len > EXT2_NAME_LEN) return 0; - /* - * "" means "." ---> so paths like "/usr/lib//libc.a" work - */ - if (!len && le16_to_cpu(de->name_len) == 1 && (de->name[0] == '.') && - (de->name[1] == '\0')) - return 1; - if (len != le16_to_cpu(de->name_len)) + if (len != de->name_len) return 0; return !memcmp(name, de->name, len); } @@ -65,7 +61,7 @@ */ static struct buffer_head * ext2_find_entry (struct inode * dir, const char * const name, int namelen, - struct ext2_dir_entry ** res_dir) + struct ext2_dir_entry_2 ** res_dir) { struct super_block * sb; struct buffer_head * bh_use[NAMEI_RA_SIZE]; @@ -96,7 +92,7 @@ for (block = 0, offset = 0; offset < dir->i_size; block++) { struct buffer_head * bh; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; char * dlimit; if ((block % NAMEI_RA_BLOCKS) == 0 && toread) { @@ -105,9 +101,11 @@ } bh = bh_use[block % NAMEI_RA_SIZE]; if (!bh) { +#if 0 ext2_error (sb, "ext2_find_entry", "directory #%lu contains a hole at offset %lu", dir->i_ino, offset); +#endif offset += sb->s_blocksize; continue; } @@ -119,13 +117,13 @@ break; } - de = (struct ext2_dir_entry *) bh->b_data; + de = (struct ext2_dir_entry_2 *) 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 (le32_to_cpu(de->inode) != 0 && ext2_match (namelen, name, de)) { + if (ext2_match (namelen, name, de)) { for (i = 0; i < NAMEI_RA_SIZE; ++i) { if (bh_use[i] != bh) brelse (bh_use[i]); @@ -134,7 +132,7 @@ return bh; } offset += le16_to_cpu(de->rec_len); - de = (struct ext2_dir_entry *) + de = (struct ext2_dir_entry_2 *) ((char *) de + le16_to_cpu(de->rec_len)); } @@ -158,7 +156,7 @@ int ext2_lookup(struct inode * dir, struct dentry *dentry) { struct inode * inode; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; struct buffer_head * bh; if (dentry->d_name.len > EXT2_NAME_LEN) @@ -190,13 +188,13 @@ */ static struct buffer_head * ext2_add_entry (struct inode * dir, const char * name, int namelen, - struct ext2_dir_entry ** res_dir, + struct ext2_dir_entry_2 ** res_dir, int *err) { unsigned long offset; unsigned short rec_len; struct buffer_head * bh; - struct ext2_dir_entry * de, * de1; + struct ext2_dir_entry_2 * de, * de1; struct super_block * sb; *err = -EINVAL; @@ -226,7 +224,7 @@ return NULL; rec_len = EXT2_DIR_REC_LEN(namelen); offset = 0; - de = (struct ext2_dir_entry *) bh->b_data; + de = (struct ext2_dir_entry_2 *) bh->b_data; *err = -ENOSPC; while (1) { if ((char *)de >= sb->s_blocksize + bh->b_data) { @@ -243,16 +241,17 @@ ext2_debug ("creating next block\n"); - de = (struct ext2_dir_entry *) bh->b_data; + de = (struct ext2_dir_entry_2 *) bh->b_data; de->inode = le32_to_cpu(0); de->rec_len = le16_to_cpu(sb->s_blocksize); dir->i_size = offset + sb->s_blocksize; + dir->u.ext2_i.i_flags &= ~EXT2_BTREE_FL; mark_inode_dirty(dir); } else { ext2_debug ("skipping to next block\n"); - de = (struct ext2_dir_entry *) bh->b_data; + de = (struct ext2_dir_entry_2 *) bh->b_data; } } if (!ext2_check_dir_entry ("ext2_add_entry", dir, de, bh, @@ -261,24 +260,25 @@ brelse (bh); return NULL; } - if (le32_to_cpu(de->inode) != 0 && ext2_match (namelen, name, de)) { + if (ext2_match (namelen, name, de)) { *err = -EEXIST; brelse (bh); return NULL; } if ((le32_to_cpu(de->inode) == 0 && le16_to_cpu(de->rec_len) >= rec_len) || - (le16_to_cpu(de->rec_len) >= EXT2_DIR_REC_LEN(le16_to_cpu(de->name_len)) + rec_len)) { + (le16_to_cpu(de->rec_len) >= EXT2_DIR_REC_LEN(de->name_len) + rec_len)) { offset += le16_to_cpu(de->rec_len); if (le32_to_cpu(de->inode)) { - de1 = (struct ext2_dir_entry *) ((char *) de + - EXT2_DIR_REC_LEN(le16_to_cpu(de->name_len))); + de1 = (struct ext2_dir_entry_2 *) ((char *) de + + EXT2_DIR_REC_LEN(de->name_len)); de1->rec_len = cpu_to_le16(le16_to_cpu(de->rec_len) - - EXT2_DIR_REC_LEN(le16_to_cpu(de->name_len))); - de->rec_len = cpu_to_le16(EXT2_DIR_REC_LEN(le16_to_cpu(de->name_len))); + EXT2_DIR_REC_LEN(de->name_len)); + de->rec_len = cpu_to_le16(EXT2_DIR_REC_LEN(de->name_len)); de = de1; } de->inode = cpu_to_le32(0); - de->name_len = cpu_to_le16(namelen); + de->name_len = namelen; + de->file_type = 0; memcpy (de->name, name, namelen); /* * XXX shouldn't update any times until successful @@ -292,6 +292,7 @@ * and/or different from the directory change time. */ dir->i_mtime = dir->i_ctime = CURRENT_TIME; + dir->u.ext2_i.i_flags &= ~EXT2_BTREE_FL; mark_inode_dirty(dir); dir->i_version = ++event; mark_buffer_dirty(bh, 1); @@ -300,7 +301,7 @@ return bh; } offset += le16_to_cpu(de->rec_len); - de = (struct ext2_dir_entry *) ((char *) de + le16_to_cpu(de->rec_len)); + de = (struct ext2_dir_entry_2 *) ((char *) de + le16_to_cpu(de->rec_len)); } brelse (bh); return NULL; @@ -310,15 +311,15 @@ * ext2_delete_entry deletes a directory entry by merging it with the * previous entry */ -static int ext2_delete_entry (struct ext2_dir_entry * dir, +static int ext2_delete_entry (struct ext2_dir_entry_2 * dir, struct buffer_head * bh) { - struct ext2_dir_entry * de, * pde; + struct ext2_dir_entry_2 * de, * pde; int i; i = 0; pde = NULL; - de = (struct ext2_dir_entry *) bh->b_data; + de = (struct ext2_dir_entry_2 *) bh->b_data; while (i < bh->b_size) { if (!ext2_check_dir_entry ("ext2_delete_entry", NULL, de, bh, i)) @@ -333,7 +334,7 @@ } i += le16_to_cpu(de->rec_len); pde = de; - de = (struct ext2_dir_entry *) ((char *) de + le16_to_cpu(de->rec_len)); + de = (struct ext2_dir_entry_2 *) ((char *) de + le16_to_cpu(de->rec_len)); } return -ENOENT; } @@ -350,7 +351,7 @@ { struct inode * inode; struct buffer_head * bh; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; int err = -EIO; /* @@ -371,6 +372,9 @@ return err; } de->inode = cpu_to_le32(inode->i_ino); + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_REG_FILE; dir->i_version = ++event; mark_buffer_dirty(bh, 1); if (IS_SYNC(dir)) { @@ -386,7 +390,7 @@ { struct inode * inode; struct buffer_head * bh; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; int err = -EIO; err = -ENAMETOOLONG; @@ -400,29 +404,48 @@ inode->i_uid = current->fsuid; inode->i_mode = mode; inode->i_op = NULL; - if (S_ISREG(inode->i_mode)) + bh = ext2_add_entry (dir, dentry->d_name.name, dentry->d_name.len, &de, &err); + if (!bh) + goto out_no_entry; + de->inode = cpu_to_le32(inode->i_ino); + dir->i_version = ++event; + if (S_ISREG(inode->i_mode)) { inode->i_op = &ext2_file_inode_operations; - else if (S_ISDIR(inode->i_mode)) { + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_REG_FILE; + } 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; + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_DIR; } - else if (S_ISLNK(inode->i_mode)) + else if (S_ISLNK(inode->i_mode)) { inode->i_op = &ext2_symlink_inode_operations; - else if (S_ISCHR(inode->i_mode)) + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_SYMLINK; + } else if (S_ISCHR(inode->i_mode)) { inode->i_op = &chrdev_inode_operations; - else if (S_ISBLK(inode->i_mode)) + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_CHRDEV; + } else if (S_ISBLK(inode->i_mode)) { inode->i_op = &blkdev_inode_operations; - else if (S_ISFIFO(inode->i_mode)) + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_BLKDEV; + } else if (S_ISFIFO(inode->i_mode)) { init_fifo(inode); + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_FIFO; + } if (S_ISBLK(mode) || S_ISCHR(mode)) inode->i_rdev = to_kdev_t(rdev); mark_inode_dirty(inode); - bh = ext2_add_entry (dir, dentry->d_name.name, dentry->d_name.len, &de, &err); - if (!bh) - goto out_no_entry; - de->inode = cpu_to_le32(inode->i_ino); - dir->i_version = ++event; mark_buffer_dirty(bh, 1); if (IS_SYNC(dir)) { ll_rw_block (WRITE, 1, &bh); @@ -445,7 +468,7 @@ { struct inode * inode; struct buffer_head * bh, * dir_block; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; int err; err = -ENAMETOOLONG; @@ -463,6 +486,7 @@ inode->i_op = &ext2_dir_inode_operations; inode->i_size = inode->i_sb->s_blocksize; + inode->i_blocks = 0; dir_block = ext2_bread (inode, 0, 1, &err); if (!dir_block) { inode->i_nlink--; /* is this nlink == 0? */ @@ -470,17 +494,22 @@ iput (inode); return err; } - inode->i_blocks = inode->i_sb->s_blocksize / 512; - de = (struct ext2_dir_entry *) dir_block->b_data; + de = (struct ext2_dir_entry_2 *) dir_block->b_data; de->inode = cpu_to_le32(inode->i_ino); - de->name_len = cpu_to_le16(1); - de->rec_len = cpu_to_le16(EXT2_DIR_REC_LEN(le16_to_cpu(de->name_len))); + de->name_len = 1; + de->rec_len = cpu_to_le16(EXT2_DIR_REC_LEN(de->name_len)); strcpy (de->name, "."); - de = (struct ext2_dir_entry *) ((char *) de + le16_to_cpu(de->rec_len)); + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_DIR; + de = (struct ext2_dir_entry_2 *) ((char *) de + le16_to_cpu(de->rec_len)); de->inode = cpu_to_le32(dir->i_ino); de->rec_len = cpu_to_le16(inode->i_sb->s_blocksize - EXT2_DIR_REC_LEN(1)); - de->name_len = cpu_to_le16(2); + de->name_len = 2; strcpy (de->name, ".."); + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_DIR; inode->i_nlink = 2; mark_buffer_dirty(dir_block, 1); brelse (dir_block); @@ -492,6 +521,9 @@ if (!bh) goto out_no_entry; de->inode = cpu_to_le32(inode->i_ino); + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_DIR; dir->i_version = ++event; mark_buffer_dirty(bh, 1); if (IS_SYNC(dir)) { @@ -499,6 +531,7 @@ wait_on_buffer (bh); } dir->i_nlink++; + dir->u.ext2_i.i_flags &= ~EXT2_BTREE_FL; mark_inode_dirty(dir); d_instantiate(dentry, inode); brelse (bh); @@ -520,7 +553,7 @@ { unsigned long offset; struct buffer_head * bh; - struct ext2_dir_entry * de, * de1; + struct ext2_dir_entry_2 * de, * de1; struct super_block * sb; int err; @@ -532,8 +565,8 @@ inode->i_ino); return 1; } - de = (struct ext2_dir_entry *) bh->b_data; - de1 = (struct ext2_dir_entry *) ((char *) de + le16_to_cpu(de->rec_len)); + de = (struct ext2_dir_entry_2 *) bh->b_data; + de1 = (struct ext2_dir_entry_2 *) ((char *) de + le16_to_cpu(de->rec_len)); if (le32_to_cpu(de->inode) != inode->i_ino || !le32_to_cpu(de1->inode) || strcmp (".", de->name) || strcmp ("..", de1->name)) { ext2_warning (inode->i_sb, "empty_dir", @@ -542,19 +575,21 @@ return 1; } offset = le16_to_cpu(de->rec_len) + le16_to_cpu(de1->rec_len); - de = (struct ext2_dir_entry *) ((char *) de1 + le16_to_cpu(de1->rec_len)); + de = (struct ext2_dir_entry_2 *) ((char *) de1 + le16_to_cpu(de1->rec_len)); while (offset < inode->i_size ) { if (!bh || (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) { +#if 0 ext2_error (sb, "empty_dir", "directory #%lu contains a hole at offset %lu", inode->i_ino, offset); +#endif offset += sb->s_blocksize; continue; } - de = (struct ext2_dir_entry *) bh->b_data; + de = (struct ext2_dir_entry_2 *) bh->b_data; } if (!ext2_check_dir_entry ("empty_dir", inode, de, bh, offset)) { @@ -566,7 +601,7 @@ return 0; } offset += le16_to_cpu(de->rec_len); - de = (struct ext2_dir_entry *) ((char *) de + le16_to_cpu(de->rec_len)); + de = (struct ext2_dir_entry_2 *) ((char *) de + le16_to_cpu(de->rec_len)); } brelse (bh); return 1; @@ -577,7 +612,7 @@ int retval; struct inode * inode; struct buffer_head * bh; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; retval = -ENAMETOOLONG; if (dentry->d_name.len > EXT2_NAME_LEN) @@ -652,6 +687,7 @@ mark_inode_dirty(inode); dir->i_nlink--; inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->u.ext2_i.i_flags &= ~EXT2_BTREE_FL; mark_inode_dirty(dir); d_delete(dentry); @@ -666,7 +702,7 @@ int retval; struct inode * inode; struct buffer_head * bh; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; retval = -ENAMETOOLONG; if (dentry->d_name.len > EXT2_NAME_LEN) @@ -711,6 +747,7 @@ wait_on_buffer (bh); } dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->u.ext2_i.i_flags &= ~EXT2_BTREE_FL; mark_inode_dirty(dir); inode->i_nlink--; mark_inode_dirty(inode); @@ -726,7 +763,7 @@ int ext2_symlink (struct inode * dir, struct dentry *dentry, const char * symname) { - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; struct inode * inode; struct buffer_head * bh = NULL, * name_block = NULL; char * link; @@ -774,6 +811,9 @@ if (!bh) goto out_no_entry; de->inode = cpu_to_le32(inode->i_ino); + if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + de->file_type = EXT2_FT_SYMLINK; dir->i_version = ++event; mark_buffer_dirty(bh, 1); if (IS_SYNC(dir)) { @@ -797,7 +837,7 @@ struct inode * dir, struct dentry *dentry) { struct inode *inode = old_dentry->d_inode; - struct ext2_dir_entry * de; + struct ext2_dir_entry_2 * de; struct buffer_head * bh; int err; @@ -815,6 +855,21 @@ return err; de->inode = cpu_to_le32(inode->i_ino); + if (EXT2_HAS_INCOMPAT_FEATURE(inode->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) { + if (S_ISREG(inode->i_mode)) + de->file_type = EXT2_FT_REG_FILE; + else if (S_ISDIR(inode->i_mode)) + de->file_type = EXT2_FT_DIR; + else if (S_ISLNK(inode->i_mode)) + de->file_type = EXT2_FT_SYMLINK; + else if (S_ISCHR(inode->i_mode)) + de->file_type = EXT2_FT_CHRDEV; + else if (S_ISBLK(inode->i_mode)) + de->file_type = EXT2_FT_BLKDEV; + else if (S_ISFIFO(inode->i_mode)) + de->file_type = EXT2_FT_FIFO; + } dir->i_version = ++event; mark_buffer_dirty(bh, 1); if (IS_SYNC(dir)) { @@ -831,8 +886,8 @@ } #define PARENT_INO(buffer) \ - ((struct ext2_dir_entry *) ((char *) buffer + \ - le16_to_cpu(((struct ext2_dir_entry *) buffer)->rec_len)))->inode + ((struct ext2_dir_entry_2 *) ((char *) buffer + \ + le16_to_cpu(((struct ext2_dir_entry_2 *) buffer)->rec_len)))->inode /* * rename uses retrying to avoid race-conditions: at least they should be @@ -850,7 +905,7 @@ { struct inode * old_inode, * new_inode; struct buffer_head * old_bh, * new_bh, * dir_bh; - struct ext2_dir_entry * old_de, * new_de; + struct ext2_dir_entry_2 * old_de, * new_de; int retval; old_bh = new_bh = dir_bh = NULL; @@ -942,6 +997,10 @@ * ok, that's it */ new_de->inode = le32_to_cpu(old_inode->i_ino); + if (EXT2_HAS_INCOMPAT_FEATURE(new_dir->i_sb, + EXT2_FEATURE_INCOMPAT_FILETYPE)) + new_de->file_type = old_de->file_type; + ext2_delete_entry (old_de, old_bh); old_dir->i_version = ++event; @@ -951,6 +1010,7 @@ mark_inode_dirty(new_inode); } old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME; + old_dir->u.ext2_i.i_flags &= ~EXT2_BTREE_FL; mark_inode_dirty(old_dir); if (dir_bh) { PARENT_INO(dir_bh->b_data) = le32_to_cpu(new_dir->i_ino); @@ -962,6 +1022,7 @@ mark_inode_dirty(new_inode); } else { new_dir->i_nlink++; + new_dir->u.ext2_i.i_flags &= ~EXT2_BTREE_FL; mark_inode_dirty(new_dir); } } =================================================================== RCS file: fs/ext2/RCS/file.c,v retrieving revision 1.1 diff -u -r1.1 fs/ext2/file.c --- fs/ext2/file.c 1998/04/02 15:42:17 1.1 +++ fs/ext2/file.c 1998/04/02 17:41:45 @@ -13,6 +13,9 @@ * Copyright (C) 1991, 1992 Linus Torvalds * * ext2 fs regular file handling primitives + * + * 64-bit file support on 64-bit platforms by Jakub Jelinek + * (jj@sunsite.ms.mff.cuni.cz) */ #include <asm/uaccess.h> @@ -36,6 +39,23 @@ static long long ext2_file_lseek(struct file *, long long, int); static ssize_t ext2_file_write (struct file *, const char *, size_t, loff_t *); static int ext2_release_file (struct inode *, struct file *); +#if BITS_PER_LONG < 64 +static int ext2_open_file (struct inode *, struct file *); + +#else + +#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) +}; + +#endif /* * We have mostly NULL's here: the current defaults are ok for @@ -49,7 +69,11 @@ NULL, /* poll - default */ ext2_ioctl, /* ioctl */ generic_file_mmap, /* mmap */ +#if BITS_PER_LONG == 64 NULL, /* no special open is needed */ +#else + ext2_open_file, +#endif ext2_release_file, /* release */ ext2_sync_file, /* fsync */ NULL, /* fasync */ @@ -86,7 +110,6 @@ long long offset, int origin) { - long long retval; struct inode *inode = file->f_dentry->d_inode; switch (origin) { @@ -96,17 +119,20 @@ case 1: offset += file->f_pos; } - retval = -EINVAL; - /* make sure the offset fits in 32 bits */ - if (((unsigned long long) offset >> 32) == 0) { - if (offset != file->f_pos) { - file->f_pos = offset; - file->f_reada = 0; - file->f_version = ++event; - } - retval = offset; + if (((unsigned long long) offset >> 32) != 0) { +#if BITS_PER_LONG < 64 + return -EINVAL; +#else + if (offset > ext2_max_sizes[EXT2_BLOCK_SIZE_BITS(inode->i_sb)]) + return -EINVAL; +#endif + } + if (offset != file->f_pos) { + file->f_pos = offset; + file->f_reada = 0; + file->f_version = ++event; } - return retval; + return offset; } static inline void remove_suid(struct inode *inode) @@ -128,7 +154,7 @@ size_t count, loff_t *ppos) { struct inode * inode = filp->f_dentry->d_inode; - __u32 pos; + off_t pos; long block; int offset; int written, c; @@ -165,14 +191,37 @@ pos = *ppos; if (pos != *ppos) return -EINVAL; +#if BITS_PER_LONG >= 64 + if (pos > ext2_max_sizes[EXT2_BLOCK_SIZE_BITS(sb)]) + return -EINVAL; +#endif } /* Check for overflow.. */ +#if BITS_PER_LONG < 64 if (pos > (__u32) (pos + count)) { count = ~pos; /* == 0xFFFFFFFF - pos */ if (!count) return -EFBIG; } +#else + off_t max = ext2_max_sizes[EXT2_BLOCK_SIZE_BITS(sb)]; + + if (pos + count > max) { + count = max - pos; + if (!count) + return -EFBIG; + } + if (((pos + count) >> 32) && + !(sb->u.ext2_sb.s_es->s_feature_ro_compat & + cpu_to_le32(EXT2_FEATURE_RO_COMPAT_LARGE_FILE))) { + /* If this is the first large file created, add a flag + to the superblock */ + sb->u.ext2_sb.s_es->s_feature_ro_compat |= + cpu_to_le32(EXT2_FEATURE_RO_COMPAT_LARGE_FILE); + mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1); + } +#endif /* * If a file has been opened in synchronous mode, we have to ensure @@ -265,12 +314,25 @@ /* * Called when an inode is released. Note that this is different - * from ext2_open: open gets called at every open, but release + * from ext2_file_open: open gets called at every open, but release * gets called only when /all/ the files are closed. */ static int ext2_release_file (struct inode * inode, struct file * filp) { - if (filp->f_mode & 2) + if (filp->f_mode & FMODE_WRITE) ext2_discard_prealloc (inode); return 0; } + +#if BITS_PER_LONG < 64 +/* + * Called when an inode is about to be open. + * We use this to disallow opening RW large files on 32bit systems. + */ +static int ext2_open_file (struct inode * inode, struct file * filp) +{ + if (inode->u.ext2_i.i_high_size && (filp->f_mode & FMODE_WRITE)) + return -EFBIG; + return 0; +} +#endif =================================================================== RCS file: fs/ext2/RCS/inode.c,v retrieving revision 1.1 diff -u -r1.1 fs/ext2/inode.c --- fs/ext2/inode.c 1998/04/02 15:42:17 1.1 +++ fs/ext2/inode.c 1998/04/02 17:44:24 @@ -12,9 +12,12 @@ * * Copyright (C) 1991, 1992 Linus Torvalds * - * Goal-directed block allocation by Stephen Tweedie (sct@dcs.ed.ac.uk), 1993 + * Goal-directed block allocation by Stephen Tweedie + * (sct@dcs.ed.ac.uk), 1993, 1998 * Big-endian to little-endian byte-swapping/bitmaps by * David S. Miller (davem@caip.rutgers.edu), 1995 + * 64-bit file support on 64-bit platforms by Jakub Jelinek + * (jj@sunsite.ms.mff.cuni.cz) */ #include <asm/uaccess.h> @@ -417,9 +420,44 @@ int create, int *err) { struct buffer_head * bh; - + int prev_blocks; + + prev_blocks = inode->i_blocks; + bh = ext2_getblk (inode, block, create, err); - if (!bh || buffer_uptodate(bh)) + if (!bh) + return bh; + + /* + * If the inode has grown, and this is a directory, then perform + * preallocation of a few more blocks to try to keep directory + * fragmentation down. + */ + if (create && + S_ISDIR(inode->i_mode) && + inode->i_blocks > prev_blocks && + EXT2_HAS_COMPAT_FEATURE(inode->i_sb, + EXT2_FEATURE_COMPAT_DIR_PREALLOC)) { + int i; + struct buffer_head *tmp_bh; + + for (i = 1; + i < EXT2_SB(inode->i_sb)->s_es->s_prealloc_dir_blocks; + i++) { + /* + * ext2_getblk will zero out the contents of the + * directory for us + */ + tmp_bh = ext2_getblk(inode, block+i, create, err); + if (!tmp_bh) { + brelse (bh); + return 0; + } + brelse (tmp_bh); + } + } + + if (buffer_uptodate(bh)) return bh; ll_rw_block (READ, 1, &bh); wait_on_buffer (bh); @@ -447,18 +485,23 @@ inode->i_ino > le32_to_cpu(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; + goto bad_inode; } 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", + if (block_group >= inode->i_sb->u.ext2_sb.s_groups_count) { + ext2_error (inode->i_sb, "ext2_read_inode", "group >= groups count"); + goto bad_inode; + } group_desc = block_group >> EXT2_DESC_PER_BLOCK_BITS(inode->i_sb); desc = block_group & (EXT2_DESC_PER_BLOCK(inode->i_sb) - 1); bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc]; - if (!bh) - ext2_panic (inode->i_sb, "ext2_read_inode", + if (!bh) { + ext2_error (inode->i_sb, "ext2_read_inode", "Descriptor not loaded"); + goto bad_inode; + } + gdp = (struct ext2_group_desc *) bh->b_data; /* * Figure out the offset within the block group inode table @@ -467,10 +510,12 @@ EXT2_INODE_SIZE(inode->i_sb); block = le32_to_cpu(gdp[desc].bg_inode_table) + (offset >> EXT2_BLOCK_SIZE_BITS(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 - " + if (!(bh = bread (inode->i_dev, block, inode->i_sb->s_blocksize))) { + ext2_error (inode->i_sb, "ext2_read_inode", + "unable to read inode block - " "inode=%lu, block=%lu", inode->i_ino, block); + goto bad_inode; + } offset &= (EXT2_BLOCK_SIZE(inode->i_sb) - 1); raw_inode = (struct ext2_inode *) (bh->b_data + offset); @@ -493,7 +538,20 @@ 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 = le32_to_cpu(raw_inode->i_file_acl); - inode->u.ext2_i.i_dir_acl = le32_to_cpu(raw_inode->i_dir_acl); + if (S_ISDIR(inode->i_mode)) + inode->u.ext2_i.i_dir_acl = le32_to_cpu(raw_inode->i_dir_acl); + else { + inode->u.ext2_i.i_dir_acl = 0; + inode->u.ext2_i.i_high_size = + le32_to_cpu(raw_inode->i_size_high); +#if BITS_PER_LONG < 64 + if (raw_inode->i_size_high) + inode->i_size = (__u32)-1; +#else + inode->i_size |= ((__u64)le32_to_cpu(raw_inode->i_size_high)) + << 32; +#endif + } inode->u.ext2_i.i_version = le32_to_cpu(raw_inode->i_version); inode->u.ext2_i.i_block_group = block_group; inode->u.ext2_i.i_next_alloc_block = 0; @@ -542,6 +600,11 @@ inode->i_attr_flags |= ATTR_FLAG_NOATIME; inode->i_flags |= MS_NOATIME; } + return; + +bad_inode: + make_bad_inode(inode); + return; } static int ext2_update_inode(struct inode * inode, int do_sync) @@ -561,18 +624,22 @@ inode->i_ino > le32_to_cpu(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; + return -EIO; } 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", + if (block_group >= inode->i_sb->u.ext2_sb.s_groups_count) { + ext2_error (inode->i_sb, "ext2_write_inode", "group >= groups count"); + return -EIO; + } group_desc = block_group >> EXT2_DESC_PER_BLOCK_BITS(inode->i_sb); desc = block_group & (EXT2_DESC_PER_BLOCK(inode->i_sb) - 1); bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc]; - if (!bh) - ext2_panic (inode->i_sb, "ext2_write_inode", + if (!bh) { + ext2_error (inode->i_sb, "ext2_write_inode", "Descriptor not loaded"); + return -EIO; + } gdp = (struct ext2_group_desc *) bh->b_data; /* * Figure out the offset within the block group inode table @@ -581,10 +648,12 @@ EXT2_INODE_SIZE(inode->i_sb); block = le32_to_cpu(gdp[desc].bg_inode_table) + (offset >> EXT2_BLOCK_SIZE_BITS(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 - " + if (!(bh = bread (inode->i_dev, block, inode->i_sb->s_blocksize))) { + ext2_error (inode->i_sb, "ext2_write_inode", + "unable to read inode block - " "inode=%lu, block=%lu", inode->i_ino, block); + return -EIO; + } offset &= EXT2_BLOCK_SIZE(inode->i_sb) - 1; raw_inode = (struct ext2_inode *) (bh->b_data + offset); @@ -603,7 +672,16 @@ 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 = cpu_to_le32(inode->u.ext2_i.i_file_acl); - raw_inode->i_dir_acl = cpu_to_le32(inode->u.ext2_i.i_dir_acl); + if (S_ISDIR(inode->i_mode)) + raw_inode->i_dir_acl = cpu_to_le32(inode->u.ext2_i.i_dir_acl); + else { +#if BITS_PER_LONG < 64 + raw_inode->i_size_high = + cpu_to_le32(inode->u.ext2_i.i_high_size); +#else + raw_inode->i_size_high = cpu_to_le32(inode->i_size >> 32); +#endif + } raw_inode->i_version = cpu_to_le32(inode->u.ext2_i.i_version); if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) raw_inode->i_block[0] = cpu_to_le32(kdev_t_to_nr(inode->i_rdev)); @@ -620,7 +698,7 @@ printk ("IO error syncing ext2 inode [" "%s:%08lx]\n", kdevname(inode->i_dev), inode->i_ino); - err = -1; + err = -EIO; } } brelse (bh); =================================================================== RCS file: fs/ext2/RCS/ioctl.c,v retrieving revision 1.1 diff -u -r1.1 fs/ext2/ioctl.c --- fs/ext2/ioctl.c 1998/04/02 15:42:17 1.1 +++ fs/ext2/ioctl.c 1998/04/02 15:42:25 @@ -19,16 +19,18 @@ int ext2_ioctl (struct inode * inode, struct file * filp, unsigned int cmd, unsigned long arg) { - unsigned long flags; + unsigned int flags; ext2_debug ("cmd = %u, arg = %lu\n", cmd, arg); switch (cmd) { case EXT2_IOC_GETFLAGS: + flags = inode->u.ext2_i.i_flags & EXT2_FL_USER_VISIBLE; return put_user(inode->u.ext2_i.i_flags, (int *) arg); case EXT2_IOC_SETFLAGS: if (get_user(flags, (int *) arg)) - return -EFAULT; + return -EFAULT; + flags = flags & EXT2_FL_USER_MODIFIABLE; /* * The IMMUTABLE and APPEND_ONLY flags can only be changed by * the super user when the security level is zero. @@ -44,7 +46,8 @@ return -EPERM; if (IS_RDONLY(inode)) return -EROFS; - inode->u.ext2_i.i_flags = flags; + inode->u.ext2_i.i_flags = (inode->u.ext2_i.i_flags & + ~EXT2_FL_USER_MODIFIABLE) | flags; if (flags & EXT2_SYNC_FL) inode->i_flags |= MS_SYNCHRONOUS; else =================================================================== RCS file: fs/ext2/RCS/ialloc.c,v retrieving revision 1.1 diff -u -r1.1 fs/ext2/ialloc.c --- fs/ext2/ialloc.c 1998/04/02 15:43:57 1.1 +++ fs/ext2/ialloc.c 1998/04/02 18:27:32 @@ -37,50 +37,41 @@ #include <asm/bitops.h> #include <asm/byteorder.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, +/* + * Read the inode allocation bitmap for a given block_group, reading + * into the specified slot in the superblock's bitmap cache. + * + * Return >=0 on success or a -ve error code. + */ +static int read_inode_bitmap (struct super_block * sb, unsigned long block_group, unsigned int bitmap_nr) { struct ext2_group_desc * gdp; - struct buffer_head * bh; + struct buffer_head * bh = NULL; + int retval = 0; - gdp = get_group_desc (sb, block_group, NULL); + gdp = ext2_get_group_desc (sb, block_group, NULL); + if (!gdp) { + retval = -EIO; + goto error_out; + } bh = bread (sb->s_dev, le32_to_cpu(gdp->bg_inode_bitmap), sb->s_blocksize); - if (!bh) - ext2_panic (sb, "read_inode_bitmap", + if (!bh) { + ext2_error (sb, "read_inode_bitmap", "Cannot read inode bitmap - " "block_group = %lu, inode_bitmap = %lu", - block_group, (unsigned long) le32_to_cpu(gdp->bg_inode_bitmap)); + block_group, (unsigned long) gdp->bg_inode_bitmap); + retval = -EIO; + } + /* + * On IO error, just leave a zero in the superblock's block pointer for + * this group. The IO will be retried next time. + */ +error_out: sb->u.ext2_sb.s_inode_bitmap_number[bitmap_nr] = block_group; sb->u.ext2_sb.s_inode_bitmap[bitmap_nr] = bh; + return retval; } /* @@ -93,11 +84,13 @@ * 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. + * + * Return the slot used to store the bitmap, or a -ve error code. */ static int load_inode_bitmap (struct super_block * sb, unsigned int block_group) { - int i, j; + int i, j, retval = 0; unsigned long inode_bitmap_number; struct buffer_head * inode_bitmap; @@ -117,7 +110,10 @@ else return block_group; } else { - read_inode_bitmap (sb, block_group, block_group); + retval = read_inode_bitmap (sb, block_group, + block_group); + if (retval < 0) + return retval; return block_group; } } @@ -138,6 +134,15 @@ } sb->u.ext2_sb.s_inode_bitmap_number[0] = inode_bitmap_number; sb->u.ext2_sb.s_inode_bitmap[0] = inode_bitmap; + + /* + * There's still one special case here --- if inode_bitmap == 0 + * then our last attempt to read the bitmap failed and we have + * just ended up caching that failure. Try again to read it. + */ + if (!inode_bitmap) + retval = read_inode_bitmap (sb, block_group, 0); + } else { if (sb->u.ext2_sb.s_loaded_inode_bitmaps < EXT2_MAX_GROUP_LOADED) sb->u.ext2_sb.s_loaded_inode_bitmaps++; @@ -149,9 +154,9 @@ sb->u.ext2_sb.s_inode_bitmap[j] = sb->u.ext2_sb.s_inode_bitmap[j - 1]; } - read_inode_bitmap (sb, block_group, 0); + retval = read_inode_bitmap (sb, block_group, 0); } - return 0; + return retval; } /* @@ -212,13 +217,15 @@ ino > le32_to_cpu(sb->u.ext2_sb.s_es->s_inodes_count)) { ext2_error (sb, "free_inode", "reserved inode or nonexistent inode"); - unlock_super (sb); - return; + goto error_return; } es = sb->u.ext2_sb.s_es; block_group = (ino - 1) / EXT2_INODES_PER_GROUP(sb); bit = (ino - 1) % EXT2_INODES_PER_GROUP(sb); bitmap_nr = load_inode_bitmap (sb, block_group); + if (bitmap_nr < 0) + goto error_return; + bh = sb->u.ext2_sb.s_inode_bitmap[bitmap_nr]; is_directory = S_ISDIR(inode->i_mode); @@ -233,12 +240,14 @@ ext2_warning (sb, "ext2_free_inode", "bit already cleared for inode %lu", ino); else { - gdp = get_group_desc (sb, block_group, &bh2); - gdp->bg_free_inodes_count = - cpu_to_le16(le16_to_cpu(gdp->bg_free_inodes_count) + 1); - if (is_directory) - gdp->bg_used_dirs_count = - cpu_to_le16(le16_to_cpu(gdp->bg_used_dirs_count) - 1); + gdp = ext2_get_group_desc (sb, block_group, &bh2); + if (gdp) { + gdp->bg_free_inodes_count = + cpu_to_le16(le16_to_cpu(gdp->bg_free_inodes_count) + 1); + if (is_directory) + gdp->bg_used_dirs_count = + cpu_to_le16(le16_to_cpu(gdp->bg_used_dirs_count) - 1); + } mark_buffer_dirty(bh2, 1); es->s_free_inodes_count = cpu_to_le32(le32_to_cpu(es->s_free_inodes_count) + 1); @@ -250,6 +259,7 @@ wait_on_buffer (bh); } sb->s_dirt = 1; +error_return: unlock_super (sb); } @@ -317,9 +327,10 @@ /* 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 ((le16_to_cpu(tmp->bg_used_dirs_count) << 8) < - le16_to_cpu(tmp->bg_free_inodes_count)) { + tmp = ext2_get_group_desc (sb, i, &bh2); + if (tmp && + (le16_to_cpu(tmp->bg_used_dirs_count) << 8) < + le16_to_cpu(tmp->bg_free_inodes_count)) { gdp = tmp; break; } @@ -329,9 +340,10 @@ */ if (!gdp) { for (j = 0; j < sb->u.ext2_sb.s_groups_count; j++) { - tmp = get_group_desc (sb, j, &bh2); - if (le16_to_cpu(tmp->bg_free_inodes_count) && - le16_to_cpu(tmp->bg_free_inodes_count) >= avefreei) { + tmp = ext2_get_group_desc (sb, j, &bh2); + if (tmp && + le16_to_cpu(tmp->bg_free_inodes_count) && + le16_to_cpu(tmp->bg_free_inodes_count) >= avefreei) { if (!gdp || (le16_to_cpu(tmp->bg_free_blocks_count) > le16_to_cpu(gdp->bg_free_blocks_count))) { @@ -348,8 +360,8 @@ * Try to place the inode in its parent directory */ i = dir->u.ext2_i.i_block_group; - tmp = get_group_desc (sb, i, &bh2); - if (le16_to_cpu(tmp->bg_free_inodes_count)) + tmp = ext2_get_group_desc (sb, i, &bh2); + if (tmp && le16_to_cpu(tmp->bg_free_inodes_count)) gdp = tmp; else { @@ -361,8 +373,9 @@ 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 (le16_to_cpu(tmp->bg_free_inodes_count)) { + tmp = ext2_get_group_desc (sb, i, &bh2); + if (tmp && + le16_to_cpu(tmp->bg_free_inodes_count)) { gdp = tmp; break; } @@ -376,8 +389,9 @@ 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 (le16_to_cpu(tmp->bg_free_inodes_count)) { + tmp = ext2_get_group_desc (sb, i, &bh2); + if (tmp && + le16_to_cpu(tmp->bg_free_inodes_count)) { gdp = tmp; break; } @@ -391,6 +405,13 @@ return NULL; } bitmap_nr = load_inode_bitmap (sb, i); + if (bitmap_nr < 0) { + unlock_super (sb); + iput(inode); + *err = -EIO; + return NULL; + } + bh = sb->u.ext2_sb.s_inode_bitmap[bitmap_nr]; if ((j = ext2_find_first_zero_bit ((unsigned long *) bh->b_data, EXT2_INODES_PER_GROUP(sb))) < @@ -504,9 +525,14 @@ bitmap_count = 0; gdp = NULL; for (i = 0; i < sb->u.ext2_sb.s_groups_count; i++) { - gdp = get_group_desc (sb, i, NULL); + gdp = ext2_get_group_desc (sb, i, NULL); + if (!gdp) + continue; desc_count += le16_to_cpu(gdp->bg_free_inodes_count); bitmap_nr = load_inode_bitmap (sb, i); + if (bitmap_nr < 0) + continue; + 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", @@ -536,9 +562,14 @@ bitmap_count = 0; gdp = NULL; for (i = 0; i < sb->u.ext2_sb.s_groups_count; i++) { - gdp = get_group_desc (sb, i, NULL); + gdp = ext2_get_group_desc (sb, i, NULL); + if (!gdp) + continue; desc_count += le16_to_cpu(gdp->bg_free_inodes_count); bitmap_nr = load_inode_bitmap (sb, i); + if (bitmap_nr < 0) + continue; + x = ext2_count_free (sb->u.ext2_sb.s_inode_bitmap[bitmap_nr], EXT2_INODES_PER_GROUP(sb) / 8); if (le16_to_cpu(gdp->bg_free_inodes_count) != x) =================================================================== RCS file: fs/ext2/RCS/balloc.c,v retrieving revision 1.1 diff -u -r1.1 fs/ext2/balloc.c --- fs/ext2/balloc.c 1998/04/02 15:42:17 1.1 +++ fs/ext2/balloc.c 1998/04/02 17:22:15 @@ -38,27 +38,33 @@ #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) +struct ext2_group_desc * ext2_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", + if (block_group >= sb->u.ext2_sb.s_groups_count) { + ext2_error (sb, "ext2_get_group_desc", "block_group >= groups_count - " "block_group = %d, groups_count = %lu", block_group, sb->u.ext2_sb.s_groups_count); + return NULL; + } + 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", + if (!sb->u.ext2_sb.s_group_desc[group_desc]) { + ext2_error (sb, "ext2_get_group_desc", "Group descriptor not loaded - " "block_group = %d, group_desc = %lu, desc = %lu", block_group, group_desc, desc); + return NULL; + } + gdp = (struct ext2_group_desc *) sb->u.ext2_sb.s_group_desc[group_desc]->b_data; if (bh) @@ -66,22 +72,40 @@ return gdp + desc; } -static void read_block_bitmap (struct super_block * sb, +/* + * Read the bitmap for a given block_group, reading into the specified + * slot in the superblock's bitmap cache. + * + * Return >=0 on success or a -ve error code. + */ + +static int read_block_bitmap (struct super_block * sb, unsigned int block_group, unsigned long bitmap_nr) { struct ext2_group_desc * gdp; - struct buffer_head * bh; + struct buffer_head * bh = NULL; + int retval = 0; - gdp = get_group_desc (sb, block_group, NULL); + gdp = ext2_get_group_desc (sb, block_group, NULL); + if (!gdp) + goto error_out; bh = bread (sb->s_dev, le32_to_cpu(gdp->bg_block_bitmap), sb->s_blocksize); - if (!bh) - ext2_panic (sb, "read_block_bitmap", + if (!bh) { + ext2_error (sb, "read_block_bitmap", "Cannot read block bitmap - " "block_group = %d, block_bitmap = %lu", - block_group, (unsigned long) le32_to_cpu(gdp->bg_block_bitmap)); + block_group, (unsigned long) gdp->bg_block_bitmap); + retval = -EIO; + } + /* + * On IO error, just leave a zero in the superblock's block pointer for + * this group. The IO will be retried next time. + */ +error_out: sb->u.ext2_sb.s_block_bitmap_number[bitmap_nr] = block_group; sb->u.ext2_sb.s_block_bitmap[bitmap_nr] = bh; + return retval; } /* @@ -94,11 +118,13 @@ * 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. + * + * Return the slot used to store the bitmap, or a -ve error code. */ static int load__block_bitmap (struct super_block * sb, unsigned int block_group) { - int i, j; + int i, j, retval = 0; unsigned long block_bitmap_number; struct buffer_head * block_bitmap; @@ -110,16 +136,16 @@ 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] != + 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; + ext2_error (sb, "load_block_bitmap", + "block_group != block_bitmap_number"); } + retval = read_block_bitmap (sb, block_group, block_group); + if (retval < 0) + return retval; + return block_group; } for (i = 0; i < sb->u.ext2_sb.s_loaded_block_bitmaps && @@ -137,6 +163,14 @@ } sb->u.ext2_sb.s_block_bitmap_number[0] = block_bitmap_number; sb->u.ext2_sb.s_block_bitmap[0] = block_bitmap; + + /* + * There's still one special case here --- if block_bitmap == 0 + * then our last attempt to read the bitmap failed and we have + * just ended up caching that failure. Try again to read it. + */ + if (!block_bitmap) + retval = read_block_bitmap (sb, block_group, 0); } else { if (sb->u.ext2_sb.s_loaded_block_bitmaps < EXT2_MAX_GROUP_LOADED) sb->u.ext2_sb.s_loaded_block_bitmaps++; @@ -148,24 +182,71 @@ sb->u.ext2_sb.s_block_bitmap[j] = sb->u.ext2_sb.s_block_bitmap[j - 1]; } - read_block_bitmap (sb, block_group, 0); + retval = read_block_bitmap (sb, block_group, 0); } - return 0; + return retval; } +/* + * Load the block bitmap for a given block group. First of all do a couple + * of fast lookups for common cases and then pass the request onto the guts + * of the bitmap loader. + * + * Return the slot number of the group in the superblock bitmap cache's on + * success, or a -ve error code. + * + * There is still one inconsistancy here --- if the number of groups in this + * filesystems is <= EXT2_MAX_GROUP_LOADED, then we have no way of + * differentiating between a group for which we have never performed a bitmap + * IO request, and a group for which the last bitmap read request failed. + */ 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; + int slot; - 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; + /* + * Do the lookup for the slot. First of all, check if we're asking + * for the same slot as last time, and did we succeed that last time? + */ + if (sb->u.ext2_sb.s_loaded_block_bitmaps > 0 && + sb->u.ext2_sb.s_block_bitmap_number[0] == block_group && + sb->u.ext2_sb.s_block_bitmap[block_group]) { + slot = 0; + } + /* + * Or can we do a fast lookup based on a loaded group on a filesystem + * small enough to be mapped directly into the superblock? + */ + else 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]) { + slot = block_group; + } + /* + * If not, then do a full lookup for this block group. + */ + else { + slot = load__block_bitmap (sb, block_group); + } - return load__block_bitmap (sb, block_group); + /* + * <0 means we just got an error + */ + if (slot < 0) + return slot; + + /* + * If it's a valid slot, we may still have cached a previous IO error, + * in which case the bh in the superblock cache will be zero. + */ + if (!sb->u.ext2_sb.s_block_bitmap[slot]) + return -EIO; + + /* + * Must have been read in OK to get this far. + */ + return slot; } void ext2_free_blocks (const struct inode * inode, unsigned long block, @@ -194,8 +275,7 @@ ext2_error (sb, "ext2_free_blocks", "Freeing blocks not in datazone - " "block = %lu, count = %lu", block, count); - unlock_super (sb); - return; + goto error_return; } ext2_debug ("freeing block %lu\n", block); @@ -215,8 +295,13 @@ count -= overflow; } bitmap_nr = load_block_bitmap (sb, block_group); + if (bitmap_nr < 0) + goto error_return; + bh = sb->u.ext2_sb.s_block_bitmap[bitmap_nr]; - gdp = get_group_desc (sb, block_group, &bh2); + gdp = ext2_get_group_desc (sb, block_group, &bh2); + if (!gdp) + goto error_return; if (test_opt (sb, CHECK_STRICT) && (in_range (le32_to_cpu(gdp->bg_block_bitmap), block, count) || @@ -259,6 +344,7 @@ goto do_more; } sb->s_dirt = 1; +error_return: unlock_super (sb); return; } @@ -312,7 +398,10 @@ goal >= le32_to_cpu(es->s_blocks_count)) goal = le32_to_cpu(es->s_first_data_block); i = (goal - le32_to_cpu(es->s_first_data_block)) / EXT2_BLOCKS_PER_GROUP(sb); - gdp = get_group_desc (sb, i, &bh2); + gdp = ext2_get_group_desc (sb, i, &bh2); + if (!gdp) + goto io_error; + if (le16_to_cpu(gdp->bg_free_blocks_count) > 0) { j = ((goal - le32_to_cpu(es->s_first_data_block)) % EXT2_BLOCKS_PER_GROUP(sb)); #ifdef EXT2FS_DEBUG @@ -320,6 +409,9 @@ goal_attempts++; #endif bitmap_nr = load_block_bitmap (sb, i); + if (bitmap_nr < 0) + goto io_error; + bh = sb->u.ext2_sb.s_block_bitmap[bitmap_nr]; ext2_debug ("goal is at %d:%d.\n", i, j); @@ -384,7 +476,12 @@ i++; if (i >= sb->u.ext2_sb.s_groups_count) i = 0; - gdp = get_group_desc (sb, i, &bh2); + gdp = ext2_get_group_desc (sb, i, &bh2); + if (!gdp) { + *err = -EIO; + unlock_super (sb); + return 0; + } if (le16_to_cpu(gdp->bg_free_blocks_count) > 0) break; } @@ -393,6 +490,9 @@ return 0; } bitmap_nr = load_block_bitmap (sb, i); + if (bitmap_nr < 0) + goto io_error; + 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; @@ -455,10 +555,16 @@ */ #ifdef EXT2_PREALLOCATE if (prealloc_block) { + int prealloc_goal; + + prealloc_goal = es->s_prealloc_blocks ? + es->s_prealloc_blocks : EXT2_DEFAULT_PREALLOC_BLOCKS; + *prealloc_count = 0; *prealloc_block = tmp + 1; for (k = 1; - k < 8 && (j + k) < EXT2_BLOCKS_PER_GROUP(sb); k++) { + k < prealloc_goal && (j + k) < EXT2_BLOCKS_PER_GROUP(sb); + k++) { if (sb->dq_op) if (sb->dq_op->alloc_block(inode, fs_to_dq_blocks(1, sb->s_blocksize))) break; @@ -516,6 +622,12 @@ unlock_super (sb); *err = 0; return j; + +io_error: + *err = -EIO; + unlock_super (sb); + return 0; + } unsigned long ext2_count_free_blocks (struct super_block * sb) @@ -533,9 +645,14 @@ bitmap_count = 0; gdp = NULL; for (i = 0; i < sb->u.ext2_sb.s_groups_count; i++) { - gdp = get_group_desc (sb, i, NULL); + gdp = ext2_get_group_desc (sb, i, NULL); + if (!gdp) + continue; desc_count += le16_to_cpu(gdp->bg_free_blocks_count); bitmap_nr = load_block_bitmap (sb, i); + if (bitmap_nr < 0) + continue; + x = ext2_count_free (sb->u.ext2_sb.s_block_bitmap[bitmap_nr], sb->s_blocksize); printk ("group %d: stored = %d, counted = %lu\n", @@ -590,12 +707,17 @@ 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); + gdp = ext2_get_group_desc (sb, i, NULL); + if (!gdp) + continue; desc_count += le16_to_cpu(gdp->bg_free_blocks_count); bitmap_nr = load_block_bitmap (sb, i); + if (bitmap_nr < 0) + continue; + bh = sb->u.ext2_sb.s_block_bitmap[bitmap_nr]; - if (!(sb->u.ext2_sb.s_feature_ro_compat & + if (!(le32_to_cpu(sb->u.ext2_sb.s_feature_ro_compat) & EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER) || (test_root(i, 3) || test_root(i, 5) || test_root(i, 7))) { if (!ext2_test_bit (0, bh->b_data)) =================================================================== RCS file: fs/ext2/RCS/super.c,v retrieving revision 1.1 diff -u -r1.1 fs/ext2/super.c --- fs/ext2/super.c 1998/04/02 15:42:17 1.1 +++ fs/ext2/super.c 1998/04/02 15:42:25 @@ -516,11 +516,11 @@ goto failed_mount; } } - sb->u.ext2_sb.s_feature_compat = es->s_feature_compat; - sb->u.ext2_sb.s_feature_incompat = es->s_feature_incompat; - sb->u.ext2_sb.s_feature_ro_compat = es->s_feature_ro_compat; + sb->u.ext2_sb.s_feature_compat = le32_to_cpu(es->s_feature_compat); + sb->u.ext2_sb.s_feature_incompat = le32_to_cpu(es->s_feature_incompat); + sb->u.ext2_sb.s_feature_ro_compat = le32_to_cpu(es->s_feature_ro_compat); sb->u.ext2_sb.s_frag_size = EXT2_MIN_FRAG_SIZE << - (__s32) le32_to_cpu(es->s_log_frag_size); + le32_to_cpu(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; - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu
|  |