Index: linux-2.6.18/fs/ext3/inode.c =================================================================== --- linux-2.6.18.orig/fs/ext3/inode.c +++ linux-2.6.18/fs/ext3/inode.c @@ -2690,6 +2690,13 @@ void ext3_read_inode(struct inode * inod EXT3_INODE_GET_XTIME(i_atime, inode, raw_inode); EXT3_EINODE_GET_XTIME(i_crtime, ei, raw_inode); + ei->i_fs_version = le32_to_cpu(raw_inode->i_disk_version); + if (EXT3_INODE_SIZE(inode->i_sb) > EXT3_GOOD_OLD_INODE_SIZE) { + if (EXT3_FITS_IN_INODE(raw_inode, ei, i_version_hi)) + ei->i_fs_version |= (__u64)(le32_to_cpu(raw_inode->i_version_hi)) + << 32; + } + if (S_ISREG(inode->i_mode)) { inode->i_op = &ext3_file_inode_operations; inode->i_fop = &ext3_file_operations; @@ -2828,8 +2835,14 @@ static int ext3_do_update_inode(handle_t } else for (block = 0; block < EXT3_N_BLOCKS; block++) raw_inode->i_block[block] = ei->i_data[block]; - if (ei->i_extra_isize) + raw_inode->i_disk_version = cpu_to_le32(ei->i_fs_version); + if (ei->i_extra_isize) { + if (EXT3_FITS_IN_INODE(raw_inode, ei, i_version_hi)) { + raw_inode->i_version_hi = cpu_to_le32(ei->i_fs_version + >> 32); + } raw_inode->i_extra_isize = cpu_to_le16(ei->i_extra_isize); + } BUFFER_TRACE(bh, "call ext3_journal_dirty_metadata"); rc = ext3_journal_dirty_metadata(handle, bh); @@ -3103,10 +3116,32 @@ ext3_reserve_inode_write(handle_t *handl int ext3_mark_inode_dirty(handle_t *handle, struct inode *inode) { struct ext3_iloc iloc; - int err; + int err, ret; + static int expand_message; might_sleep(); err = ext3_reserve_inode_write(handle, inode, &iloc); + if (EXT3_I(inode)->i_extra_isize < + EXT3_SB(inode->i_sb)->s_want_extra_isize && + !(EXT3_I(inode)->i_state & EXT3_STATE_NO_EXPAND)) { + /* We need extra buffer credits since we may write into EA block + * with this same handle */ + if ((ext3_journal_extend(handle, + EXT3_DATA_TRANS_BLOCKS(inode->i_sb))) == 0) { + ret = ext3_expand_extra_isize(inode, + EXT3_SB(inode->i_sb)->s_want_extra_isize, + iloc, handle); + if (ret) { + EXT3_I(inode)->i_state |= EXT3_STATE_NO_EXPAND; + if (!expand_message) { + ext3_warning(inode->i_sb, __FUNCTION__, + "Unable to expand inode %lu. Delete some" + " EAs or run e2fsck.", inode->i_ino); + expand_message = 1; + } + } + } + } if (!err) err = ext3_mark_iloc_dirty(handle, inode, &iloc); return err; Index: linux-2.6.18/include/linux/ext3_fs.h =================================================================== --- linux-2.6.18.orig/include/linux/ext3_fs.h +++ linux-2.6.18/include/linux/ext3_fs.h @@ -224,6 +224,7 @@ struct ext3_group_desc #define EXT3_STATE_JDATA 0x00000001 /* journaled data exists */ #define EXT3_STATE_NEW 0x00000002 /* inode is newly created */ #define EXT3_STATE_XATTR 0x00000004 /* has in-inode xattrs */ +#define EXT3_STATE_NO_EXPAND 0x00000008 /* No space for expansion */ /* Used to pass group descriptor data when online resize is done */ struct ext3_new_group_input { @@ -297,7 +298,7 @@ struct ext3_inode { __le32 i_flags; /* File flags */ union { struct { - __u32 l_i_reserved1; + __u32 l_i_version; } linux1; struct { __u32 h_i_translator; @@ -342,6 +343,7 @@ struct ext3_inode { __le32 i_atime_extra; /* extra Access time (nsec << 2 | epoch) */ __le32 i_crtime; /* File Creation time */ __le32 i_crtime_extra; /* extra File Creation time (nsec << 2 | epoch) */ + __le32 i_version_hi; /* high 32 bits for 64-bit version */ }; #define i_size_high i_dir_acl @@ -404,6 +406,8 @@ do { \ raw_inode->xtime ## _extra); \ } while (0) +#define i_disk_version osd1.linux1.l_i_version + #if defined(__KERNEL__) || defined(__linux__) #define i_reserved1 osd1.linux1.l_i_reserved1 #define i_frag osd2.linux2.l_i_frag Index: linux-2.6.18/include/linux/ext3_fs_i.h =================================================================== --- linux-2.6.18.orig/include/linux/ext3_fs_i.h +++ linux-2.6.18/include/linux/ext3_fs_i.h @@ -21,6 +21,8 @@ #include #include +#define HAVE_DISK_INODE_VERSION + /* data type for block offset of block group */ typedef int ext3_grpblk_t; @@ -162,6 +164,8 @@ struct ext3_inode_info { spinlock_t i_prealloc_lock; void *i_filterdata; + + __u64 i_fs_version; }; #endif /* _LINUX_EXT3_FS_I */ Index: linux-2.6.18/fs/ext3/xattr.c =================================================================== --- linux-2.6.18.orig/fs/ext3/xattr.c +++ linux-2.6.18/fs/ext3/xattr.c @@ -505,6 +505,20 @@ ext3_xattr_release_block(handle_t *handl } } +static inline size_t ext3_xattr_free_space(struct ext3_xattr_entry *last, + size_t *min_offs, void *base, int *total) +{ + for (; !IS_LAST_ENTRY(last); last = EXT3_XATTR_NEXT(last)) { + *total += EXT3_XATTR_LEN(last->e_name_len); + if (!last->e_value_block && last->e_value_size) { + size_t offs = le16_to_cpu(last->e_value_offs); + if (offs < *min_offs) + *min_offs = offs; + } + } + return (*min_offs - ((void *)last - base) - sizeof(__u32)); +} + struct ext3_xattr_info { int name_index; const char *name; @@ -945,13 +959,18 @@ ext3_xattr_set_handle(handle_t *handle, struct ext3_xattr_block_find bs = { .s = { .not_found = -ENODATA, }, }; + unsigned long no_expand; int error; if (!name) return -EINVAL; if (strlen(name) > 255) return -ERANGE; + down_write(&EXT3_I(inode)->xattr_sem); + no_expand = EXT3_I(inode)->i_state & EXT3_STATE_NO_EXPAND; + EXT3_I(inode)->i_state |= EXT3_STATE_NO_EXPAND; + error = ext3_get_inode_loc(inode, &is.iloc); if (error) goto cleanup; @@ -1009,6 +1028,8 @@ ext3_xattr_set_handle(handle_t *handle, ext3_xattr_update_super_block(handle, inode->i_sb); inode->i_ctime = ext3_current_time(inode); error = ext3_mark_iloc_dirty(handle, inode, &is.iloc); + if (!value) + EXT3_I(inode)->i_state &= ~EXT3_STATE_NO_EXPAND; /* * The bh is consumed by ext3_mark_iloc_dirty, even with * error != 0. @@ -1021,6 +1042,8 @@ ext3_xattr_set_handle(handle_t *handle, cleanup: brelse(is.iloc.bh); brelse(bs.bh); + if (no_expand == 0) + EXT3_I(inode)->i_state &= ~EXT3_STATE_NO_EXPAND; up_write(&EXT3_I(inode)->xattr_sem); return error; } @@ -1060,6 +1083,249 @@ retry: return error; } +static void ext3_xattr_shift_entries(struct ext3_xattr_entry *entry, + int value_offs_shift, void *to, + void *from, size_t n, int blocksize) +{ + struct ext3_xattr_entry *last = entry; + int new_offs; + + /* Adjust the value offsets of the entries */ + for (; !IS_LAST_ENTRY(last); last = EXT3_XATTR_NEXT(last)) { + if (!last->e_value_block && last->e_value_size) { + new_offs = le16_to_cpu(last->e_value_offs) + + value_offs_shift; + BUG_ON(new_offs + le32_to_cpu(last->e_value_size) > + blocksize); + last->e_value_offs = cpu_to_le16(new_offs); + } + } + /* Shift the entries by n bytes */ + memmove(to, from, n); +} + +/* Expand an inode by new_extra_isize bytes. + * Returns 0 on success or negative error number on failure. + */ +int ext3_expand_extra_isize(struct inode *inode, int new_extra_isize, + struct ext3_iloc iloc, handle_t *handle) +{ + struct ext3_inode *raw_inode; + struct ext3_xattr_ibody_header *header; + struct ext3_xattr_entry *entry, *last, *first; + struct buffer_head *bh = NULL; + struct ext3_xattr_ibody_find *is = NULL; + struct ext3_xattr_block_find *bs = NULL; + char *buffer = NULL, *b_entry_name = NULL; + size_t min_offs, free; + int total_ino, total_blk; + void *base, *start, *end; + int extra_isize = 0, error = 0, tried_min_extra_isize = 0; + int s_min_extra_isize = EXT3_SB(inode->i_sb)->s_es->s_min_extra_isize; + + down_write(&EXT3_I(inode)->xattr_sem); + +retry: + if (EXT3_I(inode)->i_extra_isize >= new_extra_isize) { + up_write(&EXT3_I(inode)->xattr_sem); + return 0; + } + + raw_inode = ext3_raw_inode(&iloc); + + header = IHDR(inode, raw_inode); + entry = IFIRST(header); + + /* No extended attributes present */ + if (!(EXT3_I(inode)->i_state & EXT3_STATE_XATTR) || + header->h_magic != cpu_to_le32(EXT3_XATTR_MAGIC)) { + memset((void *)raw_inode + EXT3_GOOD_OLD_INODE_SIZE, 0, + new_extra_isize); + EXT3_I(inode)->i_extra_isize = new_extra_isize; + goto cleanup; + } + + /* + * Check if enough free space is available in the inode to shift the + * entries ahead by new_extra_isize. + */ + + base = start = entry; + end = (void *)raw_inode + EXT3_SB(inode->i_sb)->s_inode_size; + min_offs = end - base; + last = entry; + total_ino = sizeof(struct ext3_xattr_ibody_header); + + free = ext3_xattr_free_space(last, &min_offs, base, &total_ino); + if (free >= new_extra_isize) { + entry = IFIRST(header); + ext3_xattr_shift_entries(entry, EXT3_I(inode)->i_extra_isize - + new_extra_isize, (void *)raw_inode + + EXT3_GOOD_OLD_INODE_SIZE + new_extra_isize, + (void *)header, total_ino, + inode->i_sb->s_blocksize); + EXT3_I(inode)->i_extra_isize = new_extra_isize; + error = 0; + goto cleanup; + } + + /* + * Enough free space isn't available in the inode, check if + * EA block can hold new_extra_isize bytes. + */ + if (EXT3_I(inode)->i_file_acl) { + bh = sb_bread(inode->i_sb, EXT3_I(inode)->i_file_acl); + error = -EIO; + if (!bh) + goto cleanup; + if (ext3_xattr_check_block(bh)) { + ext3_error(inode->i_sb, __FUNCTION__, + "inode %lu: bad block "E3FSBLK, inode->i_ino, + EXT3_I(inode)->i_file_acl); + error = -EIO; + goto cleanup; + } + base = BHDR(bh); + first = BFIRST(bh); + end = bh->b_data + bh->b_size; + min_offs = end - base; + free = ext3_xattr_free_space(first, &min_offs, base, + &total_blk); + if (free < new_extra_isize) { + if (!tried_min_extra_isize && s_min_extra_isize) { + tried_min_extra_isize++; + new_extra_isize = s_min_extra_isize; + goto retry; + } + error = -1; + goto cleanup; + } + } else { + free = inode->i_sb->s_blocksize; + } + + while (new_extra_isize > 0) { + size_t offs, size, entry_size; + struct ext3_xattr_entry *small_entry = NULL; + struct ext3_xattr_info i = { + .value = NULL, + .value_len = 0, + }; + unsigned int total_size, shift_bytes, temp = ~0U; + + is = (struct ext3_xattr_ibody_find *) kmalloc(sizeof(struct + ext3_xattr_ibody_find), GFP_KERNEL); + bs = (struct ext3_xattr_block_find *) kmalloc(sizeof(struct + ext3_xattr_block_find), GFP_KERNEL); + memset((void *)is, 0, sizeof(struct ext3_xattr_ibody_find)); + memset((void *)bs, 0, sizeof(struct ext3_xattr_block_find)); + + is->s.not_found = bs->s.not_found = -ENODATA; + is->iloc.bh = NULL; + bs->bh = NULL; + + last = IFIRST(header); + /* Find the entry best suited to be pushed into EA block */ + entry = NULL; + for (; !IS_LAST_ENTRY(last); last = EXT3_XATTR_NEXT(last)) { + total_size = EXT3_XATTR_SIZE(le32_to_cpu(last->e_value_size)) + + EXT3_XATTR_LEN(last->e_name_len); + if (total_size <= free && total_size < temp) { + if (total_size < new_extra_isize) { + small_entry = last; + } else { + entry = last; + temp = total_size; + } + } + } + + if (entry == NULL) { + if (small_entry) { + entry = small_entry; + } else { + if (!tried_min_extra_isize && + s_min_extra_isize) { + tried_min_extra_isize++; + new_extra_isize = s_min_extra_isize; + goto retry; + } + error = -1; + goto cleanup; + } + } + offs = le16_to_cpu(entry->e_value_offs); + size = le32_to_cpu(entry->e_value_size); + entry_size = EXT3_XATTR_LEN(entry->e_name_len); + i.name_index = entry->e_name_index, + buffer = kmalloc(EXT3_XATTR_SIZE(size), GFP_KERNEL); + b_entry_name = kmalloc(entry->e_name_len + 1, GFP_KERNEL); + /* Save the entry name and the entry value */ + memcpy((void *)buffer, (void *)IFIRST(header) + offs, + EXT3_XATTR_SIZE(size)); + memcpy((void *)b_entry_name, (void *)entry->e_name, + entry->e_name_len); + b_entry_name[entry->e_name_len] = '\0'; + i.name = b_entry_name; + + error = ext3_get_inode_loc(inode, &is->iloc); + if (error) + goto cleanup; + + error = ext3_xattr_ibody_find(inode, &i, is); + if (error) + goto cleanup; + + /* Remove the chosen entry from the inode */ + error = ext3_xattr_ibody_set(handle, inode, &i, is); + + entry = IFIRST(header); + if (entry_size + EXT3_XATTR_SIZE(size) >= new_extra_isize) + shift_bytes = new_extra_isize; + else + shift_bytes = entry_size + size; + /* Adjust the offsets and shift the remaining entries ahead */ + ext3_xattr_shift_entries(entry, EXT3_I(inode)->i_extra_isize - + shift_bytes, (void *)raw_inode + + EXT3_GOOD_OLD_INODE_SIZE + extra_isize + shift_bytes, + (void *)header, total_ino - entry_size, + inode->i_sb->s_blocksize); + + extra_isize += shift_bytes; + new_extra_isize -= shift_bytes; + EXT3_I(inode)->i_extra_isize = extra_isize; + + i.name = b_entry_name; + i.value = buffer; + i.value_len = cpu_to_le32(size); + error = ext3_xattr_block_find(inode, &i, bs); + if (error) + goto cleanup; + + /* Add entry which was removed from the inode into the block */ + error = ext3_xattr_block_set(handle, inode, &i, bs); + if (error) + goto cleanup; + } + +cleanup: + if (b_entry_name) + kfree(b_entry_name); + if (buffer) + kfree(buffer); + if (is) { + brelse(is->iloc.bh); + kfree(is); + } + if (bs) + kfree(bs); + brelse(bh); + up_write(&EXT3_I(inode)->xattr_sem); + return error; +} + + + /* * ext3_xattr_delete_inode() * Index: linux-2.6.18/fs/ext3/xattr.h =================================================================== --- linux-2.6.18.orig/fs/ext3/xattr.h +++ linux-2.6.18/fs/ext3/xattr.h @@ -74,6 +74,9 @@ extern int ext3_xattr_set_handle(handle_ extern void ext3_xattr_delete_inode(handle_t *, struct inode *); extern void ext3_xattr_put_super(struct super_block *); +int ext3_expand_extra_isize(struct inode *inode, int new_extra_isize, + struct ext3_iloc iloc, handle_t *handle); + extern int init_ext3_xattr(void); extern void exit_ext3_xattr(void); Index: linux-2.6.18/fs/ext3/ialloc.c =================================================================== --- linux-2.6.18.orig/fs/ext3/ialloc.c +++ linux-2.6.18/fs/ext3/ialloc.c @@ -751,6 +751,7 @@ got: ei->i_dtime = 0; ei->i_block_alloc_info = NULL; ei->i_block_group = group; + ei->i_fs_version = 0; ext3_set_inode_flags(inode); if (IS_DIRSYNC(inode))