btrfs: only copy dir index keys when logging a directory
Currently, when logging a directory, we copy both dir items and dir index items from the fs/subvolume tree to the log tree. Both items have exactly the same data (same struct btrfs_dir_item), the difference lies in the key values, where a dir index key contains the index number of a directory entry while the dir item key does not, as it's used for doing fast lookups of an entry by name, while the former is used for sorting entries when listing a directory. We can exploit that and log only the dir index items, since they contain all the information needed to correctly add, replace and delete directory entries when replaying a log tree. Logging only the dir index items is also backward and forward compatible: an unpatched kernel (without this change) can correctly replay a log tree generated by a patched kernel (with this patch), and a patched kernel can correctly replay a log tree generated by an unpatched kernel. The backward compatibility is ensured because: 1) For inserting a new dentry: a dentry is only inserted when we find a new dir index key - we can only insert if we know the dir index offset, which is encoded in the dir index key's offset; 2) For deleting dentries: during log replay, before adding or replacing dentries, we first replay dentry deletions. Whenever we find a dir item key or a dir index key in the subvolume/fs tree that is not logged in a range for which the log tree is authoritative, we do the unlink of the dentry, which removes both the existing dir item key and the dir index key. Therefore logging just dir index keys is enough to ensure dentry deletions are correctly replayed; 3) For dentry replacements: they work when we log only dir index keys and this is mostly due to a combination of 1) and 2). If we replace a dentry with name "foobar" to point from inode A to inode B, then we know the dir index key for the new dentry is different from the old one, as it has an index number (key offset) larger than the old one. This results in replaying a deletion, through replay_dir_deletes(), that causes the old dentry to be removed, both the dir item key and the dir index key, as mentioned at 2). Then when processing the new dir index key, we add the new dentry, adding both a new dir item key and a new index key pointing to inode B, as stated in 1). The forward compatibility, the ability for a patched kernel to replay a log created by an older, unpatched kernel, comes from the changes required for making sure we are able to replay a log that only contains dir index keys - we simply ignore every dir item key we find. So modify directory logging to log only dir index items, and modify the log replay process to ignore dir item keys, from log trees created by an unpatched kernel, and process only with dir index keys. This reduces the amount of logged metadata by about half, and therefore the time spent logging or fsyncing large directories (less CPU time and less IO). The following test script was used to measure this change: #!/bin/bash DEV=/dev/nvme0n1 MNT=/mnt/nvme0n1 NUM_NEW_FILES=1000000 NUM_FILE_DELETES=10000 mkfs.btrfs -f $DEV mount -o ssd $DEV $MNT mkdir $MNT/testdir for ((i = 1; i <= $NUM_NEW_FILES; i++)); do echo -n > $MNT/testdir/file_$i done start=$(date +%s%N) xfs_io -c "fsync" $MNT/testdir end=$(date +%s%N) dur=$(( (end - start) / 1000000 )) echo "dir fsync took $dur ms after adding $NUM_NEW_FILES files" # sync to force transaction commit and wipeout the log. sync del_inc=$(( $NUM_NEW_FILES / $NUM_FILE_DELETES )) for ((i = 1; i <= $NUM_NEW_FILES; i += $del_inc)); do rm -f $MNT/testdir/file_$i done start=$(date +%s%N) xfs_io -c "fsync" $MNT/testdir end=$(date +%s%N) dur=$(( (end - start) / 1000000 )) echo "dir fsync took $dur ms after deleting $NUM_FILE_DELETES files" echo umount $MNT The tests were run on a physical machine, with a non-debug kernel (Debian's default kernel config), for different values of $NUM_NEW_FILES and $NUM_FILE_DELETES, and the results were the following: ** Before patch, NUM_NEW_FILES = 1 000 000, NUM_DELETE_FILES = 10 000 ** dir fsync took 8412 ms after adding 1000000 files dir fsync took 500 ms after deleting 10000 files ** After patch, NUM_NEW_FILES = 1 000 000, NUM_DELETE_FILES = 10 000 ** dir fsync took 4252 ms after adding 1000000 files (-49.5%) dir fsync took 269 ms after deleting 10000 files (-46.2%) ** Before patch, NUM_NEW_FILES = 100 000, NUM_DELETE_FILES = 1 000 ** dir fsync took 745 ms after adding 100000 files dir fsync took 59 ms after deleting 1000 files ** After patch, NUM_NEW_FILES = 100 000, NUM_DELETE_FILES = 1 000 ** dir fsync took 404 ms after adding 100000 files (-45.8%) dir fsync took 31 ms after deleting 1000 files (-47.5%) ** Before patch, NUM_NEW_FILES = 10 000, NUM_DELETE_FILES = 1 000 ** dir fsync took 67 ms after adding 10000 files dir fsync took 9 ms after deleting 1000 files ** After patch, NUM_NEW_FILES = 10 000, NUM_DELETE_FILES = 1 000 ** dir fsync took 36 ms after adding 10000 files (-46.3%) dir fsync took 5 ms after deleting 1000 files (-44.4%) ** Before patch, NUM_NEW_FILES = 1 000, NUM_DELETE_FILES = 100 ** dir fsync took 9 ms after adding 1000 files dir fsync took 4 ms after deleting 100 files ** After patch, NUM_NEW_FILES = 1 000, NUM_DELETE_FILES = 100 ** dir fsync took 7 ms after adding 1000 files (-22.2%) dir fsync took 3 ms after deleting 100 files (-25.0%) Reviewed-by: Josef Bacik <josef@toxicpanda.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Родитель
17130a65f0
Коммит
339d035424
|
@ -138,19 +138,11 @@ struct btrfs_inode {
|
|||
/* a local copy of root's last_log_commit */
|
||||
int last_log_commit;
|
||||
|
||||
union {
|
||||
/*
|
||||
* Total number of bytes pending delalloc, used by stat to
|
||||
* calculate the real block usage of the file. This is used
|
||||
* only for files.
|
||||
*/
|
||||
u64 delalloc_bytes;
|
||||
/*
|
||||
* The offset of the last dir item key that was logged.
|
||||
* This is used only for directories.
|
||||
*/
|
||||
u64 last_dir_item_offset;
|
||||
};
|
||||
/*
|
||||
* Total number of bytes pending delalloc, used by stat to calculate the
|
||||
* real block usage of the file. This is used only for files.
|
||||
*/
|
||||
u64 delalloc_bytes;
|
||||
|
||||
union {
|
||||
/*
|
||||
|
|
|
@ -1950,6 +1950,34 @@ static noinline int insert_one_name(struct btrfs_trans_handle *trans,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int delete_conflicting_dir_entry(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_inode *dir,
|
||||
struct btrfs_path *path,
|
||||
struct btrfs_dir_item *dst_di,
|
||||
const struct btrfs_key *log_key,
|
||||
u8 log_type,
|
||||
bool exists)
|
||||
{
|
||||
struct btrfs_key found_key;
|
||||
|
||||
btrfs_dir_item_key_to_cpu(path->nodes[0], dst_di, &found_key);
|
||||
/* The existing dentry points to the same inode, don't delete it. */
|
||||
if (found_key.objectid == log_key->objectid &&
|
||||
found_key.type == log_key->type &&
|
||||
found_key.offset == log_key->offset &&
|
||||
btrfs_dir_type(path->nodes[0], dst_di) == log_type)
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* Don't drop the conflicting directory entry if the inode for the new
|
||||
* entry doesn't exist.
|
||||
*/
|
||||
if (!exists)
|
||||
return 0;
|
||||
|
||||
return drop_one_dir_item(trans, path, dir, dst_di);
|
||||
}
|
||||
|
||||
/*
|
||||
* take a single entry in a log directory item and replay it into
|
||||
* the subvolume.
|
||||
|
@ -1975,14 +2003,17 @@ static noinline int replay_one_name(struct btrfs_trans_handle *trans,
|
|||
{
|
||||
char *name;
|
||||
int name_len;
|
||||
struct btrfs_dir_item *dst_di;
|
||||
struct btrfs_key found_key;
|
||||
struct btrfs_dir_item *dir_dst_di;
|
||||
struct btrfs_dir_item *index_dst_di;
|
||||
bool dir_dst_matches = false;
|
||||
bool index_dst_matches = false;
|
||||
struct btrfs_key log_key;
|
||||
struct btrfs_key search_key;
|
||||
struct inode *dir;
|
||||
u8 log_type;
|
||||
bool exists;
|
||||
int ret;
|
||||
bool update_size = (key->type == BTRFS_DIR_INDEX_KEY);
|
||||
bool update_size = true;
|
||||
bool name_added = false;
|
||||
|
||||
dir = read_one_inode(root, key->objectid);
|
||||
|
@ -2008,76 +2039,53 @@ static noinline int replay_one_name(struct btrfs_trans_handle *trans,
|
|||
exists = (ret == 0);
|
||||
ret = 0;
|
||||
|
||||
if (key->type == BTRFS_DIR_ITEM_KEY) {
|
||||
dst_di = btrfs_lookup_dir_item(trans, root, path, key->objectid,
|
||||
name, name_len, 1);
|
||||
} else if (key->type == BTRFS_DIR_INDEX_KEY) {
|
||||
dst_di = btrfs_lookup_dir_index_item(trans, root, path,
|
||||
key->objectid,
|
||||
key->offset, name,
|
||||
name_len, 1);
|
||||
} else {
|
||||
/* Corruption */
|
||||
ret = -EINVAL;
|
||||
dir_dst_di = btrfs_lookup_dir_item(trans, root, path, key->objectid,
|
||||
name, name_len, 1);
|
||||
if (IS_ERR(dir_dst_di)) {
|
||||
ret = PTR_ERR(dir_dst_di);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (IS_ERR(dst_di)) {
|
||||
ret = PTR_ERR(dst_di);
|
||||
goto out;
|
||||
} else if (!dst_di) {
|
||||
/* we need a sequence number to insert, so we only
|
||||
* do inserts for the BTRFS_DIR_INDEX_KEY types
|
||||
*/
|
||||
if (key->type != BTRFS_DIR_INDEX_KEY)
|
||||
} else if (dir_dst_di) {
|
||||
ret = delete_conflicting_dir_entry(trans, BTRFS_I(dir), path,
|
||||
dir_dst_di, &log_key, log_type,
|
||||
exists);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
goto insert;
|
||||
dir_dst_matches = (ret == 1);
|
||||
}
|
||||
|
||||
btrfs_dir_item_key_to_cpu(path->nodes[0], dst_di, &found_key);
|
||||
/* the existing item matches the logged item */
|
||||
if (found_key.objectid == log_key.objectid &&
|
||||
found_key.type == log_key.type &&
|
||||
found_key.offset == log_key.offset &&
|
||||
btrfs_dir_type(path->nodes[0], dst_di) == log_type) {
|
||||
btrfs_release_path(path);
|
||||
|
||||
index_dst_di = btrfs_lookup_dir_index_item(trans, root, path,
|
||||
key->objectid, key->offset,
|
||||
name, name_len, 1);
|
||||
if (IS_ERR(index_dst_di)) {
|
||||
ret = PTR_ERR(index_dst_di);
|
||||
goto out;
|
||||
} else if (index_dst_di) {
|
||||
ret = delete_conflicting_dir_entry(trans, BTRFS_I(dir), path,
|
||||
index_dst_di, &log_key,
|
||||
log_type, exists);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
index_dst_matches = (ret == 1);
|
||||
}
|
||||
|
||||
btrfs_release_path(path);
|
||||
|
||||
if (dir_dst_matches && index_dst_matches) {
|
||||
ret = 0;
|
||||
update_size = false;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* don't drop the conflicting directory entry if the inode
|
||||
* for the new entry doesn't exist
|
||||
*/
|
||||
if (!exists)
|
||||
goto out;
|
||||
|
||||
ret = drop_one_dir_item(trans, path, BTRFS_I(dir), dst_di);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (key->type == BTRFS_DIR_INDEX_KEY)
|
||||
goto insert;
|
||||
out:
|
||||
btrfs_release_path(path);
|
||||
if (!ret && update_size) {
|
||||
btrfs_i_size_write(BTRFS_I(dir), dir->i_size + name_len * 2);
|
||||
ret = btrfs_update_inode(trans, root, BTRFS_I(dir));
|
||||
}
|
||||
kfree(name);
|
||||
iput(dir);
|
||||
if (!ret && name_added)
|
||||
ret = 1;
|
||||
return ret;
|
||||
|
||||
insert:
|
||||
/*
|
||||
* Check if the inode reference exists in the log for the given name,
|
||||
* inode and parent inode
|
||||
*/
|
||||
found_key.objectid = log_key.objectid;
|
||||
found_key.type = BTRFS_INODE_REF_KEY;
|
||||
found_key.offset = key->objectid;
|
||||
ret = backref_in_log(root->log_root, &found_key, 0, name, name_len);
|
||||
search_key.objectid = log_key.objectid;
|
||||
search_key.type = BTRFS_INODE_REF_KEY;
|
||||
search_key.offset = key->objectid;
|
||||
ret = backref_in_log(root->log_root, &search_key, 0, name, name_len);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
} else if (ret) {
|
||||
|
@ -2087,10 +2095,10 @@ insert:
|
|||
goto out;
|
||||
}
|
||||
|
||||
found_key.objectid = log_key.objectid;
|
||||
found_key.type = BTRFS_INODE_EXTREF_KEY;
|
||||
found_key.offset = key->objectid;
|
||||
ret = backref_in_log(root->log_root, &found_key, key->objectid, name,
|
||||
search_key.objectid = log_key.objectid;
|
||||
search_key.type = BTRFS_INODE_EXTREF_KEY;
|
||||
search_key.offset = key->objectid;
|
||||
ret = backref_in_log(root->log_root, &search_key, key->objectid, name,
|
||||
name_len);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
|
@ -2109,87 +2117,76 @@ insert:
|
|||
name_added = true;
|
||||
update_size = false;
|
||||
ret = 0;
|
||||
goto out;
|
||||
|
||||
out:
|
||||
if (!ret && update_size) {
|
||||
btrfs_i_size_write(BTRFS_I(dir), dir->i_size + name_len * 2);
|
||||
ret = btrfs_update_inode(trans, root, BTRFS_I(dir));
|
||||
}
|
||||
kfree(name);
|
||||
iput(dir);
|
||||
if (!ret && name_added)
|
||||
ret = 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* find all the names in a directory item and reconcile them into
|
||||
* the subvolume. Only BTRFS_DIR_ITEM_KEY types will have more than
|
||||
* one name in a directory item, but the same code gets used for
|
||||
* both directory index types
|
||||
*/
|
||||
/* Replay one dir item from a BTRFS_DIR_INDEX_KEY key. */
|
||||
static noinline int replay_one_dir_item(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path,
|
||||
struct extent_buffer *eb, int slot,
|
||||
struct btrfs_key *key)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 item_size = btrfs_item_size_nr(eb, slot);
|
||||
int ret;
|
||||
struct btrfs_dir_item *di;
|
||||
int name_len;
|
||||
unsigned long ptr;
|
||||
unsigned long ptr_end;
|
||||
struct btrfs_path *fixup_path = NULL;
|
||||
|
||||
ptr = btrfs_item_ptr_offset(eb, slot);
|
||||
ptr_end = ptr + item_size;
|
||||
while (ptr < ptr_end) {
|
||||
di = (struct btrfs_dir_item *)ptr;
|
||||
name_len = btrfs_dir_name_len(eb, di);
|
||||
ret = replay_one_name(trans, root, path, eb, di, key);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ptr = (unsigned long)(di + 1);
|
||||
ptr += name_len;
|
||||
/* We only log dir index keys, which only contain a single dir item. */
|
||||
ASSERT(key->type == BTRFS_DIR_INDEX_KEY);
|
||||
|
||||
/*
|
||||
* If this entry refers to a non-directory (directories can not
|
||||
* have a link count > 1) and it was added in the transaction
|
||||
* that was not committed, make sure we fixup the link count of
|
||||
* the inode it the entry points to. Otherwise something like
|
||||
* the following would result in a directory pointing to an
|
||||
* inode with a wrong link that does not account for this dir
|
||||
* entry:
|
||||
*
|
||||
* mkdir testdir
|
||||
* touch testdir/foo
|
||||
* touch testdir/bar
|
||||
* sync
|
||||
*
|
||||
* ln testdir/bar testdir/bar_link
|
||||
* ln testdir/foo testdir/foo_link
|
||||
* xfs_io -c "fsync" testdir/bar
|
||||
*
|
||||
* <power failure>
|
||||
*
|
||||
* mount fs, log replay happens
|
||||
*
|
||||
* File foo would remain with a link count of 1 when it has two
|
||||
* entries pointing to it in the directory testdir. This would
|
||||
* make it impossible to ever delete the parent directory has
|
||||
* it would result in stale dentries that can never be deleted.
|
||||
*/
|
||||
if (ret == 1 && btrfs_dir_type(eb, di) != BTRFS_FT_DIR) {
|
||||
struct btrfs_key di_key;
|
||||
di = btrfs_item_ptr(eb, slot, struct btrfs_dir_item);
|
||||
ret = replay_one_name(trans, root, path, eb, di, key);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!fixup_path) {
|
||||
fixup_path = btrfs_alloc_path();
|
||||
if (!fixup_path) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If this entry refers to a non-directory (directories can not have a
|
||||
* link count > 1) and it was added in the transaction that was not
|
||||
* committed, make sure we fixup the link count of the inode the entry
|
||||
* points to. Otherwise something like the following would result in a
|
||||
* directory pointing to an inode with a wrong link that does not account
|
||||
* for this dir entry:
|
||||
*
|
||||
* mkdir testdir
|
||||
* touch testdir/foo
|
||||
* touch testdir/bar
|
||||
* sync
|
||||
*
|
||||
* ln testdir/bar testdir/bar_link
|
||||
* ln testdir/foo testdir/foo_link
|
||||
* xfs_io -c "fsync" testdir/bar
|
||||
*
|
||||
* <power failure>
|
||||
*
|
||||
* mount fs, log replay happens
|
||||
*
|
||||
* File foo would remain with a link count of 1 when it has two entries
|
||||
* pointing to it in the directory testdir. This would make it impossible
|
||||
* to ever delete the parent directory has it would result in stale
|
||||
* dentries that can never be deleted.
|
||||
*/
|
||||
if (ret == 1 && btrfs_dir_type(eb, di) != BTRFS_FT_DIR) {
|
||||
struct btrfs_path *fixup_path;
|
||||
struct btrfs_key di_key;
|
||||
|
||||
btrfs_dir_item_key_to_cpu(eb, di, &di_key);
|
||||
ret = link_to_fixup_dir(trans, root, fixup_path,
|
||||
di_key.objectid);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
ret = 0;
|
||||
fixup_path = btrfs_alloc_path();
|
||||
if (!fixup_path)
|
||||
return -ENOMEM;
|
||||
|
||||
btrfs_dir_item_key_to_cpu(eb, di, &di_key);
|
||||
ret = link_to_fixup_dir(trans, root, fixup_path, di_key.objectid);
|
||||
btrfs_free_path(fixup_path);
|
||||
}
|
||||
btrfs_free_path(fixup_path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -2743,12 +2740,13 @@ static int replay_one_buffer(struct btrfs_root *log, struct extent_buffer *eb,
|
|||
eb, i, &key);
|
||||
if (ret)
|
||||
break;
|
||||
} else if (key.type == BTRFS_DIR_ITEM_KEY) {
|
||||
ret = replay_one_dir_item(wc->trans, root, path,
|
||||
eb, i, &key);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* We don't log BTRFS_DIR_ITEM_KEY keys anymore, only the
|
||||
* BTRFS_DIR_INDEX_KEY items which we use to derive the
|
||||
* BTRFS_DIR_ITEM_KEY items. If we are replaying a log from an
|
||||
* older kernel with such keys, ignore them.
|
||||
*/
|
||||
}
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
|
@ -3551,20 +3549,10 @@ void btrfs_del_dir_entries_in_log(struct btrfs_trans_handle *trans,
|
|||
goto out_unlock;
|
||||
}
|
||||
|
||||
di = btrfs_lookup_dir_item(trans, log, path, dir_ino,
|
||||
name, name_len, -1);
|
||||
if (IS_ERR(di)) {
|
||||
err = PTR_ERR(di);
|
||||
goto fail;
|
||||
}
|
||||
if (di) {
|
||||
ret = btrfs_delete_one_dir_name(trans, log, path, di);
|
||||
if (ret) {
|
||||
err = ret;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
btrfs_release_path(path);
|
||||
/*
|
||||
* We only log dir index items of a directory, so we don't need to look
|
||||
* for dir item keys.
|
||||
*/
|
||||
di = btrfs_lookup_dir_index_item(trans, log, path, dir_ino,
|
||||
index, name, name_len, -1);
|
||||
if (IS_ERR(di)) {
|
||||
|
@ -3628,7 +3616,7 @@ void btrfs_del_inode_ref_in_log(struct btrfs_trans_handle *trans,
|
|||
static noinline int insert_dir_log_key(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *log,
|
||||
struct btrfs_path *path,
|
||||
int key_type, u64 dirid,
|
||||
u64 dirid,
|
||||
u64 first_offset, u64 last_offset)
|
||||
{
|
||||
int ret;
|
||||
|
@ -3637,10 +3625,7 @@ static noinline int insert_dir_log_key(struct btrfs_trans_handle *trans,
|
|||
|
||||
key.objectid = dirid;
|
||||
key.offset = first_offset;
|
||||
if (key_type == BTRFS_DIR_ITEM_KEY)
|
||||
key.type = BTRFS_DIR_LOG_ITEM_KEY;
|
||||
else
|
||||
key.type = BTRFS_DIR_LOG_INDEX_KEY;
|
||||
key.type = BTRFS_DIR_LOG_INDEX_KEY;
|
||||
ret = btrfs_insert_empty_item(trans, log, path, &key, sizeof(*item));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -3732,7 +3717,6 @@ static int process_dir_items_leaf(struct btrfs_trans_handle *trans,
|
|||
struct btrfs_inode *inode,
|
||||
struct btrfs_path *path,
|
||||
struct btrfs_path *dst_path,
|
||||
int key_type,
|
||||
struct btrfs_log_ctx *ctx)
|
||||
{
|
||||
struct btrfs_root *log = inode->root->log_root;
|
||||
|
@ -3740,24 +3724,18 @@ static int process_dir_items_leaf(struct btrfs_trans_handle *trans,
|
|||
const int nritems = btrfs_header_nritems(src);
|
||||
const u64 ino = btrfs_ino(inode);
|
||||
const bool inode_logged_before = inode_logged(trans, inode);
|
||||
u64 last_logged_key_offset;
|
||||
bool last_found = false;
|
||||
int batch_start = 0;
|
||||
int batch_size = 0;
|
||||
int i;
|
||||
|
||||
if (key_type == BTRFS_DIR_ITEM_KEY)
|
||||
last_logged_key_offset = inode->last_dir_item_offset;
|
||||
else
|
||||
last_logged_key_offset = inode->last_dir_index_offset;
|
||||
|
||||
for (i = path->slots[0]; i < nritems; i++) {
|
||||
struct btrfs_key key;
|
||||
int ret;
|
||||
|
||||
btrfs_item_key_to_cpu(src, &key, i);
|
||||
|
||||
if (key.objectid != ino || key.type != key_type) {
|
||||
if (key.objectid != ino || key.type != BTRFS_DIR_INDEX_KEY) {
|
||||
last_found = true;
|
||||
break;
|
||||
}
|
||||
|
@ -3806,7 +3784,7 @@ static int process_dir_items_leaf(struct btrfs_trans_handle *trans,
|
|||
* we logged is in the log tree, saving time and avoiding adding
|
||||
* contention on the log tree.
|
||||
*/
|
||||
if (key.offset > last_logged_key_offset)
|
||||
if (key.offset > inode->last_dir_index_offset)
|
||||
goto add_to_batch;
|
||||
/*
|
||||
* Check if the key was already logged before. If not we can add
|
||||
|
@ -3865,7 +3843,7 @@ add_to_batch:
|
|||
static noinline int log_dir_items(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_inode *inode,
|
||||
struct btrfs_path *path,
|
||||
struct btrfs_path *dst_path, int key_type,
|
||||
struct btrfs_path *dst_path,
|
||||
struct btrfs_log_ctx *ctx,
|
||||
u64 min_offset, u64 *last_offset_ret)
|
||||
{
|
||||
|
@ -3879,7 +3857,7 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
|
|||
u64 ino = btrfs_ino(inode);
|
||||
|
||||
min_key.objectid = ino;
|
||||
min_key.type = key_type;
|
||||
min_key.type = BTRFS_DIR_INDEX_KEY;
|
||||
min_key.offset = min_offset;
|
||||
|
||||
ret = btrfs_search_forward(root, &min_key, path, trans->transid);
|
||||
|
@ -3888,9 +3866,10 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
|
|||
* we didn't find anything from this transaction, see if there
|
||||
* is anything at all
|
||||
*/
|
||||
if (ret != 0 || min_key.objectid != ino || min_key.type != key_type) {
|
||||
if (ret != 0 || min_key.objectid != ino ||
|
||||
min_key.type != BTRFS_DIR_INDEX_KEY) {
|
||||
min_key.objectid = ino;
|
||||
min_key.type = key_type;
|
||||
min_key.type = BTRFS_DIR_INDEX_KEY;
|
||||
min_key.offset = (u64)-1;
|
||||
btrfs_release_path(path);
|
||||
ret = btrfs_search_slot(NULL, root, &min_key, path, 0, 0);
|
||||
|
@ -3898,7 +3877,7 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
|
|||
btrfs_release_path(path);
|
||||
return ret;
|
||||
}
|
||||
ret = btrfs_previous_item(root, path, ino, key_type);
|
||||
ret = btrfs_previous_item(root, path, ino, BTRFS_DIR_INDEX_KEY);
|
||||
|
||||
/* if ret == 0 there are items for this type,
|
||||
* create a range to tell us the last key of this type.
|
||||
|
@ -3909,18 +3888,18 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
|
|||
struct btrfs_key tmp;
|
||||
btrfs_item_key_to_cpu(path->nodes[0], &tmp,
|
||||
path->slots[0]);
|
||||
if (key_type == tmp.type)
|
||||
if (tmp.type == BTRFS_DIR_INDEX_KEY)
|
||||
first_offset = max(min_offset, tmp.offset) + 1;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* go backward to find any previous key */
|
||||
ret = btrfs_previous_item(root, path, ino, key_type);
|
||||
ret = btrfs_previous_item(root, path, ino, BTRFS_DIR_INDEX_KEY);
|
||||
if (ret == 0) {
|
||||
struct btrfs_key tmp;
|
||||
btrfs_item_key_to_cpu(path->nodes[0], &tmp, path->slots[0]);
|
||||
if (key_type == tmp.type) {
|
||||
if (tmp.type == BTRFS_DIR_INDEX_KEY) {
|
||||
first_offset = tmp.offset;
|
||||
ret = overwrite_item(trans, log, dst_path,
|
||||
path->nodes[0], path->slots[0],
|
||||
|
@ -3951,8 +3930,7 @@ search:
|
|||
* from our directory
|
||||
*/
|
||||
while (1) {
|
||||
ret = process_dir_items_leaf(trans, inode, path, dst_path,
|
||||
key_type, ctx);
|
||||
ret = process_dir_items_leaf(trans, inode, path, dst_path, ctx);
|
||||
if (ret != 0) {
|
||||
if (ret < 0)
|
||||
err = ret;
|
||||
|
@ -3973,7 +3951,7 @@ search:
|
|||
goto done;
|
||||
}
|
||||
btrfs_item_key_to_cpu(path->nodes[0], &min_key, path->slots[0]);
|
||||
if (min_key.objectid != ino || min_key.type != key_type) {
|
||||
if (min_key.objectid != ino || min_key.type != BTRFS_DIR_INDEX_KEY) {
|
||||
last_offset = (u64)-1;
|
||||
goto done;
|
||||
}
|
||||
|
@ -4004,8 +3982,8 @@ done:
|
|||
* insert the log range keys to indicate where the log
|
||||
* is valid
|
||||
*/
|
||||
ret = insert_dir_log_key(trans, log, path, key_type,
|
||||
ino, first_offset, last_offset);
|
||||
ret = insert_dir_log_key(trans, log, path, ino, first_offset,
|
||||
last_offset);
|
||||
if (ret)
|
||||
err = ret;
|
||||
}
|
||||
|
@ -4033,35 +4011,28 @@ static noinline int log_directory_changes(struct btrfs_trans_handle *trans,
|
|||
u64 min_key;
|
||||
u64 max_key;
|
||||
int ret;
|
||||
int key_type = BTRFS_DIR_ITEM_KEY;
|
||||
|
||||
/*
|
||||
* If this is the first time we are being logged in the current
|
||||
* transaction, or we were logged before but the inode was evicted and
|
||||
* reloaded later, in which case its logged_trans is 0, reset the values
|
||||
* of the last logged key offsets. Note that we don't use the helper
|
||||
* reloaded later, in which case its logged_trans is 0, reset the value
|
||||
* of the last logged key offset. Note that we don't use the helper
|
||||
* function inode_logged() here - that is because the function returns
|
||||
* true after an inode eviction, assuming the worst case as it can not
|
||||
* know for sure if the inode was logged before. So we can not skip key
|
||||
* searches in the case the inode was evicted, because it may not have
|
||||
* been logged in this transaction and may have been logged in a past
|
||||
* transaction, so we need to reset the last dir item and index offsets
|
||||
* to (u64)-1.
|
||||
* transaction, so we need to reset the last dir index offset to (u64)-1.
|
||||
*/
|
||||
if (inode->logged_trans != trans->transid) {
|
||||
inode->last_dir_item_offset = (u64)-1;
|
||||
if (inode->logged_trans != trans->transid)
|
||||
inode->last_dir_index_offset = (u64)-1;
|
||||
}
|
||||
again:
|
||||
|
||||
min_key = 0;
|
||||
max_key = 0;
|
||||
if (key_type == BTRFS_DIR_ITEM_KEY)
|
||||
ctx->last_dir_item_offset = inode->last_dir_item_offset;
|
||||
else
|
||||
ctx->last_dir_item_offset = inode->last_dir_index_offset;
|
||||
ctx->last_dir_item_offset = inode->last_dir_index_offset;
|
||||
|
||||
while (1) {
|
||||
ret = log_dir_items(trans, inode, path, dst_path, key_type,
|
||||
ret = log_dir_items(trans, inode, path, dst_path,
|
||||
ctx, min_key, &max_key);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -4070,13 +4041,8 @@ again:
|
|||
min_key = max_key + 1;
|
||||
}
|
||||
|
||||
if (key_type == BTRFS_DIR_ITEM_KEY) {
|
||||
inode->last_dir_item_offset = ctx->last_dir_item_offset;
|
||||
key_type = BTRFS_DIR_INDEX_KEY;
|
||||
goto again;
|
||||
} else {
|
||||
inode->last_dir_index_offset = ctx->last_dir_item_offset;
|
||||
}
|
||||
inode->last_dir_index_offset = ctx->last_dir_item_offset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -5899,18 +5865,12 @@ struct btrfs_dir_list {
|
|||
* link_to_fixup_dir());
|
||||
*
|
||||
* 2) For directories we log with a mode of LOG_INODE_ALL. It's possible that
|
||||
* while logging the inode's items new items with keys BTRFS_DIR_ITEM_KEY and
|
||||
* BTRFS_DIR_INDEX_KEY are added to fs/subvol tree and the logged inode item
|
||||
* while logging the inode's items new index items (key type
|
||||
* BTRFS_DIR_INDEX_KEY) are added to fs/subvol tree and the logged inode item
|
||||
* has a size that doesn't match the sum of the lengths of all the logged
|
||||
* names. This does not result in a problem because if a dir_item key is
|
||||
* logged but its matching dir_index key is not logged, at log replay time we
|
||||
* don't use it to replay the respective name (see replay_one_name()). On the
|
||||
* other hand if only the dir_index key ends up being logged, the respective
|
||||
* name is added to the fs/subvol tree with both the dir_item and dir_index
|
||||
* keys created (see replay_one_name()).
|
||||
* The directory's inode item with a wrong i_size is not a problem as well,
|
||||
* since we don't use it at log replay time to set the i_size in the inode
|
||||
* item of the fs/subvol tree (see overwrite_item()).
|
||||
* names - this is ok, not a problem, because at log replay time we set the
|
||||
* directory's i_size to the correct value (see replay_one_name() and
|
||||
* do_overwrite_item()).
|
||||
*/
|
||||
static int log_new_dir_dentries(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
|
@ -5956,7 +5916,7 @@ static int log_new_dir_dentries(struct btrfs_trans_handle *trans,
|
|||
goto next_dir_inode;
|
||||
|
||||
min_key.objectid = dir_elem->ino;
|
||||
min_key.type = BTRFS_DIR_ITEM_KEY;
|
||||
min_key.type = BTRFS_DIR_INDEX_KEY;
|
||||
min_key.offset = 0;
|
||||
again:
|
||||
btrfs_release_path(path);
|
||||
|
@ -5981,7 +5941,7 @@ process_leaf:
|
|||
|
||||
btrfs_item_key_to_cpu(leaf, &min_key, i);
|
||||
if (min_key.objectid != dir_elem->ino ||
|
||||
min_key.type != BTRFS_DIR_ITEM_KEY)
|
||||
min_key.type != BTRFS_DIR_INDEX_KEY)
|
||||
goto next_dir_inode;
|
||||
|
||||
di = btrfs_item_ptr(leaf, i, struct btrfs_dir_item);
|
||||
|
@ -6795,15 +6755,14 @@ void btrfs_log_new_name(struct btrfs_trans_handle *trans,
|
|||
* was previously logged, make sure the next log attempt on the directory
|
||||
* is not skipped and logs the inode again. This is because the log may
|
||||
* not currently be authoritative for a range including the old
|
||||
* BTRFS_DIR_ITEM_KEY and BTRFS_DIR_INDEX_KEY keys, so we want to make
|
||||
* sure after a log replay we do not end up with both the new and old
|
||||
* dentries around (in case the inode is a directory we would have a
|
||||
* directory with two hard links and 2 inode references for different
|
||||
* parents). The next log attempt of old_dir will happen at
|
||||
* btrfs_log_all_parents(), called through btrfs_log_inode_parent()
|
||||
* below, because we have previously set inode->last_unlink_trans to the
|
||||
* current transaction ID, either here or at btrfs_record_unlink_dir() in
|
||||
* case inode is a directory.
|
||||
* BTRFS_DIR_INDEX_KEY key, so we want to make sure after a log replay we
|
||||
* do not end up with both the new and old dentries around (in case the
|
||||
* inode is a directory we would have a directory with two hard links and
|
||||
* 2 inode references for different parents). The next log attempt of
|
||||
* old_dir will happen at btrfs_log_all_parents(), called through
|
||||
* btrfs_log_inode_parent() below, because we have previously set
|
||||
* inode->last_unlink_trans to the current transaction ID, either here or
|
||||
* at btrfs_record_unlink_dir() in case the inode is a directory.
|
||||
*/
|
||||
if (old_dir)
|
||||
old_dir->logged_trans = 0;
|
||||
|
|
Загрузка…
Ссылка в новой задаче