Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 61251
b: refs/heads/master
c: 6dd4ee7
h: refs/heads/master
i:
  61249: 4de2705
  61247: e5b437f
v: v3
  • Loading branch information
Kalpak Shah authored and Theodore Ts'o committed Jul 18, 2007
1 parent b2601ca commit cc02a7b
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 9 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: ef7f38359ea8b3e9c7f2cae9a4d4935f55ca9e80
refs/heads/master: 6dd4ee7cab7e3a17c571aebd444f4344c8c4946e
63 changes: 62 additions & 1 deletion trunk/fs/ext4/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -3105,6 +3105,39 @@ ext4_reserve_inode_write(handle_t *handle, struct inode *inode,
return err;
}

/*
* Expand an inode by new_extra_isize bytes.
* Returns 0 on success or negative error number on failure.
*/
int ext4_expand_extra_isize(struct inode *inode, unsigned int new_extra_isize,
struct ext4_iloc iloc, handle_t *handle)
{
struct ext4_inode *raw_inode;
struct ext4_xattr_ibody_header *header;
struct ext4_xattr_entry *entry;

if (EXT4_I(inode)->i_extra_isize >= new_extra_isize)
return 0;

raw_inode = ext4_raw_inode(&iloc);

header = IHDR(inode, raw_inode);
entry = IFIRST(header);

/* No extended attributes present */
if (!(EXT4_I(inode)->i_state & EXT4_STATE_XATTR) ||
header->h_magic != cpu_to_le32(EXT4_XATTR_MAGIC)) {
memset((void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE, 0,
new_extra_isize);
EXT4_I(inode)->i_extra_isize = new_extra_isize;
return 0;
}

/* try to expand with EAs present */
return ext4_expand_extra_isize_ea(inode, new_extra_isize,
raw_inode, handle);
}

/*
* What we do here is to mark the in-core inode as clean with respect to inode
* dirtiness (it may still be data-dirty).
Expand All @@ -3129,10 +3162,38 @@ ext4_reserve_inode_write(handle_t *handle, struct inode *inode,
int ext4_mark_inode_dirty(handle_t *handle, struct inode *inode)
{
struct ext4_iloc iloc;
int err;
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
static unsigned int mnt_count;
int err, ret;

might_sleep();
err = ext4_reserve_inode_write(handle, inode, &iloc);
if (EXT4_I(inode)->i_extra_isize < sbi->s_want_extra_isize &&
!(EXT4_I(inode)->i_state & EXT4_STATE_NO_EXPAND)) {
/*
* We need extra buffer credits since we may write into EA block
* with this same handle. If journal_extend fails, then it will
* only result in a minor loss of functionality for that inode.
* If this is felt to be critical, then e2fsck should be run to
* force a large enough s_min_extra_isize.
*/
if ((jbd2_journal_extend(handle,
EXT4_DATA_TRANS_BLOCKS(inode->i_sb))) == 0) {
ret = ext4_expand_extra_isize(inode,
sbi->s_want_extra_isize,
iloc, handle);
if (ret) {
EXT4_I(inode)->i_state |= EXT4_STATE_NO_EXPAND;
if (mnt_count != sbi->s_es->s_mnt_count) {
ext4_warning(inode->i_sb, __FUNCTION__,
"Unable to expand inode %lu. Delete"
" some EAs or run e2fsck.",
inode->i_ino);
mnt_count = sbi->s_es->s_mnt_count;
}
}
}
}
if (!err)
err = ext4_mark_iloc_dirty(handle, inode, &iloc);
return err;
Expand Down
274 changes: 267 additions & 7 deletions trunk/fs/ext4/xattr.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,6 @@
#define BFIRST(bh) ENTRY(BHDR(bh)+1)
#define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0)

#define IHDR(inode, raw_inode) \
((struct ext4_xattr_ibody_header *) \
((void *)raw_inode + \
EXT4_GOOD_OLD_INODE_SIZE + \
EXT4_I(inode)->i_extra_isize))
#define IFIRST(hdr) ((struct ext4_xattr_entry *)((hdr)+1))

#ifdef EXT4_XATTR_DEBUG
# define ea_idebug(inode, f...) do { \
printk(KERN_DEBUG "inode %s:%lu: ", \
Expand Down Expand Up @@ -508,6 +501,24 @@ ext4_xattr_release_block(handle_t *handle, struct inode *inode,
return;
}

/*
* Find the available free space for EAs. This also returns the total number of
* bytes used by EA entries.
*/
static size_t ext4_xattr_free_space(struct ext4_xattr_entry *last,
size_t *min_offs, void *base, int *total)
{
for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
*total += EXT4_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 ext4_xattr_info {
int name_index;
const char *name;
Expand Down Expand Up @@ -1014,6 +1025,8 @@ ext4_xattr_set_handle(handle_t *handle, struct inode *inode, int name_index,
if (!error) {
ext4_xattr_update_super_block(handle, inode->i_sb);
inode->i_ctime = ext4_current_time(inode);
if (!value)
EXT4_I(inode)->i_state &= ~EXT4_STATE_NO_EXPAND;
error = ext4_mark_iloc_dirty(handle, inode, &is.iloc);
/*
* The bh is consumed by ext4_mark_iloc_dirty, even with
Expand Down Expand Up @@ -1066,6 +1079,253 @@ ext4_xattr_set(struct inode *inode, int name_index, const char *name,
return error;
}

/*
* Shift the EA entries in the inode to create space for the increased
* i_extra_isize.
*/
static void ext4_xattr_shift_entries(struct ext4_xattr_entry *entry,
int value_offs_shift, void *to,
void *from, size_t n, int blocksize)
{
struct ext4_xattr_entry *last = entry;
int new_offs;

/* Adjust the value offsets of the entries */
for (; !IS_LAST_ENTRY(last); last = EXT4_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 when EAs are present.
* Returns 0 on success or negative error number on failure.
*/
int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
struct ext4_inode *raw_inode, handle_t *handle)
{
struct ext4_xattr_ibody_header *header;
struct ext4_xattr_entry *entry, *last, *first;
struct buffer_head *bh = NULL;
struct ext4_xattr_ibody_find *is = NULL;
struct ext4_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 = EXT4_SB(inode->i_sb)->s_es->s_min_extra_isize;

down_write(&EXT4_I(inode)->xattr_sem);
retry:
if (EXT4_I(inode)->i_extra_isize >= new_extra_isize) {
up_write(&EXT4_I(inode)->xattr_sem);
return 0;
}

header = IHDR(inode, raw_inode);
entry = IFIRST(header);

/*
* 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 + EXT4_SB(inode->i_sb)->s_inode_size;
min_offs = end - base;
last = entry;
total_ino = sizeof(struct ext4_xattr_ibody_header);

free = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
if (free >= new_extra_isize) {
entry = IFIRST(header);
ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize
- new_extra_isize, (void *)raw_inode +
EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
(void *)header, total_ino,
inode->i_sb->s_blocksize);
EXT4_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 (EXT4_I(inode)->i_file_acl) {
bh = sb_bread(inode->i_sb, EXT4_I(inode)->i_file_acl);
error = -EIO;
if (!bh)
goto cleanup;
if (ext4_xattr_check_block(bh)) {
ext4_error(inode->i_sb, __FUNCTION__,
"inode %lu: bad block %llu", inode->i_ino,
EXT4_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 = ext4_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;
brelse(bh);
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 ext4_xattr_entry *small_entry = NULL;
struct ext4_xattr_info i = {
.value = NULL,
.value_len = 0,
};
unsigned int total_size; /* EA entry size + value size */
unsigned int shift_bytes; /* No. of bytes to shift EAs by? */
unsigned int min_total_size = ~0U;

is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS);
bs = kzalloc(sizeof(struct ext4_xattr_block_find), GFP_NOFS);
if (!is || !bs) {
error = -ENOMEM;
goto cleanup;
}

is->s.not_found = -ENODATA;
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 = EXT4_XATTR_NEXT(last)) {
total_size =
EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
EXT4_XATTR_LEN(last->e_name_len);
if (total_size <= free && total_size < min_total_size) {
if (total_size < new_extra_isize) {
small_entry = last;
} else {
entry = last;
min_total_size = 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 = EXT4_XATTR_LEN(entry->e_name_len);
i.name_index = entry->e_name_index,
buffer = kmalloc(EXT4_XATTR_SIZE(size), GFP_NOFS);
b_entry_name = kmalloc(entry->e_name_len + 1, GFP_NOFS);
if (!buffer || !b_entry_name) {
error = -ENOMEM;
goto cleanup;
}
/* Save the entry name and the entry value */
memcpy(buffer, (void *)IFIRST(header) + offs,
EXT4_XATTR_SIZE(size));
memcpy(b_entry_name, entry->e_name, entry->e_name_len);
b_entry_name[entry->e_name_len] = '\0';
i.name = b_entry_name;

error = ext4_get_inode_loc(inode, &is->iloc);
if (error)
goto cleanup;

error = ext4_xattr_ibody_find(inode, &i, is);
if (error)
goto cleanup;

/* Remove the chosen entry from the inode */
error = ext4_xattr_ibody_set(handle, inode, &i, is);

entry = IFIRST(header);
if (entry_size + EXT4_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 */
ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize -
shift_bytes, (void *)raw_inode +
EXT4_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;
EXT4_I(inode)->i_extra_isize = extra_isize;

i.name = b_entry_name;
i.value = buffer;
i.value_len = cpu_to_le32(size);
error = ext4_xattr_block_find(inode, &i, bs);
if (error)
goto cleanup;

/* Add entry which was removed from the inode into the block */
error = ext4_xattr_block_set(handle, inode, &i, bs);
if (error)
goto cleanup;
kfree(b_entry_name);
kfree(buffer);
brelse(is->iloc.bh);
kfree(is);
kfree(bs);
}
brelse(bh);
up_write(&EXT4_I(inode)->xattr_sem);
return 0;

cleanup:
kfree(b_entry_name);
kfree(buffer);
if (is)
brelse(is->iloc.bh);
kfree(is);
kfree(bs);
brelse(bh);
up_write(&EXT4_I(inode)->xattr_sem);
return error;
}



/*
* ext4_xattr_delete_inode()
*
Expand Down
Loading

0 comments on commit cc02a7b

Please sign in to comment.