2018-04-03 17:23:33 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2011-06-13 17:52:59 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2011 STRATO. All rights reserved.
|
|
|
|
*/
|
|
|
|
|
2017-05-31 17:32:09 +00:00
|
|
|
#include <linux/mm.h>
|
btrfs: fix check_shared for fiemap ioctl
Only in the case of different root_id or different object_id, check_shared
identified extent as the shared. However, If a extent was referred by
different offset of same file, it should also be identified as shared.
In addition, check_shared's loop scale is at least n^3, so if a extent
has too many references, even causes soft hang up.
First, add all delayed_ref to the ref_tree and calculate the unqiue_refs,
if the unique_refs is greater than one, return BACKREF_FOUND_SHARED.
Then individually add the on-disk reference(inline/keyed) to the ref_tree
and calculate the unique_refs of the ref_tree to check if the unique_refs
is greater than one.Because once there are two references to return
SHARED, so the time complexity is close to the constant.
Reported-by: Tsutomu Itoh <t-itoh@jp.fujitsu.com>
Signed-off-by: Lu Fengqi <lufq.fnst@cn.fujitsu.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2016-06-13 01:36:46 +00:00
|
|
|
#include <linux/rbtree.h>
|
2017-07-12 22:20:08 +00:00
|
|
|
#include <trace/events/btrfs.h>
|
2011-06-13 17:52:59 +00:00
|
|
|
#include "ctree.h"
|
|
|
|
#include "disk-io.h"
|
|
|
|
#include "backref.h"
|
2011-11-23 17:55:04 +00:00
|
|
|
#include "ulist.h"
|
|
|
|
#include "transaction.h"
|
|
|
|
#include "delayed-ref.h"
|
2012-04-13 10:28:08 +00:00
|
|
|
#include "locking.h"
|
2020-03-23 08:08:34 +00:00
|
|
|
#include "misc.h"
|
2021-03-11 14:31:07 +00:00
|
|
|
#include "tree-mod-log.h"
|
2022-10-19 14:50:47 +00:00
|
|
|
#include "fs.h"
|
2022-10-19 14:51:00 +00:00
|
|
|
#include "accessors.h"
|
2022-10-24 18:46:57 +00:00
|
|
|
#include "extent-tree.h"
|
2022-10-26 19:08:34 +00:00
|
|
|
#include "relocation.h"
|
2011-06-13 17:52:59 +00:00
|
|
|
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
/* Just arbitrary numbers so we can be sure one of these happened. */
|
|
|
|
#define BACKREF_FOUND_SHARED 6
|
|
|
|
#define BACKREF_FOUND_NOT_SHARED 7
|
2014-09-10 20:20:45 +00:00
|
|
|
|
2012-05-17 14:43:03 +00:00
|
|
|
struct extent_inode_elem {
|
|
|
|
u64 inum;
|
|
|
|
u64 offset;
|
2022-11-01 16:15:45 +00:00
|
|
|
u64 num_bytes;
|
2012-05-17 14:43:03 +00:00
|
|
|
struct extent_inode_elem *next;
|
|
|
|
};
|
|
|
|
|
2017-06-29 03:56:55 +00:00
|
|
|
static int check_extent_in_eb(const struct btrfs_key *key,
|
|
|
|
const struct extent_buffer *eb,
|
|
|
|
const struct btrfs_file_extent_item *fi,
|
|
|
|
u64 extent_item_pos,
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
struct extent_inode_elem **eie)
|
2012-05-17 14:43:03 +00:00
|
|
|
{
|
2022-11-01 16:15:45 +00:00
|
|
|
const u64 data_len = btrfs_file_extent_num_bytes(eb, fi);
|
2013-07-05 17:58:19 +00:00
|
|
|
u64 offset = 0;
|
2012-05-17 14:43:03 +00:00
|
|
|
struct extent_inode_elem *e;
|
|
|
|
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
if (!btrfs_file_extent_compression(eb, fi) &&
|
2013-07-05 17:58:19 +00:00
|
|
|
!btrfs_file_extent_encryption(eb, fi) &&
|
|
|
|
!btrfs_file_extent_other_encoding(eb, fi)) {
|
|
|
|
u64 data_offset;
|
2012-05-17 14:43:03 +00:00
|
|
|
|
2013-07-05 17:58:19 +00:00
|
|
|
data_offset = btrfs_file_extent_offset(eb, fi);
|
|
|
|
|
|
|
|
if (extent_item_pos < data_offset ||
|
|
|
|
extent_item_pos >= data_offset + data_len)
|
|
|
|
return 1;
|
|
|
|
offset = extent_item_pos - data_offset;
|
|
|
|
}
|
2012-05-17 14:43:03 +00:00
|
|
|
|
|
|
|
e = kmalloc(sizeof(*e), GFP_NOFS);
|
|
|
|
if (!e)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
e->next = *eie;
|
|
|
|
e->inum = key->objectid;
|
2013-07-05 17:58:19 +00:00
|
|
|
e->offset = key->offset + offset;
|
2022-11-01 16:15:45 +00:00
|
|
|
e->num_bytes = data_len;
|
2012-05-17 14:43:03 +00:00
|
|
|
*eie = e;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-01-28 11:13:38 +00:00
|
|
|
static void free_inode_elem_list(struct extent_inode_elem *eie)
|
|
|
|
{
|
|
|
|
struct extent_inode_elem *eie_next;
|
|
|
|
|
|
|
|
for (; eie; eie = eie_next) {
|
|
|
|
eie_next = eie->next;
|
|
|
|
kfree(eie);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-29 03:56:55 +00:00
|
|
|
static int find_extent_in_eb(const struct extent_buffer *eb,
|
|
|
|
u64 wanted_disk_byte, u64 extent_item_pos,
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
struct extent_inode_elem **eie)
|
2012-05-17 14:43:03 +00:00
|
|
|
{
|
|
|
|
u64 disk_byte;
|
|
|
|
struct btrfs_key key;
|
|
|
|
struct btrfs_file_extent_item *fi;
|
|
|
|
int slot;
|
|
|
|
int nritems;
|
|
|
|
int extent_type;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* from the shared data ref, we only have the leaf but we need
|
|
|
|
* the key. thus, we must look into all items and see that we
|
|
|
|
* find one (some) with a reference to our extent item.
|
|
|
|
*/
|
|
|
|
nritems = btrfs_header_nritems(eb);
|
|
|
|
for (slot = 0; slot < nritems; ++slot) {
|
|
|
|
btrfs_item_key_to_cpu(eb, &key, slot);
|
|
|
|
if (key.type != BTRFS_EXTENT_DATA_KEY)
|
|
|
|
continue;
|
|
|
|
fi = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item);
|
|
|
|
extent_type = btrfs_file_extent_type(eb, fi);
|
|
|
|
if (extent_type == BTRFS_FILE_EXTENT_INLINE)
|
|
|
|
continue;
|
|
|
|
/* don't skip BTRFS_FILE_EXTENT_PREALLOC, we can handle that */
|
|
|
|
disk_byte = btrfs_file_extent_disk_bytenr(eb, fi);
|
|
|
|
if (disk_byte != wanted_disk_byte)
|
|
|
|
continue;
|
|
|
|
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
ret = check_extent_in_eb(&key, eb, fi, extent_item_pos, eie);
|
2012-05-17 14:43:03 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-07-12 22:20:06 +00:00
|
|
|
struct preftree {
|
2018-08-22 19:51:53 +00:00
|
|
|
struct rb_root_cached root;
|
2017-07-12 22:20:07 +00:00
|
|
|
unsigned int count;
|
2017-07-12 22:20:06 +00:00
|
|
|
};
|
|
|
|
|
2018-08-22 19:51:53 +00:00
|
|
|
#define PREFTREE_INIT { .root = RB_ROOT_CACHED, .count = 0 }
|
2017-07-12 22:20:06 +00:00
|
|
|
|
|
|
|
struct preftrees {
|
|
|
|
struct preftree direct; /* BTRFS_SHARED_[DATA|BLOCK]_REF_KEY */
|
|
|
|
struct preftree indirect; /* BTRFS_[TREE_BLOCK|EXTENT_DATA]_REF_KEY */
|
|
|
|
struct preftree indirect_missing_keys;
|
|
|
|
};
|
|
|
|
|
2017-07-12 22:20:10 +00:00
|
|
|
/*
|
|
|
|
* Checks for a shared extent during backref search.
|
|
|
|
*
|
|
|
|
* The share_count tracks prelim_refs (direct and indirect) having a
|
|
|
|
* ref->count >0:
|
|
|
|
* - incremented when a ref->count transitions to >0
|
|
|
|
* - decremented when a ref->count transitions to <1
|
|
|
|
*/
|
|
|
|
struct share_check {
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
struct btrfs_backref_share_check_ctx *ctx;
|
|
|
|
struct btrfs_root *root;
|
2017-07-12 22:20:10 +00:00
|
|
|
u64 inum;
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
u64 data_bytenr;
|
btrfs: avoid unnecessary resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
However when the generation of the data extent is more recent than the
last generation used to snapshot the root, we don't need to determine
the path, since the data extent can not be shared through snapshots.
For this case we currently still determine the leaf of that path (at
find_parent_nodes(), but then stop determining the other nodes in the
path (at btrfs_is_data_extent_shared()) as it's pointless.
So do the check of the data extent's generation earlier, at
find_parent_nodes(), before trying to resolve the indirect reference to
determine the leaf in the path. This saves us from doing one expensive
b+tree search in the fs tree of our target inode, as well as other minor
work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-fiemap.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1285 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 742 milliseconds (metadata cached)
After applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 689 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 393 milliseconds (metadata cached)
That's a -46.4% total reduction for the metadata not cached case, and
a -47.0% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:09 +00:00
|
|
|
u64 data_extent_gen;
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
/*
|
|
|
|
* Counts number of inodes that refer to an extent (different inodes in
|
|
|
|
* the same root or different roots) that we could find. The sharedness
|
|
|
|
* check typically stops once this counter gets greater than 1, so it
|
|
|
|
* may not reflect the total number of inodes.
|
|
|
|
*/
|
2017-07-12 22:20:10 +00:00
|
|
|
int share_count;
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
/*
|
|
|
|
* The number of times we found our inode refers to the data extent we
|
|
|
|
* are determining the sharedness. In other words, how many file extent
|
|
|
|
* items we could find for our inode that point to our target data
|
|
|
|
* extent. The value we get here after finishing the extent sharedness
|
|
|
|
* check may be smaller than reality, but if it ends up being greater
|
|
|
|
* than 1, then we know for sure the inode has multiple file extent
|
|
|
|
* items that point to our inode, and we can safely assume it's useful
|
|
|
|
* to cache the sharedness check result.
|
|
|
|
*/
|
|
|
|
int self_ref_count;
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
bool have_delayed_delete_refs;
|
2017-07-12 22:20:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static inline int extent_is_shared(struct share_check *sc)
|
|
|
|
{
|
|
|
|
return (sc && sc->share_count > 1) ? BACKREF_FOUND_SHARED : 0;
|
|
|
|
}
|
|
|
|
|
2013-08-09 05:25:36 +00:00
|
|
|
static struct kmem_cache *btrfs_prelim_ref_cache;
|
|
|
|
|
|
|
|
int __init btrfs_prelim_ref_init(void)
|
|
|
|
{
|
|
|
|
btrfs_prelim_ref_cache = kmem_cache_create("btrfs_prelim_ref",
|
2017-06-29 03:56:57 +00:00
|
|
|
sizeof(struct prelim_ref),
|
2013-08-09 05:25:36 +00:00
|
|
|
0,
|
2016-06-23 18:17:08 +00:00
|
|
|
SLAB_MEM_SPREAD,
|
2013-08-09 05:25:36 +00:00
|
|
|
NULL);
|
|
|
|
if (!btrfs_prelim_ref_cache)
|
|
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-02-19 16:24:18 +00:00
|
|
|
void __cold btrfs_prelim_ref_exit(void)
|
2013-08-09 05:25:36 +00:00
|
|
|
{
|
2016-01-29 13:36:35 +00:00
|
|
|
kmem_cache_destroy(btrfs_prelim_ref_cache);
|
2013-08-09 05:25:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 22:20:06 +00:00
|
|
|
static void free_pref(struct prelim_ref *ref)
|
|
|
|
{
|
|
|
|
kmem_cache_free(btrfs_prelim_ref_cache, ref);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return 0 when both refs are for the same block (and can be merged).
|
|
|
|
* A -1 return indicates ref1 is a 'lower' block than ref2, while 1
|
|
|
|
* indicates a 'higher' block.
|
|
|
|
*/
|
|
|
|
static int prelim_ref_compare(struct prelim_ref *ref1,
|
|
|
|
struct prelim_ref *ref2)
|
|
|
|
{
|
|
|
|
if (ref1->level < ref2->level)
|
|
|
|
return -1;
|
|
|
|
if (ref1->level > ref2->level)
|
|
|
|
return 1;
|
|
|
|
if (ref1->root_id < ref2->root_id)
|
|
|
|
return -1;
|
|
|
|
if (ref1->root_id > ref2->root_id)
|
|
|
|
return 1;
|
|
|
|
if (ref1->key_for_search.type < ref2->key_for_search.type)
|
|
|
|
return -1;
|
|
|
|
if (ref1->key_for_search.type > ref2->key_for_search.type)
|
|
|
|
return 1;
|
|
|
|
if (ref1->key_for_search.objectid < ref2->key_for_search.objectid)
|
|
|
|
return -1;
|
|
|
|
if (ref1->key_for_search.objectid > ref2->key_for_search.objectid)
|
|
|
|
return 1;
|
|
|
|
if (ref1->key_for_search.offset < ref2->key_for_search.offset)
|
|
|
|
return -1;
|
|
|
|
if (ref1->key_for_search.offset > ref2->key_for_search.offset)
|
|
|
|
return 1;
|
|
|
|
if (ref1->parent < ref2->parent)
|
|
|
|
return -1;
|
|
|
|
if (ref1->parent > ref2->parent)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-11-30 12:14:47 +00:00
|
|
|
static void update_share_count(struct share_check *sc, int oldcount,
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
int newcount, struct prelim_ref *newref)
|
2017-07-12 22:20:10 +00:00
|
|
|
{
|
|
|
|
if ((!sc) || (oldcount == 0 && newcount < 1))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (oldcount > 0 && newcount < 1)
|
|
|
|
sc->share_count--;
|
|
|
|
else if (oldcount < 1 && newcount > 0)
|
|
|
|
sc->share_count++;
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
if (newref->root_id == sc->root->root_key.objectid &&
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
newref->wanted_disk_byte == sc->data_bytenr &&
|
|
|
|
newref->key_for_search.objectid == sc->inum)
|
|
|
|
sc->self_ref_count += newref->count;
|
2017-07-12 22:20:10 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 22:20:06 +00:00
|
|
|
/*
|
|
|
|
* Add @newref to the @root rbtree, merging identical refs.
|
|
|
|
*
|
2017-07-12 22:20:10 +00:00
|
|
|
* Callers should assume that newref has been freed after calling.
|
2017-07-12 22:20:06 +00:00
|
|
|
*/
|
2017-07-12 22:20:08 +00:00
|
|
|
static void prelim_ref_insert(const struct btrfs_fs_info *fs_info,
|
|
|
|
struct preftree *preftree,
|
2017-07-12 22:20:10 +00:00
|
|
|
struct prelim_ref *newref,
|
|
|
|
struct share_check *sc)
|
2017-07-12 22:20:06 +00:00
|
|
|
{
|
2018-08-22 19:51:53 +00:00
|
|
|
struct rb_root_cached *root;
|
2017-07-12 22:20:06 +00:00
|
|
|
struct rb_node **p;
|
|
|
|
struct rb_node *parent = NULL;
|
|
|
|
struct prelim_ref *ref;
|
|
|
|
int result;
|
2018-08-22 19:51:53 +00:00
|
|
|
bool leftmost = true;
|
2017-07-12 22:20:06 +00:00
|
|
|
|
|
|
|
root = &preftree->root;
|
2018-08-22 19:51:53 +00:00
|
|
|
p = &root->rb_root.rb_node;
|
2017-07-12 22:20:06 +00:00
|
|
|
|
|
|
|
while (*p) {
|
|
|
|
parent = *p;
|
|
|
|
ref = rb_entry(parent, struct prelim_ref, rbnode);
|
|
|
|
result = prelim_ref_compare(ref, newref);
|
|
|
|
if (result < 0) {
|
|
|
|
p = &(*p)->rb_left;
|
|
|
|
} else if (result > 0) {
|
|
|
|
p = &(*p)->rb_right;
|
2018-08-22 19:51:53 +00:00
|
|
|
leftmost = false;
|
2017-07-12 22:20:06 +00:00
|
|
|
} else {
|
|
|
|
/* Identical refs, merge them and free @newref */
|
|
|
|
struct extent_inode_elem *eie = ref->inode_list;
|
|
|
|
|
|
|
|
while (eie && eie->next)
|
|
|
|
eie = eie->next;
|
|
|
|
|
|
|
|
if (!eie)
|
|
|
|
ref->inode_list = newref->inode_list;
|
|
|
|
else
|
|
|
|
eie->next = newref->inode_list;
|
2017-07-12 22:20:08 +00:00
|
|
|
trace_btrfs_prelim_ref_merge(fs_info, ref, newref,
|
|
|
|
preftree->count);
|
2017-07-12 22:20:10 +00:00
|
|
|
/*
|
|
|
|
* A delayed ref can have newref->count < 0.
|
|
|
|
* The ref->count is updated to follow any
|
|
|
|
* BTRFS_[ADD|DROP]_DELAYED_REF actions.
|
|
|
|
*/
|
|
|
|
update_share_count(sc, ref->count,
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
ref->count + newref->count, newref);
|
2017-07-12 22:20:06 +00:00
|
|
|
ref->count += newref->count;
|
|
|
|
free_pref(newref);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
update_share_count(sc, 0, newref->count, newref);
|
2017-07-12 22:20:07 +00:00
|
|
|
preftree->count++;
|
2017-07-12 22:20:08 +00:00
|
|
|
trace_btrfs_prelim_ref_insert(fs_info, newref, NULL, preftree->count);
|
2017-07-12 22:20:06 +00:00
|
|
|
rb_link_node(&newref->rbnode, parent, p);
|
2018-08-22 19:51:53 +00:00
|
|
|
rb_insert_color_cached(&newref->rbnode, root, leftmost);
|
2017-07-12 22:20:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Release the entire tree. We don't care about internal consistency so
|
|
|
|
* just free everything and then reset the tree root.
|
|
|
|
*/
|
|
|
|
static void prelim_release(struct preftree *preftree)
|
|
|
|
{
|
|
|
|
struct prelim_ref *ref, *next_ref;
|
|
|
|
|
2018-08-22 19:51:53 +00:00
|
|
|
rbtree_postorder_for_each_entry_safe(ref, next_ref,
|
2022-11-01 16:15:38 +00:00
|
|
|
&preftree->root.rb_root, rbnode) {
|
|
|
|
free_inode_elem_list(ref->inode_list);
|
2017-07-12 22:20:06 +00:00
|
|
|
free_pref(ref);
|
2022-11-01 16:15:38 +00:00
|
|
|
}
|
2017-07-12 22:20:06 +00:00
|
|
|
|
2018-08-22 19:51:53 +00:00
|
|
|
preftree->root = RB_ROOT_CACHED;
|
2017-07-12 22:20:07 +00:00
|
|
|
preftree->count = 0;
|
2017-07-12 22:20:06 +00:00
|
|
|
}
|
|
|
|
|
2012-05-15 15:55:51 +00:00
|
|
|
/*
|
|
|
|
* the rules for all callers of this function are:
|
|
|
|
* - obtaining the parent is the goal
|
|
|
|
* - if you add a key, you must know that it is a correct key
|
|
|
|
* - if you cannot add the parent or a correct key, then we will look into the
|
|
|
|
* block later to set a correct key
|
|
|
|
*
|
|
|
|
* delayed refs
|
|
|
|
* ============
|
|
|
|
* backref type | shared | indirect | shared | indirect
|
|
|
|
* information | tree | tree | data | data
|
|
|
|
* --------------------+--------+----------+--------+----------
|
|
|
|
* parent logical | y | - | - | -
|
|
|
|
* key to resolve | - | y | y | y
|
|
|
|
* tree block logical | - | - | - | -
|
|
|
|
* root for resolving | y | y | y | y
|
|
|
|
*
|
|
|
|
* - column 1: we've the parent -> done
|
|
|
|
* - column 2, 3, 4: we use the key to find the parent
|
|
|
|
*
|
|
|
|
* on disk refs (inline or keyed)
|
|
|
|
* ==============================
|
|
|
|
* backref type | shared | indirect | shared | indirect
|
|
|
|
* information | tree | tree | data | data
|
|
|
|
* --------------------+--------+----------+--------+----------
|
|
|
|
* parent logical | y | - | y | -
|
|
|
|
* key to resolve | - | - | - | y
|
|
|
|
* tree block logical | y | y | y | y
|
|
|
|
* root for resolving | - | y | y | y
|
|
|
|
*
|
|
|
|
* - column 1, 3: we've the parent -> done
|
|
|
|
* - column 2: we take the first key from the block to find the parent
|
2017-06-29 03:56:57 +00:00
|
|
|
* (see add_missing_keys)
|
2012-05-15 15:55:51 +00:00
|
|
|
* - column 4: we use the key to find the parent
|
|
|
|
*
|
|
|
|
* additional information that's available but not required to find the parent
|
|
|
|
* block might help in merging entries to gain some speed.
|
|
|
|
*/
|
2017-07-12 22:20:08 +00:00
|
|
|
static int add_prelim_ref(const struct btrfs_fs_info *fs_info,
|
|
|
|
struct preftree *preftree, u64 root_id,
|
2017-06-29 03:56:57 +00:00
|
|
|
const struct btrfs_key *key, int level, u64 parent,
|
2017-07-12 22:20:10 +00:00
|
|
|
u64 wanted_disk_byte, int count,
|
|
|
|
struct share_check *sc, gfp_t gfp_mask)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
2017-06-29 03:56:57 +00:00
|
|
|
struct prelim_ref *ref;
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2013-10-30 05:25:24 +00:00
|
|
|
if (root_id == BTRFS_DATA_RELOC_TREE_OBJECTID)
|
|
|
|
return 0;
|
|
|
|
|
2013-08-09 05:25:36 +00:00
|
|
|
ref = kmem_cache_alloc(btrfs_prelim_ref_cache, gfp_mask);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (!ref)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
ref->root_id = root_id;
|
2020-02-07 09:38:15 +00:00
|
|
|
if (key)
|
2012-05-15 15:55:51 +00:00
|
|
|
ref->key_for_search = *key;
|
2020-02-07 09:38:15 +00:00
|
|
|
else
|
2012-05-15 15:55:51 +00:00
|
|
|
memset(&ref->key_for_search, 0, sizeof(ref->key_for_search));
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2012-05-30 16:05:21 +00:00
|
|
|
ref->inode_list = NULL;
|
2011-11-23 17:55:04 +00:00
|
|
|
ref->level = level;
|
|
|
|
ref->count = count;
|
|
|
|
ref->parent = parent;
|
|
|
|
ref->wanted_disk_byte = wanted_disk_byte;
|
2017-07-12 22:20:10 +00:00
|
|
|
prelim_ref_insert(fs_info, preftree, ref, sc);
|
|
|
|
return extent_is_shared(sc);
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 22:20:06 +00:00
|
|
|
/* direct refs use root == 0, key == NULL */
|
2017-07-12 22:20:08 +00:00
|
|
|
static int add_direct_ref(const struct btrfs_fs_info *fs_info,
|
|
|
|
struct preftrees *preftrees, int level, u64 parent,
|
2017-07-12 22:20:10 +00:00
|
|
|
u64 wanted_disk_byte, int count,
|
|
|
|
struct share_check *sc, gfp_t gfp_mask)
|
2017-07-12 22:20:06 +00:00
|
|
|
{
|
2017-07-12 22:20:08 +00:00
|
|
|
return add_prelim_ref(fs_info, &preftrees->direct, 0, NULL, level,
|
2017-07-12 22:20:10 +00:00
|
|
|
parent, wanted_disk_byte, count, sc, gfp_mask);
|
2017-07-12 22:20:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* indirect refs use parent == 0 */
|
2017-07-12 22:20:08 +00:00
|
|
|
static int add_indirect_ref(const struct btrfs_fs_info *fs_info,
|
|
|
|
struct preftrees *preftrees, u64 root_id,
|
2017-07-12 22:20:06 +00:00
|
|
|
const struct btrfs_key *key, int level,
|
2017-07-12 22:20:10 +00:00
|
|
|
u64 wanted_disk_byte, int count,
|
|
|
|
struct share_check *sc, gfp_t gfp_mask)
|
2017-07-12 22:20:06 +00:00
|
|
|
{
|
|
|
|
struct preftree *tree = &preftrees->indirect;
|
|
|
|
|
|
|
|
if (!key)
|
|
|
|
tree = &preftrees->indirect_missing_keys;
|
2017-07-12 22:20:08 +00:00
|
|
|
return add_prelim_ref(fs_info, tree, root_id, key, level, 0,
|
2017-07-12 22:20:10 +00:00
|
|
|
wanted_disk_byte, count, sc, gfp_mask);
|
2017-07-12 22:20:06 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 09:38:16 +00:00
|
|
|
static int is_shared_data_backref(struct preftrees *preftrees, u64 bytenr)
|
|
|
|
{
|
|
|
|
struct rb_node **p = &preftrees->direct.root.rb_root.rb_node;
|
|
|
|
struct rb_node *parent = NULL;
|
|
|
|
struct prelim_ref *ref = NULL;
|
2020-04-29 13:27:32 +00:00
|
|
|
struct prelim_ref target = {};
|
2020-02-07 09:38:16 +00:00
|
|
|
int result;
|
|
|
|
|
|
|
|
target.parent = bytenr;
|
|
|
|
|
|
|
|
while (*p) {
|
|
|
|
parent = *p;
|
|
|
|
ref = rb_entry(parent, struct prelim_ref, rbnode);
|
|
|
|
result = prelim_ref_compare(ref, &target);
|
|
|
|
|
|
|
|
if (result < 0)
|
|
|
|
p = &(*p)->rb_left;
|
|
|
|
else if (result > 0)
|
|
|
|
p = &(*p)->rb_right;
|
|
|
|
else
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
static int add_all_parents(struct btrfs_backref_walk_ctx *ctx,
|
|
|
|
struct btrfs_root *root, struct btrfs_path *path,
|
2020-02-07 09:38:16 +00:00
|
|
|
struct ulist *parents,
|
|
|
|
struct preftrees *preftrees, struct prelim_ref *ref,
|
2022-11-01 16:15:47 +00:00
|
|
|
int level)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
2012-06-19 13:42:26 +00:00
|
|
|
int ret = 0;
|
|
|
|
int slot;
|
|
|
|
struct extent_buffer *eb;
|
|
|
|
struct btrfs_key key;
|
2014-01-24 19:05:42 +00:00
|
|
|
struct btrfs_key *key_for_search = &ref->key_for_search;
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_file_extent_item *fi;
|
2013-07-05 18:03:47 +00:00
|
|
|
struct extent_inode_elem *eie = NULL, *old = NULL;
|
2011-11-23 17:55:04 +00:00
|
|
|
u64 disk_byte;
|
2014-01-24 19:05:42 +00:00
|
|
|
u64 wanted_disk_byte = ref->wanted_disk_byte;
|
|
|
|
u64 count = 0;
|
2020-02-07 09:38:15 +00:00
|
|
|
u64 data_offset;
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2012-06-19 13:42:26 +00:00
|
|
|
if (level != 0) {
|
|
|
|
eb = path->nodes[level];
|
|
|
|
ret = ulist_add(parents, eb->start, 0, GFP_NOFS);
|
2012-05-30 16:05:21 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2011-11-23 17:55:04 +00:00
|
|
|
return 0;
|
2012-06-19 13:42:26 +00:00
|
|
|
}
|
2011-11-23 17:55:04 +00:00
|
|
|
|
|
|
|
/*
|
2020-02-07 09:38:16 +00:00
|
|
|
* 1. We normally enter this function with the path already pointing to
|
|
|
|
* the first item to check. But sometimes, we may enter it with
|
|
|
|
* slot == nritems.
|
|
|
|
* 2. We are searching for normal backref but bytenr of this leaf
|
|
|
|
* matches shared data backref
|
2020-02-07 09:38:17 +00:00
|
|
|
* 3. The leaf owner is not equal to the root we are searching
|
|
|
|
*
|
2020-02-07 09:38:16 +00:00
|
|
|
* For these cases, go to the next leaf before we continue.
|
2011-11-23 17:55:04 +00:00
|
|
|
*/
|
2020-02-07 09:38:16 +00:00
|
|
|
eb = path->nodes[0];
|
|
|
|
if (path->slots[0] >= btrfs_header_nritems(eb) ||
|
2020-02-07 09:38:17 +00:00
|
|
|
is_shared_data_backref(preftrees, eb->start) ||
|
|
|
|
ref->root_id != btrfs_header_owner(eb)) {
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->time_seq == BTRFS_SEQ_LAST)
|
2015-04-16 06:54:50 +00:00
|
|
|
ret = btrfs_next_leaf(root, path);
|
|
|
|
else
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = btrfs_next_old_leaf(root, path, ctx->time_seq);
|
2015-04-16 06:54:50 +00:00
|
|
|
}
|
2011-11-23 17:55:04 +00:00
|
|
|
|
btrfs: backref, use correct count to resolve normal data refs
With the following patches:
- btrfs: backref, only collect file extent items matching backref offset
- btrfs: backref, not adding refs from shared block when resolving normal backref
- btrfs: backref, only search backref entries from leaves of the same root
we only collect the normal data refs we want, so the imprecise upper
bound total_refs of that EXTENT_ITEM could now be changed to the count
of the normal backref entry we want to search.
Background and how the patches fit together:
Btrfs has two types of data backref.
For BTRFS_EXTENT_DATA_REF_KEY type of backref, we don't have the
exact block number. Therefore, we need to call resolve_indirect_refs.
It uses btrfs_search_slot to locate the leaf block. Then
we need to walk through the leaves to search for the EXTENT_DATA items
that have disk bytenr matching the extent item (add_all_parents).
When resolving indirect refs, we could take entries that don't
belong to the backref entry we are searching for right now.
For that reason when searching backref entry, we always use total
refs of that EXTENT_ITEM rather than individual count.
For example:
item 11 key (40831553536 EXTENT_ITEM 4194304) itemoff 15460 itemsize
extent refs 24 gen 7302 flags DATA
shared data backref parent 394985472 count 10 #1
extent data backref root 257 objectid 260 offset 1048576 count 3 #2
extent data backref root 256 objectid 260 offset 65536 count 6 #3
extent data backref root 257 objectid 260 offset 65536 count 5 #4
For example, when searching backref entry #4, we'll use total_refs
24, a very loose loop ending condition, instead of total_refs = 5.
But using total_refs = 24 is not accurate. Sometimes, we'll never find
all the refs from specific root. As a result, the loop keeps on going
until we reach the end of that inode.
The first 3 patches, handle 3 different types refs we might encounter.
These refs do not belong to the normal backref we are searching, and
hence need to be skipped.
This patch changes the total_refs to correct number so that we could
end loop as soon as we find all the refs we want.
btrfs send uses backref to find possible clone sources, the following
is a simple test to compare the results with and without this patch:
$ btrfs subvolume create /sub1
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=64K count=1 seek=$((i-1)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot /sub1 /sub2
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=4K count=1 seek=$(((i-1)*16+10)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot -r /sub1 /snap1
$ time btrfs send /snap1 | btrfs receive /volume2
Without this patch:
real 69m48.124s
user 0m50.199s
sys 70m15.600s
With this patch:
real 1m59.683s
user 0m35.421s
sys 2m42.684s
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: ethanwu <ethanwu@synology.com>
[ add patchset cover letter with background and numbers ]
Signed-off-by: David Sterba <dsterba@suse.com>
2020-02-07 09:38:18 +00:00
|
|
|
while (!ret && count < ref->count) {
|
2011-11-23 17:55:04 +00:00
|
|
|
eb = path->nodes[0];
|
2012-06-19 13:42:26 +00:00
|
|
|
slot = path->slots[0];
|
|
|
|
|
|
|
|
btrfs_item_key_to_cpu(eb, &key, slot);
|
|
|
|
|
|
|
|
if (key.objectid != key_for_search->objectid ||
|
|
|
|
key.type != BTRFS_EXTENT_DATA_KEY)
|
|
|
|
break;
|
|
|
|
|
2020-02-07 09:38:16 +00:00
|
|
|
/*
|
|
|
|
* We are searching for normal backref but bytenr of this leaf
|
2020-02-07 09:38:17 +00:00
|
|
|
* matches shared data backref, OR
|
|
|
|
* the leaf owner is not equal to the root we are searching for
|
2020-02-07 09:38:16 +00:00
|
|
|
*/
|
2020-02-07 09:38:17 +00:00
|
|
|
if (slot == 0 &&
|
|
|
|
(is_shared_data_backref(preftrees, eb->start) ||
|
|
|
|
ref->root_id != btrfs_header_owner(eb))) {
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->time_seq == BTRFS_SEQ_LAST)
|
2020-02-07 09:38:16 +00:00
|
|
|
ret = btrfs_next_leaf(root, path);
|
|
|
|
else
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = btrfs_next_old_leaf(root, path, ctx->time_seq);
|
2020-02-07 09:38:16 +00:00
|
|
|
continue;
|
|
|
|
}
|
2012-06-19 13:42:26 +00:00
|
|
|
fi = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item);
|
|
|
|
disk_byte = btrfs_file_extent_disk_bytenr(eb, fi);
|
2020-02-07 09:38:15 +00:00
|
|
|
data_offset = btrfs_file_extent_offset(eb, fi);
|
2012-06-19 13:42:26 +00:00
|
|
|
|
|
|
|
if (disk_byte == wanted_disk_byte) {
|
|
|
|
eie = NULL;
|
2013-07-05 18:03:47 +00:00
|
|
|
old = NULL;
|
2020-02-07 09:38:15 +00:00
|
|
|
if (ref->key_for_search.offset == key.offset - data_offset)
|
|
|
|
count++;
|
|
|
|
else
|
|
|
|
goto next;
|
2022-11-01 16:15:47 +00:00
|
|
|
if (!ctx->ignore_extent_item_pos) {
|
2012-06-19 13:42:26 +00:00
|
|
|
ret = check_extent_in_eb(&key, eb, fi,
|
2022-11-01 16:15:47 +00:00
|
|
|
ctx->extent_item_pos, &eie);
|
2012-06-19 13:42:26 +00:00
|
|
|
if (ret < 0)
|
|
|
|
break;
|
|
|
|
}
|
2013-07-05 18:03:47 +00:00
|
|
|
if (ret > 0)
|
|
|
|
goto next;
|
2014-07-28 08:57:04 +00:00
|
|
|
ret = ulist_add_merge_ptr(parents, eb->start,
|
|
|
|
eie, (void **)&old, GFP_NOFS);
|
2013-07-05 18:03:47 +00:00
|
|
|
if (ret < 0)
|
|
|
|
break;
|
2022-11-01 16:15:47 +00:00
|
|
|
if (!ret && !ctx->ignore_extent_item_pos) {
|
2013-07-05 18:03:47 +00:00
|
|
|
while (old->next)
|
|
|
|
old = old->next;
|
|
|
|
old->next = eie;
|
2012-06-19 13:42:26 +00:00
|
|
|
}
|
2014-01-28 11:13:38 +00:00
|
|
|
eie = NULL;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
2013-07-05 18:03:47 +00:00
|
|
|
next:
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->time_seq == BTRFS_SEQ_LAST)
|
2015-04-16 06:54:50 +00:00
|
|
|
ret = btrfs_next_item(root, path);
|
|
|
|
else
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = btrfs_next_old_item(root, path, ctx->time_seq);
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
2012-06-19 13:42:26 +00:00
|
|
|
if (ret > 0)
|
|
|
|
ret = 0;
|
2014-01-28 11:13:38 +00:00
|
|
|
else if (ret < 0)
|
|
|
|
free_inode_elem_list(eie);
|
2012-06-19 13:42:26 +00:00
|
|
|
return ret;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* resolve an indirect backref in the form (root_id, key, level)
|
|
|
|
* to a logical address
|
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
static int resolve_indirect_ref(struct btrfs_backref_walk_ctx *ctx,
|
|
|
|
struct btrfs_path *path,
|
2020-02-07 09:38:16 +00:00
|
|
|
struct preftrees *preftrees,
|
2022-11-01 16:15:47 +00:00
|
|
|
struct prelim_ref *ref, struct ulist *parents)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
|
|
|
struct btrfs_root *root;
|
|
|
|
struct extent_buffer *eb;
|
|
|
|
int ret = 0;
|
|
|
|
int root_level;
|
|
|
|
int level = ref->level;
|
2020-02-07 09:38:15 +00:00
|
|
|
struct btrfs_key search_key = ref->key_for_search;
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2020-10-19 20:02:31 +00:00
|
|
|
/*
|
|
|
|
* If we're search_commit_root we could possibly be holding locks on
|
|
|
|
* other tree nodes. This happens when qgroups does backref walks when
|
|
|
|
* adding new delayed refs. To deal with this we need to look in cache
|
|
|
|
* for the root, and if we don't find it then we need to search the
|
|
|
|
* tree_root's commit root, thus the btrfs_get_fs_root_commit_root usage
|
|
|
|
* here.
|
|
|
|
*/
|
|
|
|
if (path->search_commit_root)
|
2022-11-01 16:15:47 +00:00
|
|
|
root = btrfs_get_fs_root_commit_root(ctx->fs_info, path, ref->root_id);
|
2020-10-19 20:02:31 +00:00
|
|
|
else
|
2022-11-01 16:15:47 +00:00
|
|
|
root = btrfs_get_fs_root(ctx->fs_info, ref->root_id, false);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (IS_ERR(root)) {
|
|
|
|
ret = PTR_ERR(root);
|
2020-01-24 14:32:28 +00:00
|
|
|
goto out_free;
|
|
|
|
}
|
|
|
|
|
btrfs: do not resolve backrefs for roots that are being deleted
Zygo reported a deadlock where a task was stuck in the inode logical
resolve code. The deadlock looks like this
Task 1
btrfs_ioctl_logical_to_ino
->iterate_inodes_from_logical
->iterate_extent_inodes
->path->search_commit_root isn't set, so a transaction is started
->resolve_indirect_ref for a root that's being deleted
->search for our key, attempt to lock a node, DEADLOCK
Task 2
btrfs_drop_snapshot
->walk down to a leaf, lock it, walk up, lock node
->end transaction
->start transaction
-> wait_cur_trans
Task 3
btrfs_commit_transaction
->wait_event(cur_trans->write_wait, num_writers == 1) DEADLOCK
We are holding a transaction open in btrfs_ioctl_logical_to_ino while we
try to resolve our references. btrfs_drop_snapshot() holds onto its
locks while it stops and starts transaction handles, because it assumes
nobody is going to touch the root now. Commit just does what commit
does, waiting for the writers to finish, blocking any new trans handles
from starting.
Fix this by making the backref code not try to resolve backrefs of roots
that are currently being deleted. This will keep us from walking into a
snapshot that's currently being deleted.
This problem was harder to hit before because we rarely broke out of the
snapshot delete halfway through, but with my delayed ref throttling code
it happened much more often. However we've always been able to do this,
so it's not a new problem.
Fixes: 8da6d5815c59 ("Btrfs: added btrfs_find_all_roots()")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2020-03-13 21:17:09 +00:00
|
|
|
if (!path->search_commit_root &&
|
|
|
|
test_bit(BTRFS_ROOT_DELETING, &root->state)) {
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
if (btrfs_is_testing(ctx->fs_info)) {
|
2015-10-05 14:35:29 +00:00
|
|
|
ret = -ENOENT;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2014-03-13 19:42:13 +00:00
|
|
|
if (path->search_commit_root)
|
|
|
|
root_level = btrfs_header_level(root->commit_root);
|
2022-11-01 16:15:47 +00:00
|
|
|
else if (ctx->time_seq == BTRFS_SEQ_LAST)
|
2015-04-16 06:54:50 +00:00
|
|
|
root_level = btrfs_header_level(root->node);
|
2014-03-13 19:42:13 +00:00
|
|
|
else
|
2022-11-01 16:15:47 +00:00
|
|
|
root_level = btrfs_old_root_level(root, ctx->time_seq);
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2020-02-14 21:11:47 +00:00
|
|
|
if (root_level + 1 == level)
|
2011-11-23 17:55:04 +00:00
|
|
|
goto out;
|
|
|
|
|
2020-02-07 09:38:15 +00:00
|
|
|
/*
|
|
|
|
* We can often find data backrefs with an offset that is too large
|
|
|
|
* (>= LLONG_MAX, maximum allowed file offset) due to underflows when
|
|
|
|
* subtracting a file's offset with the data offset of its
|
|
|
|
* corresponding extent data item. This can happen for example in the
|
|
|
|
* clone ioctl.
|
|
|
|
*
|
|
|
|
* So if we detect such case we set the search key's offset to zero to
|
|
|
|
* make sure we will find the matching file extent item at
|
|
|
|
* add_all_parents(), otherwise we will miss it because the offset
|
|
|
|
* taken form the backref is much larger then the offset of the file
|
|
|
|
* extent item. This can make us scan a very large number of file
|
|
|
|
* extent items, but at least it will not make us miss any.
|
|
|
|
*
|
|
|
|
* This is an ugly workaround for a behaviour that should have never
|
|
|
|
* existed, but it does and a fix for the clone ioctl would touch a lot
|
|
|
|
* of places, cause backwards incompatibility and would not fix the
|
|
|
|
* problem for extents cloned with older kernels.
|
|
|
|
*/
|
|
|
|
if (search_key.type == BTRFS_EXTENT_DATA_KEY &&
|
|
|
|
search_key.offset >= LLONG_MAX)
|
|
|
|
search_key.offset = 0;
|
2011-11-23 17:55:04 +00:00
|
|
|
path->lowest_level = level;
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->time_seq == BTRFS_SEQ_LAST)
|
2020-02-07 09:38:15 +00:00
|
|
|
ret = btrfs_search_slot(NULL, root, &search_key, path, 0, 0);
|
2015-04-16 06:54:50 +00:00
|
|
|
else
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = btrfs_search_old_slot(root, &search_key, path, ctx->time_seq);
|
2014-01-23 05:47:48 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
btrfs_debug(ctx->fs_info,
|
2016-09-20 14:05:02 +00:00
|
|
|
"search slot in root %llu (level %d, ref count %d) returned %d for key (%llu %u %llu)",
|
2013-08-20 11:20:07 +00:00
|
|
|
ref->root_id, level, ref->count, ret,
|
|
|
|
ref->key_for_search.objectid, ref->key_for_search.type,
|
|
|
|
ref->key_for_search.offset);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
eb = path->nodes[level];
|
2012-06-27 13:23:09 +00:00
|
|
|
while (!eb) {
|
2013-10-31 05:00:08 +00:00
|
|
|
if (WARN_ON(!level)) {
|
2012-06-27 13:23:09 +00:00
|
|
|
ret = 1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
level--;
|
|
|
|
eb = path->nodes[level];
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = add_all_parents(ctx, root, path, parents, preftrees, ref, level);
|
2011-11-23 17:55:04 +00:00
|
|
|
out:
|
2020-01-24 14:33:01 +00:00
|
|
|
btrfs_put_root(root);
|
2020-01-24 14:32:28 +00:00
|
|
|
out_free:
|
2013-06-12 20:20:08 +00:00
|
|
|
path->lowest_level = 0;
|
|
|
|
btrfs_release_path(path);
|
2011-11-23 17:55:04 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-06-29 03:56:56 +00:00
|
|
|
static struct extent_inode_elem *
|
|
|
|
unode_aux_to_inode_list(struct ulist_node *node)
|
|
|
|
{
|
|
|
|
if (!node)
|
|
|
|
return NULL;
|
|
|
|
return (struct extent_inode_elem *)(uintptr_t)node->aux;
|
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:37 +00:00
|
|
|
static void free_leaf_list(struct ulist *ulist)
|
|
|
|
{
|
|
|
|
struct ulist_node *node;
|
|
|
|
struct ulist_iterator uiter;
|
|
|
|
|
|
|
|
ULIST_ITER_INIT(&uiter);
|
|
|
|
while ((node = ulist_next(ulist, &uiter)))
|
|
|
|
free_inode_elem_list(unode_aux_to_inode_list(node));
|
|
|
|
|
|
|
|
ulist_free(ulist);
|
|
|
|
}
|
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
/*
|
2018-11-28 11:05:13 +00:00
|
|
|
* We maintain three separate rbtrees: one for direct refs, one for
|
2017-07-12 22:20:06 +00:00
|
|
|
* indirect refs which have a key, and one for indirect refs which do not
|
|
|
|
* have a key. Each tree does merge on insertion.
|
|
|
|
*
|
|
|
|
* Once all of the references are located, we iterate over the tree of
|
|
|
|
* indirect refs with missing keys. An appropriate key is located and
|
|
|
|
* the ref is moved onto the tree for indirect refs. After all missing
|
|
|
|
* keys are thus located, we iterate over the indirect ref tree, resolve
|
|
|
|
* each reference, and then insert the resolved reference onto the
|
|
|
|
* direct tree (merging there too).
|
|
|
|
*
|
|
|
|
* New backrefs (i.e., for parent nodes) are added to the appropriate
|
|
|
|
* rbtree as they are encountered. The new backrefs are subsequently
|
|
|
|
* resolved as above.
|
2011-11-23 17:55:04 +00:00
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
static int resolve_indirect_refs(struct btrfs_backref_walk_ctx *ctx,
|
|
|
|
struct btrfs_path *path,
|
2017-07-12 22:20:06 +00:00
|
|
|
struct preftrees *preftrees,
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
struct share_check *sc)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
int ret = 0;
|
|
|
|
struct ulist *parents;
|
|
|
|
struct ulist_node *node;
|
2012-05-22 12:56:50 +00:00
|
|
|
struct ulist_iterator uiter;
|
2017-07-12 22:20:06 +00:00
|
|
|
struct rb_node *rnode;
|
2011-11-23 17:55:04 +00:00
|
|
|
|
|
|
|
parents = ulist_alloc(GFP_NOFS);
|
|
|
|
if (!parents)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/*
|
2017-07-12 22:20:06 +00:00
|
|
|
* We could trade memory usage for performance here by iterating
|
|
|
|
* the tree, allocating new refs for each insertion, and then
|
|
|
|
* freeing the entire indirect tree when we're done. In some test
|
|
|
|
* cases, the tree can grow quite large (~200k objects).
|
2011-11-23 17:55:04 +00:00
|
|
|
*/
|
2018-08-22 19:51:53 +00:00
|
|
|
while ((rnode = rb_first_cached(&preftrees->indirect.root))) {
|
2017-07-12 22:20:06 +00:00
|
|
|
struct prelim_ref *ref;
|
|
|
|
|
|
|
|
ref = rb_entry(rnode, struct prelim_ref, rbnode);
|
|
|
|
if (WARN(ref->parent,
|
|
|
|
"BUG: direct ref found in indirect tree")) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2018-08-22 19:51:53 +00:00
|
|
|
rb_erase_cached(&ref->rbnode, &preftrees->indirect.root);
|
2017-07-12 22:20:07 +00:00
|
|
|
preftrees->indirect.count--;
|
2017-07-12 22:20:06 +00:00
|
|
|
|
|
|
|
if (ref->count == 0) {
|
|
|
|
free_pref(ref);
|
2011-11-23 17:55:04 +00:00
|
|
|
continue;
|
2017-07-12 22:20:06 +00:00
|
|
|
}
|
|
|
|
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
if (sc && ref->root_id != sc->root->root_key.objectid) {
|
2017-07-12 22:20:06 +00:00
|
|
|
free_pref(ref);
|
2014-09-10 20:20:45 +00:00
|
|
|
ret = BACKREF_FOUND_SHARED;
|
|
|
|
goto out;
|
|
|
|
}
|
2022-11-01 16:15:47 +00:00
|
|
|
err = resolve_indirect_ref(ctx, path, preftrees, ref, parents);
|
2014-01-23 05:47:49 +00:00
|
|
|
/*
|
|
|
|
* we can only tolerate ENOENT,otherwise,we should catch error
|
|
|
|
* and return directly.
|
|
|
|
*/
|
|
|
|
if (err == -ENOENT) {
|
2022-11-01 16:15:47 +00:00
|
|
|
prelim_ref_insert(ctx->fs_info, &preftrees->direct, ref,
|
2017-07-12 22:20:10 +00:00
|
|
|
NULL);
|
2011-11-23 17:55:04 +00:00
|
|
|
continue;
|
2014-01-23 05:47:49 +00:00
|
|
|
} else if (err) {
|
2017-07-12 22:20:06 +00:00
|
|
|
free_pref(ref);
|
2014-01-23 05:47:49 +00:00
|
|
|
ret = err;
|
|
|
|
goto out;
|
|
|
|
}
|
2011-11-23 17:55:04 +00:00
|
|
|
|
|
|
|
/* we put the first parent into the ref at hand */
|
2012-05-22 12:56:50 +00:00
|
|
|
ULIST_ITER_INIT(&uiter);
|
|
|
|
node = ulist_next(parents, &uiter);
|
2011-11-23 17:55:04 +00:00
|
|
|
ref->parent = node ? node->val : 0;
|
2017-06-29 03:56:56 +00:00
|
|
|
ref->inode_list = unode_aux_to_inode_list(node);
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2017-07-12 22:20:06 +00:00
|
|
|
/* Add a prelim_ref(s) for any other parent(s). */
|
2012-05-22 12:56:50 +00:00
|
|
|
while ((node = ulist_next(parents, &uiter))) {
|
2017-07-12 22:20:06 +00:00
|
|
|
struct prelim_ref *new_ref;
|
|
|
|
|
2013-08-09 05:25:36 +00:00
|
|
|
new_ref = kmem_cache_alloc(btrfs_prelim_ref_cache,
|
|
|
|
GFP_NOFS);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (!new_ref) {
|
2017-07-12 22:20:06 +00:00
|
|
|
free_pref(ref);
|
2011-11-23 17:55:04 +00:00
|
|
|
ret = -ENOMEM;
|
2013-04-15 10:26:38 +00:00
|
|
|
goto out;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
memcpy(new_ref, ref, sizeof(*ref));
|
|
|
|
new_ref->parent = node->val;
|
2017-06-29 03:56:56 +00:00
|
|
|
new_ref->inode_list = unode_aux_to_inode_list(node);
|
2022-11-01 16:15:47 +00:00
|
|
|
prelim_ref_insert(ctx->fs_info, &preftrees->direct,
|
2017-07-12 22:20:10 +00:00
|
|
|
new_ref, NULL);
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
2017-07-12 22:20:06 +00:00
|
|
|
|
2017-07-12 22:20:10 +00:00
|
|
|
/*
|
2018-11-28 11:05:13 +00:00
|
|
|
* Now it's a direct ref, put it in the direct tree. We must
|
2017-07-12 22:20:10 +00:00
|
|
|
* do this last because the ref could be merged/freed here.
|
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
prelim_ref_insert(ctx->fs_info, &preftrees->direct, ref, NULL);
|
2017-07-12 22:20:06 +00:00
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
ulist_reinit(parents);
|
2017-07-12 22:20:09 +00:00
|
|
|
cond_resched();
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
2013-04-15 10:26:38 +00:00
|
|
|
out:
|
2022-11-01 16:15:37 +00:00
|
|
|
/*
|
|
|
|
* We may have inode lists attached to refs in the parents ulist, so we
|
|
|
|
* must free them before freeing the ulist and its refs.
|
|
|
|
*/
|
|
|
|
free_leaf_list(parents);
|
2011-11-23 17:55:04 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-05-15 15:55:51 +00:00
|
|
|
/*
|
|
|
|
* read tree blocks and add keys where required.
|
|
|
|
*/
|
2017-06-29 03:56:57 +00:00
|
|
|
static int add_missing_keys(struct btrfs_fs_info *fs_info,
|
btrfs: honor path->skip_locking in backref code
Qgroups will do the old roots lookup at delayed ref time, which could be
while walking down the extent root while running a delayed ref. This
should be fine, except we specifically lock eb's in the backref walking
code irrespective of path->skip_locking, which deadlocks the system.
Fix up the backref code to honor path->skip_locking, nobody will be
modifying the commit_root when we're searching so it's completely safe
to do.
This happens since fb235dc06fac ("btrfs: qgroup: Move half of the qgroup
accounting time out of commit trans"), kernel may lockup with quota
enabled.
There is one backref trace triggered by snapshot dropping along with
write operation in the source subvolume. The example can be reliably
reproduced:
btrfs-cleaner D 0 4062 2 0x80000000
Call Trace:
schedule+0x32/0x90
btrfs_tree_read_lock+0x93/0x130 [btrfs]
find_parent_nodes+0x29b/0x1170 [btrfs]
btrfs_find_all_roots_safe+0xa8/0x120 [btrfs]
btrfs_find_all_roots+0x57/0x70 [btrfs]
btrfs_qgroup_trace_extent_post+0x37/0x70 [btrfs]
btrfs_qgroup_trace_leaf_items+0x10b/0x140 [btrfs]
btrfs_qgroup_trace_subtree+0xc8/0xe0 [btrfs]
do_walk_down+0x541/0x5e3 [btrfs]
walk_down_tree+0xab/0xe7 [btrfs]
btrfs_drop_snapshot+0x356/0x71a [btrfs]
btrfs_clean_one_deleted_snapshot+0xb8/0xf0 [btrfs]
cleaner_kthread+0x12b/0x160 [btrfs]
kthread+0x112/0x130
ret_from_fork+0x27/0x50
When dropping snapshots with qgroup enabled, we will trigger backref
walk.
However such backref walk at that timing is pretty dangerous, as if one
of the parent nodes get WRITE locked by other thread, we could cause a
dead lock.
For example:
FS 260 FS 261 (Dropped)
node A node B
/ \ / \
node C node D node E
/ \ / \ / \
leaf F|leaf G|leaf H|leaf I|leaf J|leaf K
The lock sequence would be:
Thread A (cleaner) | Thread B (other writer)
-----------------------------------------------------------------------
write_lock(B) |
write_lock(D) |
^^^ called by walk_down_tree() |
| write_lock(A)
| write_lock(D) << Stall
read_lock(H) << for backref walk |
read_lock(D) << lock owner is |
the same thread A |
so read lock is OK |
read_lock(A) << Stall |
So thread A hold write lock D, and needs read lock A to unlock.
While thread B holds write lock A, while needs lock D to unlock.
This will cause a deadlock.
This is not only limited to snapshot dropping case. As the backref
walk, even only happens on commit trees, is breaking the normal top-down
locking order, makes it deadlock prone.
Fixes: fb235dc06fac ("btrfs: qgroup: Move half of the qgroup accounting time out of commit trans")
CC: stable@vger.kernel.org # 4.14+
Reported-and-tested-by: David Sterba <dsterba@suse.com>
Reported-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
[ rebase to latest branch and fix lock assert bug in btrfs/007 ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
[ copy logs and deadlock analysis from Qu's patch ]
Signed-off-by: David Sterba <dsterba@suse.com>
2019-01-16 16:00:57 +00:00
|
|
|
struct preftrees *preftrees, bool lock)
|
2012-05-15 15:55:51 +00:00
|
|
|
{
|
2017-06-29 03:56:57 +00:00
|
|
|
struct prelim_ref *ref;
|
2012-05-15 15:55:51 +00:00
|
|
|
struct extent_buffer *eb;
|
2017-07-12 22:20:06 +00:00
|
|
|
struct preftree *tree = &preftrees->indirect_missing_keys;
|
|
|
|
struct rb_node *node;
|
2012-05-15 15:55:51 +00:00
|
|
|
|
2018-08-22 19:51:53 +00:00
|
|
|
while ((node = rb_first_cached(&tree->root))) {
|
2017-07-12 22:20:06 +00:00
|
|
|
ref = rb_entry(node, struct prelim_ref, rbnode);
|
2018-08-22 19:51:53 +00:00
|
|
|
rb_erase_cached(node, &tree->root);
|
2017-07-12 22:20:06 +00:00
|
|
|
|
|
|
|
BUG_ON(ref->parent); /* should not be a direct ref */
|
|
|
|
BUG_ON(ref->key_for_search.type);
|
2012-05-15 15:55:51 +00:00
|
|
|
BUG_ON(!ref->wanted_disk_byte);
|
2017-07-12 22:20:06 +00:00
|
|
|
|
2020-11-05 15:45:18 +00:00
|
|
|
eb = read_tree_block(fs_info, ref->wanted_disk_byte,
|
|
|
|
ref->root_id, 0, ref->level - 1, NULL);
|
2015-05-25 09:30:15 +00:00
|
|
|
if (IS_ERR(eb)) {
|
2017-07-12 22:20:06 +00:00
|
|
|
free_pref(ref);
|
2015-05-25 09:30:15 +00:00
|
|
|
return PTR_ERR(eb);
|
2022-02-22 07:41:19 +00:00
|
|
|
}
|
|
|
|
if (!extent_buffer_uptodate(eb)) {
|
2017-07-12 22:20:06 +00:00
|
|
|
free_pref(ref);
|
2013-04-23 18:17:42 +00:00
|
|
|
free_extent_buffer(eb);
|
|
|
|
return -EIO;
|
|
|
|
}
|
2022-02-22 07:41:19 +00:00
|
|
|
|
btrfs: honor path->skip_locking in backref code
Qgroups will do the old roots lookup at delayed ref time, which could be
while walking down the extent root while running a delayed ref. This
should be fine, except we specifically lock eb's in the backref walking
code irrespective of path->skip_locking, which deadlocks the system.
Fix up the backref code to honor path->skip_locking, nobody will be
modifying the commit_root when we're searching so it's completely safe
to do.
This happens since fb235dc06fac ("btrfs: qgroup: Move half of the qgroup
accounting time out of commit trans"), kernel may lockup with quota
enabled.
There is one backref trace triggered by snapshot dropping along with
write operation in the source subvolume. The example can be reliably
reproduced:
btrfs-cleaner D 0 4062 2 0x80000000
Call Trace:
schedule+0x32/0x90
btrfs_tree_read_lock+0x93/0x130 [btrfs]
find_parent_nodes+0x29b/0x1170 [btrfs]
btrfs_find_all_roots_safe+0xa8/0x120 [btrfs]
btrfs_find_all_roots+0x57/0x70 [btrfs]
btrfs_qgroup_trace_extent_post+0x37/0x70 [btrfs]
btrfs_qgroup_trace_leaf_items+0x10b/0x140 [btrfs]
btrfs_qgroup_trace_subtree+0xc8/0xe0 [btrfs]
do_walk_down+0x541/0x5e3 [btrfs]
walk_down_tree+0xab/0xe7 [btrfs]
btrfs_drop_snapshot+0x356/0x71a [btrfs]
btrfs_clean_one_deleted_snapshot+0xb8/0xf0 [btrfs]
cleaner_kthread+0x12b/0x160 [btrfs]
kthread+0x112/0x130
ret_from_fork+0x27/0x50
When dropping snapshots with qgroup enabled, we will trigger backref
walk.
However such backref walk at that timing is pretty dangerous, as if one
of the parent nodes get WRITE locked by other thread, we could cause a
dead lock.
For example:
FS 260 FS 261 (Dropped)
node A node B
/ \ / \
node C node D node E
/ \ / \ / \
leaf F|leaf G|leaf H|leaf I|leaf J|leaf K
The lock sequence would be:
Thread A (cleaner) | Thread B (other writer)
-----------------------------------------------------------------------
write_lock(B) |
write_lock(D) |
^^^ called by walk_down_tree() |
| write_lock(A)
| write_lock(D) << Stall
read_lock(H) << for backref walk |
read_lock(D) << lock owner is |
the same thread A |
so read lock is OK |
read_lock(A) << Stall |
So thread A hold write lock D, and needs read lock A to unlock.
While thread B holds write lock A, while needs lock D to unlock.
This will cause a deadlock.
This is not only limited to snapshot dropping case. As the backref
walk, even only happens on commit trees, is breaking the normal top-down
locking order, makes it deadlock prone.
Fixes: fb235dc06fac ("btrfs: qgroup: Move half of the qgroup accounting time out of commit trans")
CC: stable@vger.kernel.org # 4.14+
Reported-and-tested-by: David Sterba <dsterba@suse.com>
Reported-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
[ rebase to latest branch and fix lock assert bug in btrfs/007 ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
[ copy logs and deadlock analysis from Qu's patch ]
Signed-off-by: David Sterba <dsterba@suse.com>
2019-01-16 16:00:57 +00:00
|
|
|
if (lock)
|
|
|
|
btrfs_tree_read_lock(eb);
|
2012-05-15 15:55:51 +00:00
|
|
|
if (btrfs_header_level(eb) == 0)
|
|
|
|
btrfs_item_key_to_cpu(eb, &ref->key_for_search, 0);
|
|
|
|
else
|
|
|
|
btrfs_node_key_to_cpu(eb, &ref->key_for_search, 0);
|
btrfs: honor path->skip_locking in backref code
Qgroups will do the old roots lookup at delayed ref time, which could be
while walking down the extent root while running a delayed ref. This
should be fine, except we specifically lock eb's in the backref walking
code irrespective of path->skip_locking, which deadlocks the system.
Fix up the backref code to honor path->skip_locking, nobody will be
modifying the commit_root when we're searching so it's completely safe
to do.
This happens since fb235dc06fac ("btrfs: qgroup: Move half of the qgroup
accounting time out of commit trans"), kernel may lockup with quota
enabled.
There is one backref trace triggered by snapshot dropping along with
write operation in the source subvolume. The example can be reliably
reproduced:
btrfs-cleaner D 0 4062 2 0x80000000
Call Trace:
schedule+0x32/0x90
btrfs_tree_read_lock+0x93/0x130 [btrfs]
find_parent_nodes+0x29b/0x1170 [btrfs]
btrfs_find_all_roots_safe+0xa8/0x120 [btrfs]
btrfs_find_all_roots+0x57/0x70 [btrfs]
btrfs_qgroup_trace_extent_post+0x37/0x70 [btrfs]
btrfs_qgroup_trace_leaf_items+0x10b/0x140 [btrfs]
btrfs_qgroup_trace_subtree+0xc8/0xe0 [btrfs]
do_walk_down+0x541/0x5e3 [btrfs]
walk_down_tree+0xab/0xe7 [btrfs]
btrfs_drop_snapshot+0x356/0x71a [btrfs]
btrfs_clean_one_deleted_snapshot+0xb8/0xf0 [btrfs]
cleaner_kthread+0x12b/0x160 [btrfs]
kthread+0x112/0x130
ret_from_fork+0x27/0x50
When dropping snapshots with qgroup enabled, we will trigger backref
walk.
However such backref walk at that timing is pretty dangerous, as if one
of the parent nodes get WRITE locked by other thread, we could cause a
dead lock.
For example:
FS 260 FS 261 (Dropped)
node A node B
/ \ / \
node C node D node E
/ \ / \ / \
leaf F|leaf G|leaf H|leaf I|leaf J|leaf K
The lock sequence would be:
Thread A (cleaner) | Thread B (other writer)
-----------------------------------------------------------------------
write_lock(B) |
write_lock(D) |
^^^ called by walk_down_tree() |
| write_lock(A)
| write_lock(D) << Stall
read_lock(H) << for backref walk |
read_lock(D) << lock owner is |
the same thread A |
so read lock is OK |
read_lock(A) << Stall |
So thread A hold write lock D, and needs read lock A to unlock.
While thread B holds write lock A, while needs lock D to unlock.
This will cause a deadlock.
This is not only limited to snapshot dropping case. As the backref
walk, even only happens on commit trees, is breaking the normal top-down
locking order, makes it deadlock prone.
Fixes: fb235dc06fac ("btrfs: qgroup: Move half of the qgroup accounting time out of commit trans")
CC: stable@vger.kernel.org # 4.14+
Reported-and-tested-by: David Sterba <dsterba@suse.com>
Reported-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
[ rebase to latest branch and fix lock assert bug in btrfs/007 ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
[ copy logs and deadlock analysis from Qu's patch ]
Signed-off-by: David Sterba <dsterba@suse.com>
2019-01-16 16:00:57 +00:00
|
|
|
if (lock)
|
|
|
|
btrfs_tree_read_unlock(eb);
|
2012-05-15 15:55:51 +00:00
|
|
|
free_extent_buffer(eb);
|
2017-07-12 22:20:10 +00:00
|
|
|
prelim_ref_insert(fs_info, &preftrees->indirect, ref, NULL);
|
2017-07-12 22:20:09 +00:00
|
|
|
cond_resched();
|
2012-05-15 15:55:51 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
/*
|
|
|
|
* add all currently queued delayed refs from this head whose seq nr is
|
|
|
|
* smaller or equal that seq to the list
|
|
|
|
*/
|
2017-07-12 22:20:08 +00:00
|
|
|
static int add_delayed_refs(const struct btrfs_fs_info *fs_info,
|
|
|
|
struct btrfs_delayed_ref_head *head, u64 seq,
|
btrfs: backref, use correct count to resolve normal data refs
With the following patches:
- btrfs: backref, only collect file extent items matching backref offset
- btrfs: backref, not adding refs from shared block when resolving normal backref
- btrfs: backref, only search backref entries from leaves of the same root
we only collect the normal data refs we want, so the imprecise upper
bound total_refs of that EXTENT_ITEM could now be changed to the count
of the normal backref entry we want to search.
Background and how the patches fit together:
Btrfs has two types of data backref.
For BTRFS_EXTENT_DATA_REF_KEY type of backref, we don't have the
exact block number. Therefore, we need to call resolve_indirect_refs.
It uses btrfs_search_slot to locate the leaf block. Then
we need to walk through the leaves to search for the EXTENT_DATA items
that have disk bytenr matching the extent item (add_all_parents).
When resolving indirect refs, we could take entries that don't
belong to the backref entry we are searching for right now.
For that reason when searching backref entry, we always use total
refs of that EXTENT_ITEM rather than individual count.
For example:
item 11 key (40831553536 EXTENT_ITEM 4194304) itemoff 15460 itemsize
extent refs 24 gen 7302 flags DATA
shared data backref parent 394985472 count 10 #1
extent data backref root 257 objectid 260 offset 1048576 count 3 #2
extent data backref root 256 objectid 260 offset 65536 count 6 #3
extent data backref root 257 objectid 260 offset 65536 count 5 #4
For example, when searching backref entry #4, we'll use total_refs
24, a very loose loop ending condition, instead of total_refs = 5.
But using total_refs = 24 is not accurate. Sometimes, we'll never find
all the refs from specific root. As a result, the loop keeps on going
until we reach the end of that inode.
The first 3 patches, handle 3 different types refs we might encounter.
These refs do not belong to the normal backref we are searching, and
hence need to be skipped.
This patch changes the total_refs to correct number so that we could
end loop as soon as we find all the refs we want.
btrfs send uses backref to find possible clone sources, the following
is a simple test to compare the results with and without this patch:
$ btrfs subvolume create /sub1
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=64K count=1 seek=$((i-1)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot /sub1 /sub2
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=4K count=1 seek=$(((i-1)*16+10)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot -r /sub1 /snap1
$ time btrfs send /snap1 | btrfs receive /volume2
Without this patch:
real 69m48.124s
user 0m50.199s
sys 70m15.600s
With this patch:
real 1m59.683s
user 0m35.421s
sys 2m42.684s
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: ethanwu <ethanwu@synology.com>
[ add patchset cover letter with background and numbers ]
Signed-off-by: David Sterba <dsterba@suse.com>
2020-02-07 09:38:18 +00:00
|
|
|
struct preftrees *preftrees, struct share_check *sc)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
2015-03-30 09:03:00 +00:00
|
|
|
struct btrfs_delayed_ref_node *node;
|
2012-05-15 15:55:51 +00:00
|
|
|
struct btrfs_key key;
|
2017-10-19 18:16:00 +00:00
|
|
|
struct rb_node *n;
|
2017-07-12 22:20:11 +00:00
|
|
|
int count;
|
2012-01-26 20:01:11 +00:00
|
|
|
int ret = 0;
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2014-01-23 14:21:38 +00:00
|
|
|
spin_lock(&head->lock);
|
2018-08-22 19:51:50 +00:00
|
|
|
for (n = rb_first_cached(&head->ref_tree); n; n = rb_next(n)) {
|
2017-10-19 18:16:00 +00:00
|
|
|
node = rb_entry(n, struct btrfs_delayed_ref_node,
|
|
|
|
ref_node);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (node->seq > seq)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (node->action) {
|
|
|
|
case BTRFS_ADD_DELAYED_EXTENT:
|
|
|
|
case BTRFS_UPDATE_DELAYED_HEAD:
|
|
|
|
WARN_ON(1);
|
|
|
|
continue;
|
|
|
|
case BTRFS_ADD_DELAYED_REF:
|
2017-07-12 22:20:11 +00:00
|
|
|
count = node->ref_mod;
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
case BTRFS_DROP_DELAYED_REF:
|
2017-07-12 22:20:11 +00:00
|
|
|
count = node->ref_mod * -1;
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
default:
|
btrfs: use BUG() instead of BUG_ON(1)
BUG_ON(1) leads to bogus warnings from clang when
CONFIG_PROFILE_ANNOTATED_BRANCHES is set:
fs/btrfs/volumes.c:5041:3: error: variable 'max_chunk_size' is used uninitialized whenever 'if' condition is false
[-Werror,-Wsometimes-uninitialized]
BUG_ON(1);
^~~~~~~~~
include/asm-generic/bug.h:61:36: note: expanded from macro 'BUG_ON'
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
^~~~~~~~~~~~~~~~~~~
include/linux/compiler.h:48:23: note: expanded from macro 'unlikely'
# define unlikely(x) (__branch_check__(x, 0, __builtin_constant_p(x)))
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
fs/btrfs/volumes.c:5046:9: note: uninitialized use occurs here
max_chunk_size);
^~~~~~~~~~~~~~
include/linux/kernel.h:860:36: note: expanded from macro 'min'
#define min(x, y) __careful_cmp(x, y, <)
^
include/linux/kernel.h:853:17: note: expanded from macro '__careful_cmp'
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
^
include/linux/kernel.h:847:25: note: expanded from macro '__cmp_once'
typeof(y) unique_y = (y); \
^
fs/btrfs/volumes.c:5041:3: note: remove the 'if' if its condition is always true
BUG_ON(1);
^
include/asm-generic/bug.h:61:32: note: expanded from macro 'BUG_ON'
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
^
fs/btrfs/volumes.c:4993:20: note: initialize the variable 'max_chunk_size' to silence this warning
u64 max_chunk_size;
^
= 0
Change it to BUG() so clang can see that this code path can never
continue.
Reviewed-by: Nikolay Borisov <nborisov@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-03-25 13:02:25 +00:00
|
|
|
BUG();
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
switch (node->type) {
|
|
|
|
case BTRFS_TREE_BLOCK_REF_KEY: {
|
2017-07-12 22:20:06 +00:00
|
|
|
/* NORMAL INDIRECT METADATA backref */
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_delayed_tree_ref *ref;
|
btrfs: fix processing of delayed tree block refs during backref walking
During backref walking, when processing a delayed reference with a type of
BTRFS_TREE_BLOCK_REF_KEY, we have two bugs there:
1) We are accessing the delayed references extent_op, and its key, without
the protection of the delayed ref head's lock;
2) If there's no extent op for the delayed ref head, we end up with an
uninitialized key in the stack, variable 'tmp_op_key', and then pass
it to add_indirect_ref(), which adds the reference to the indirect
refs rb tree.
This is wrong, because indirect references should have a NULL key
when we don't have access to the key, and in that case they should be
added to the indirect_missing_keys rb tree and not to the indirect rb
tree.
This means that if have BTRFS_TREE_BLOCK_REF_KEY delayed ref resulting
from freeing an extent buffer, therefore with a count of -1, it will
not cancel out the corresponding reference we have in the extent tree
(with a count of 1), since both references end up in different rb
trees.
When using fiemap, where we often need to check if extents are shared
through shared subtrees resulting from snapshots, it means we can
incorrectly report an extent as shared when it's no longer shared.
However this is temporary because after the transaction is committed
the extent is no longer reported as shared, as running the delayed
reference results in deleting the tree block reference from the extent
tree.
Outside the fiemap context, the result is unpredictable, as the key was
not initialized but it's used when navigating the rb trees to insert
and search for references (prelim_ref_compare()), and we expect all
references in the indirect rb tree to have valid keys.
The following reproducer triggers the second bug:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount -o compress $DEV $MNT
# With a compressed 128M file we get a tree height of 2 (level 1 root).
xfs_io -f -c "pwrite -b 1M 0 128M" $MNT/foo
btrfs subvolume snapshot $MNT $MNT/snap
# Fiemap should output 0x2008 in the flags column.
# 0x2000 means shared extent
# 0x8 means encoded extent (because it's compressed)
echo
echo "fiemap after snapshot, range [120M, 120M + 128K):"
xfs_io -c "fiemap -v 120M 128K" $MNT/foo
echo
# Overwrite one extent and fsync to flush delalloc and COW a new path
# in the snapshot's tree.
#
# After this we have a BTRFS_DROP_DELAYED_REF delayed ref of type
# BTRFS_TREE_BLOCK_REF_KEY with a count of -1 for every COWed extent
# buffer in the path.
#
# In the extent tree we have inline references of type
# BTRFS_TREE_BLOCK_REF_KEY, with a count of 1, for the same extent
# buffers, so they should cancel each other, and the extent buffers in
# the fs tree should no longer be considered as shared.
#
echo "Overwriting file range [120M, 120M + 128K)..."
xfs_io -c "pwrite -b 128K 120M 128K" $MNT/snap/foo
xfs_io -c "fsync" $MNT/snap/foo
# Fiemap should output 0x8 in the flags column. The extent in the range
# [120M, 120M + 128K) is no longer shared, it's now exclusive to the fs
# tree.
echo
echo "fiemap after overwrite range [120M, 120M + 128K):"
xfs_io -c "fiemap -v 120M 128K" $MNT/foo
echo
umount $MNT
Running it before this patch:
$ ./test.sh
(...)
wrote 134217728/134217728 bytes at offset 0
128 MiB, 128 ops; 0.1152 sec (1.085 GiB/sec and 1110.5809 ops/sec)
Create a snapshot of '/mnt/sdj' in '/mnt/sdj/snap'
fiemap after snapshot, range [120M, 120M + 128K):
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [245760..246015]: 34304..34559 256 0x2008
Overwriting file range [120M, 120M + 128K)...
wrote 131072/131072 bytes at offset 125829120
128 KiB, 1 ops; 0.0001 sec (683.060 MiB/sec and 5464.4809 ops/sec)
fiemap after overwrite range [120M, 120M + 128K):
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [245760..246015]: 34304..34559 256 0x2008
The extent in the range [120M, 120M + 128K) is still reported as shared
(0x2000 bit set) after overwriting that range and flushing delalloc, which
is not correct - an entire path was COWed in the snapshot's tree and the
extent is now only referenced by the original fs tree.
Running it after this patch:
$ ./test.sh
(...)
wrote 134217728/134217728 bytes at offset 0
128 MiB, 128 ops; 0.1198 sec (1.043 GiB/sec and 1068.2067 ops/sec)
Create a snapshot of '/mnt/sdj' in '/mnt/sdj/snap'
fiemap after snapshot, range [120M, 120M + 128K):
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [245760..246015]: 34304..34559 256 0x2008
Overwriting file range [120M, 120M + 128K)...
wrote 131072/131072 bytes at offset 125829120
128 KiB, 1 ops; 0.0001 sec (694.444 MiB/sec and 5555.5556 ops/sec)
fiemap after overwrite range [120M, 120M + 128K):
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [245760..246015]: 34304..34559 256 0x8
Now the extent is not reported as shared anymore.
So fix this by passing a NULL key pointer to add_indirect_ref() when
processing a delayed reference for a tree block if there's no extent op
for our delayed ref head with a defined key. Also access the extent op
only after locking the delayed ref head's lock.
The reproducer will be converted later to a test case for fstests.
Fixes: 86d5f994425252 ("btrfs: convert prelimary reference tracking to use rbtrees")
Fixes: a6dbceafb915e8 ("btrfs: Remove unused op_key var from add_delayed_refs")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:52 +00:00
|
|
|
struct btrfs_key *key_ptr = NULL;
|
|
|
|
|
|
|
|
if (head->extent_op && head->extent_op->update_key) {
|
|
|
|
btrfs_disk_key_to_cpu(&key, &head->extent_op->key);
|
|
|
|
key_ptr = &key;
|
|
|
|
}
|
2011-11-23 17:55:04 +00:00
|
|
|
|
|
|
|
ref = btrfs_delayed_node_to_tree_ref(node);
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_indirect_ref(fs_info, preftrees, ref->root,
|
btrfs: fix processing of delayed tree block refs during backref walking
During backref walking, when processing a delayed reference with a type of
BTRFS_TREE_BLOCK_REF_KEY, we have two bugs there:
1) We are accessing the delayed references extent_op, and its key, without
the protection of the delayed ref head's lock;
2) If there's no extent op for the delayed ref head, we end up with an
uninitialized key in the stack, variable 'tmp_op_key', and then pass
it to add_indirect_ref(), which adds the reference to the indirect
refs rb tree.
This is wrong, because indirect references should have a NULL key
when we don't have access to the key, and in that case they should be
added to the indirect_missing_keys rb tree and not to the indirect rb
tree.
This means that if have BTRFS_TREE_BLOCK_REF_KEY delayed ref resulting
from freeing an extent buffer, therefore with a count of -1, it will
not cancel out the corresponding reference we have in the extent tree
(with a count of 1), since both references end up in different rb
trees.
When using fiemap, where we often need to check if extents are shared
through shared subtrees resulting from snapshots, it means we can
incorrectly report an extent as shared when it's no longer shared.
However this is temporary because after the transaction is committed
the extent is no longer reported as shared, as running the delayed
reference results in deleting the tree block reference from the extent
tree.
Outside the fiemap context, the result is unpredictable, as the key was
not initialized but it's used when navigating the rb trees to insert
and search for references (prelim_ref_compare()), and we expect all
references in the indirect rb tree to have valid keys.
The following reproducer triggers the second bug:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount -o compress $DEV $MNT
# With a compressed 128M file we get a tree height of 2 (level 1 root).
xfs_io -f -c "pwrite -b 1M 0 128M" $MNT/foo
btrfs subvolume snapshot $MNT $MNT/snap
# Fiemap should output 0x2008 in the flags column.
# 0x2000 means shared extent
# 0x8 means encoded extent (because it's compressed)
echo
echo "fiemap after snapshot, range [120M, 120M + 128K):"
xfs_io -c "fiemap -v 120M 128K" $MNT/foo
echo
# Overwrite one extent and fsync to flush delalloc and COW a new path
# in the snapshot's tree.
#
# After this we have a BTRFS_DROP_DELAYED_REF delayed ref of type
# BTRFS_TREE_BLOCK_REF_KEY with a count of -1 for every COWed extent
# buffer in the path.
#
# In the extent tree we have inline references of type
# BTRFS_TREE_BLOCK_REF_KEY, with a count of 1, for the same extent
# buffers, so they should cancel each other, and the extent buffers in
# the fs tree should no longer be considered as shared.
#
echo "Overwriting file range [120M, 120M + 128K)..."
xfs_io -c "pwrite -b 128K 120M 128K" $MNT/snap/foo
xfs_io -c "fsync" $MNT/snap/foo
# Fiemap should output 0x8 in the flags column. The extent in the range
# [120M, 120M + 128K) is no longer shared, it's now exclusive to the fs
# tree.
echo
echo "fiemap after overwrite range [120M, 120M + 128K):"
xfs_io -c "fiemap -v 120M 128K" $MNT/foo
echo
umount $MNT
Running it before this patch:
$ ./test.sh
(...)
wrote 134217728/134217728 bytes at offset 0
128 MiB, 128 ops; 0.1152 sec (1.085 GiB/sec and 1110.5809 ops/sec)
Create a snapshot of '/mnt/sdj' in '/mnt/sdj/snap'
fiemap after snapshot, range [120M, 120M + 128K):
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [245760..246015]: 34304..34559 256 0x2008
Overwriting file range [120M, 120M + 128K)...
wrote 131072/131072 bytes at offset 125829120
128 KiB, 1 ops; 0.0001 sec (683.060 MiB/sec and 5464.4809 ops/sec)
fiemap after overwrite range [120M, 120M + 128K):
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [245760..246015]: 34304..34559 256 0x2008
The extent in the range [120M, 120M + 128K) is still reported as shared
(0x2000 bit set) after overwriting that range and flushing delalloc, which
is not correct - an entire path was COWed in the snapshot's tree and the
extent is now only referenced by the original fs tree.
Running it after this patch:
$ ./test.sh
(...)
wrote 134217728/134217728 bytes at offset 0
128 MiB, 128 ops; 0.1198 sec (1.043 GiB/sec and 1068.2067 ops/sec)
Create a snapshot of '/mnt/sdj' in '/mnt/sdj/snap'
fiemap after snapshot, range [120M, 120M + 128K):
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [245760..246015]: 34304..34559 256 0x2008
Overwriting file range [120M, 120M + 128K)...
wrote 131072/131072 bytes at offset 125829120
128 KiB, 1 ops; 0.0001 sec (694.444 MiB/sec and 5555.5556 ops/sec)
fiemap after overwrite range [120M, 120M + 128K):
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [245760..246015]: 34304..34559 256 0x8
Now the extent is not reported as shared anymore.
So fix this by passing a NULL key pointer to add_indirect_ref() when
processing a delayed reference for a tree block if there's no extent op
for our delayed ref head with a defined key. Also access the extent op
only after locking the delayed ref head's lock.
The reproducer will be converted later to a test case for fstests.
Fixes: 86d5f994425252 ("btrfs: convert prelimary reference tracking to use rbtrees")
Fixes: a6dbceafb915e8 ("btrfs: Remove unused op_key var from add_delayed_refs")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:52 +00:00
|
|
|
key_ptr, ref->level + 1,
|
2017-07-12 22:20:11 +00:00
|
|
|
node->bytenr, count, sc,
|
|
|
|
GFP_ATOMIC);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case BTRFS_SHARED_BLOCK_REF_KEY: {
|
2017-07-12 22:20:06 +00:00
|
|
|
/* SHARED DIRECT METADATA backref */
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_delayed_tree_ref *ref;
|
|
|
|
|
|
|
|
ref = btrfs_delayed_node_to_tree_ref(node);
|
2017-07-12 22:20:06 +00:00
|
|
|
|
2017-07-12 22:20:11 +00:00
|
|
|
ret = add_direct_ref(fs_info, preftrees, ref->level + 1,
|
|
|
|
ref->parent, node->bytenr, count,
|
2017-07-12 22:20:10 +00:00
|
|
|
sc, GFP_ATOMIC);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case BTRFS_EXTENT_DATA_REF_KEY: {
|
2017-07-12 22:20:06 +00:00
|
|
|
/* NORMAL INDIRECT DATA backref */
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_delayed_data_ref *ref;
|
|
|
|
ref = btrfs_delayed_node_to_data_ref(node);
|
|
|
|
|
|
|
|
key.objectid = ref->objectid;
|
|
|
|
key.type = BTRFS_EXTENT_DATA_KEY;
|
|
|
|
key.offset = ref->offset;
|
2014-09-10 20:20:45 +00:00
|
|
|
|
|
|
|
/*
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
* If we have a share check context and a reference for
|
|
|
|
* another inode, we can't exit immediately. This is
|
|
|
|
* because even if this is a BTRFS_ADD_DELAYED_REF
|
|
|
|
* reference we may find next a BTRFS_DROP_DELAYED_REF
|
|
|
|
* which cancels out this ADD reference.
|
|
|
|
*
|
|
|
|
* If this is a DROP reference and there was no previous
|
|
|
|
* ADD reference, then we need to signal that when we
|
|
|
|
* process references from the extent tree (through
|
|
|
|
* add_inline_refs() and add_keyed_refs()), we should
|
|
|
|
* not exit early if we find a reference for another
|
|
|
|
* inode, because one of the delayed DROP references
|
|
|
|
* may cancel that reference in the extent tree.
|
2014-09-10 20:20:45 +00:00
|
|
|
*/
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
if (sc && count < 0)
|
|
|
|
sc->have_delayed_delete_refs = true;
|
2014-09-10 20:20:45 +00:00
|
|
|
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_indirect_ref(fs_info, preftrees, ref->root,
|
2017-07-12 22:20:11 +00:00
|
|
|
&key, 0, node->bytenr, count, sc,
|
|
|
|
GFP_ATOMIC);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case BTRFS_SHARED_DATA_REF_KEY: {
|
2017-07-12 22:20:06 +00:00
|
|
|
/* SHARED DIRECT FULL backref */
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_delayed_data_ref *ref;
|
|
|
|
|
|
|
|
ref = btrfs_delayed_node_to_data_ref(node);
|
2017-07-12 22:20:06 +00:00
|
|
|
|
2017-07-12 22:20:11 +00:00
|
|
|
ret = add_direct_ref(fs_info, preftrees, 0, ref->parent,
|
|
|
|
node->bytenr, count, sc,
|
|
|
|
GFP_ATOMIC);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
WARN_ON(1);
|
|
|
|
}
|
2017-07-12 22:20:10 +00:00
|
|
|
/*
|
|
|
|
* We must ignore BACKREF_FOUND_SHARED until all delayed
|
|
|
|
* refs have been checked.
|
|
|
|
*/
|
|
|
|
if (ret && (ret != BACKREF_FOUND_SHARED))
|
2014-01-23 14:21:38 +00:00
|
|
|
break;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
2017-07-12 22:20:10 +00:00
|
|
|
if (!ret)
|
|
|
|
ret = extent_is_shared(sc);
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
|
2014-01-23 14:21:38 +00:00
|
|
|
spin_unlock(&head->lock);
|
|
|
|
return ret;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* add all inline backrefs for bytenr to the list
|
2017-07-12 22:20:10 +00:00
|
|
|
*
|
|
|
|
* Returns 0 on success, <0 on error, or BACKREF_FOUND_SHARED.
|
2011-11-23 17:55:04 +00:00
|
|
|
*/
|
2017-07-12 22:20:08 +00:00
|
|
|
static int add_inline_refs(const struct btrfs_fs_info *fs_info,
|
|
|
|
struct btrfs_path *path, u64 bytenr,
|
2017-07-12 22:20:06 +00:00
|
|
|
int *info_level, struct preftrees *preftrees,
|
btrfs: backref, use correct count to resolve normal data refs
With the following patches:
- btrfs: backref, only collect file extent items matching backref offset
- btrfs: backref, not adding refs from shared block when resolving normal backref
- btrfs: backref, only search backref entries from leaves of the same root
we only collect the normal data refs we want, so the imprecise upper
bound total_refs of that EXTENT_ITEM could now be changed to the count
of the normal backref entry we want to search.
Background and how the patches fit together:
Btrfs has two types of data backref.
For BTRFS_EXTENT_DATA_REF_KEY type of backref, we don't have the
exact block number. Therefore, we need to call resolve_indirect_refs.
It uses btrfs_search_slot to locate the leaf block. Then
we need to walk through the leaves to search for the EXTENT_DATA items
that have disk bytenr matching the extent item (add_all_parents).
When resolving indirect refs, we could take entries that don't
belong to the backref entry we are searching for right now.
For that reason when searching backref entry, we always use total
refs of that EXTENT_ITEM rather than individual count.
For example:
item 11 key (40831553536 EXTENT_ITEM 4194304) itemoff 15460 itemsize
extent refs 24 gen 7302 flags DATA
shared data backref parent 394985472 count 10 #1
extent data backref root 257 objectid 260 offset 1048576 count 3 #2
extent data backref root 256 objectid 260 offset 65536 count 6 #3
extent data backref root 257 objectid 260 offset 65536 count 5 #4
For example, when searching backref entry #4, we'll use total_refs
24, a very loose loop ending condition, instead of total_refs = 5.
But using total_refs = 24 is not accurate. Sometimes, we'll never find
all the refs from specific root. As a result, the loop keeps on going
until we reach the end of that inode.
The first 3 patches, handle 3 different types refs we might encounter.
These refs do not belong to the normal backref we are searching, and
hence need to be skipped.
This patch changes the total_refs to correct number so that we could
end loop as soon as we find all the refs we want.
btrfs send uses backref to find possible clone sources, the following
is a simple test to compare the results with and without this patch:
$ btrfs subvolume create /sub1
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=64K count=1 seek=$((i-1)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot /sub1 /sub2
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=4K count=1 seek=$(((i-1)*16+10)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot -r /sub1 /snap1
$ time btrfs send /snap1 | btrfs receive /volume2
Without this patch:
real 69m48.124s
user 0m50.199s
sys 70m15.600s
With this patch:
real 1m59.683s
user 0m35.421s
sys 2m42.684s
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: ethanwu <ethanwu@synology.com>
[ add patchset cover letter with background and numbers ]
Signed-off-by: David Sterba <dsterba@suse.com>
2020-02-07 09:38:18 +00:00
|
|
|
struct share_check *sc)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
2012-01-26 20:01:11 +00:00
|
|
|
int ret = 0;
|
2011-11-23 17:55:04 +00:00
|
|
|
int slot;
|
|
|
|
struct extent_buffer *leaf;
|
|
|
|
struct btrfs_key key;
|
2013-06-28 17:11:22 +00:00
|
|
|
struct btrfs_key found_key;
|
2011-11-23 17:55:04 +00:00
|
|
|
unsigned long ptr;
|
|
|
|
unsigned long end;
|
|
|
|
struct btrfs_extent_item *ei;
|
|
|
|
u64 flags;
|
|
|
|
u64 item_size;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* enumerate all inline refs
|
|
|
|
*/
|
|
|
|
leaf = path->nodes[0];
|
2012-05-22 11:43:25 +00:00
|
|
|
slot = path->slots[0];
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2021-10-21 18:58:35 +00:00
|
|
|
item_size = btrfs_item_size(leaf, slot);
|
2011-11-23 17:55:04 +00:00
|
|
|
BUG_ON(item_size < sizeof(*ei));
|
|
|
|
|
|
|
|
ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
|
|
|
|
flags = btrfs_extent_flags(leaf, ei);
|
2013-06-28 17:11:22 +00:00
|
|
|
btrfs_item_key_to_cpu(leaf, &found_key, slot);
|
2011-11-23 17:55:04 +00:00
|
|
|
|
|
|
|
ptr = (unsigned long)(ei + 1);
|
|
|
|
end = (unsigned long)ei + item_size;
|
|
|
|
|
2013-06-28 17:11:22 +00:00
|
|
|
if (found_key.type == BTRFS_EXTENT_ITEM_KEY &&
|
|
|
|
flags & BTRFS_EXTENT_FLAG_TREE_BLOCK) {
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_tree_block_info *info;
|
|
|
|
|
|
|
|
info = (struct btrfs_tree_block_info *)ptr;
|
|
|
|
*info_level = btrfs_tree_block_level(leaf, info);
|
|
|
|
ptr += sizeof(struct btrfs_tree_block_info);
|
|
|
|
BUG_ON(ptr > end);
|
2013-06-28 17:11:22 +00:00
|
|
|
} else if (found_key.type == BTRFS_METADATA_ITEM_KEY) {
|
|
|
|
*info_level = found_key.offset;
|
2011-11-23 17:55:04 +00:00
|
|
|
} else {
|
|
|
|
BUG_ON(!(flags & BTRFS_EXTENT_FLAG_DATA));
|
|
|
|
}
|
|
|
|
|
|
|
|
while (ptr < end) {
|
|
|
|
struct btrfs_extent_inline_ref *iref;
|
|
|
|
u64 offset;
|
|
|
|
int type;
|
|
|
|
|
|
|
|
iref = (struct btrfs_extent_inline_ref *)ptr;
|
2017-08-18 21:15:19 +00:00
|
|
|
type = btrfs_get_extent_inline_ref_type(leaf, iref,
|
|
|
|
BTRFS_REF_TYPE_ANY);
|
|
|
|
if (type == BTRFS_REF_TYPE_INVALID)
|
2018-06-22 08:18:01 +00:00
|
|
|
return -EUCLEAN;
|
2017-08-18 21:15:19 +00:00
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
offset = btrfs_extent_inline_ref_offset(leaf, iref);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case BTRFS_SHARED_BLOCK_REF_KEY:
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_direct_ref(fs_info, preftrees,
|
|
|
|
*info_level + 1, offset,
|
2017-07-12 22:20:10 +00:00
|
|
|
bytenr, 1, NULL, GFP_NOFS);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
case BTRFS_SHARED_DATA_REF_KEY: {
|
|
|
|
struct btrfs_shared_data_ref *sdref;
|
|
|
|
int count;
|
|
|
|
|
|
|
|
sdref = (struct btrfs_shared_data_ref *)(iref + 1);
|
|
|
|
count = btrfs_shared_data_ref_count(leaf, sdref);
|
2017-07-12 22:20:06 +00:00
|
|
|
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_direct_ref(fs_info, preftrees, 0, offset,
|
2017-07-12 22:20:10 +00:00
|
|
|
bytenr, count, sc, GFP_NOFS);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case BTRFS_TREE_BLOCK_REF_KEY:
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_indirect_ref(fs_info, preftrees, offset,
|
|
|
|
NULL, *info_level + 1,
|
2017-07-12 22:20:10 +00:00
|
|
|
bytenr, 1, NULL, GFP_NOFS);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
case BTRFS_EXTENT_DATA_REF_KEY: {
|
|
|
|
struct btrfs_extent_data_ref *dref;
|
|
|
|
int count;
|
|
|
|
u64 root;
|
|
|
|
|
|
|
|
dref = (struct btrfs_extent_data_ref *)(&iref->offset);
|
|
|
|
count = btrfs_extent_data_ref_count(leaf, dref);
|
|
|
|
key.objectid = btrfs_extent_data_ref_objectid(leaf,
|
|
|
|
dref);
|
|
|
|
key.type = BTRFS_EXTENT_DATA_KEY;
|
|
|
|
key.offset = btrfs_extent_data_ref_offset(leaf, dref);
|
2014-09-10 20:20:45 +00:00
|
|
|
|
2022-10-11 12:17:00 +00:00
|
|
|
if (sc && key.objectid != sc->inum &&
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
!sc->have_delayed_delete_refs) {
|
2014-09-10 20:20:45 +00:00
|
|
|
ret = BACKREF_FOUND_SHARED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
root = btrfs_extent_data_ref_root(leaf, dref);
|
2017-07-12 22:20:06 +00:00
|
|
|
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_indirect_ref(fs_info, preftrees, root,
|
|
|
|
&key, 0, bytenr, count,
|
2017-07-12 22:20:10 +00:00
|
|
|
sc, GFP_NOFS);
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
WARN_ON(1);
|
|
|
|
}
|
2013-04-10 11:22:50 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2011-11-23 17:55:04 +00:00
|
|
|
ptr += btrfs_extent_inline_ref_size(type);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* add all non-inline backrefs for bytenr to the list
|
2017-07-12 22:20:10 +00:00
|
|
|
*
|
|
|
|
* Returns 0 on success, <0 on error, or BACKREF_FOUND_SHARED.
|
2011-11-23 17:55:04 +00:00
|
|
|
*/
|
2021-11-05 20:45:31 +00:00
|
|
|
static int add_keyed_refs(struct btrfs_root *extent_root,
|
2017-06-29 03:56:57 +00:00
|
|
|
struct btrfs_path *path, u64 bytenr,
|
2017-07-12 22:20:06 +00:00
|
|
|
int info_level, struct preftrees *preftrees,
|
2017-07-12 22:20:10 +00:00
|
|
|
struct share_check *sc)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
2021-11-05 20:45:31 +00:00
|
|
|
struct btrfs_fs_info *fs_info = extent_root->fs_info;
|
2011-11-23 17:55:04 +00:00
|
|
|
int ret;
|
|
|
|
int slot;
|
|
|
|
struct extent_buffer *leaf;
|
|
|
|
struct btrfs_key key;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
ret = btrfs_next_item(extent_root, path);
|
|
|
|
if (ret < 0)
|
|
|
|
break;
|
|
|
|
if (ret) {
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
slot = path->slots[0];
|
|
|
|
leaf = path->nodes[0];
|
|
|
|
btrfs_item_key_to_cpu(leaf, &key, slot);
|
|
|
|
|
|
|
|
if (key.objectid != bytenr)
|
|
|
|
break;
|
|
|
|
if (key.type < BTRFS_TREE_BLOCK_REF_KEY)
|
|
|
|
continue;
|
|
|
|
if (key.type > BTRFS_SHARED_DATA_REF_KEY)
|
|
|
|
break;
|
|
|
|
|
|
|
|
switch (key.type) {
|
|
|
|
case BTRFS_SHARED_BLOCK_REF_KEY:
|
2017-07-12 22:20:06 +00:00
|
|
|
/* SHARED DIRECT METADATA backref */
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_direct_ref(fs_info, preftrees,
|
|
|
|
info_level + 1, key.offset,
|
2017-07-12 22:20:10 +00:00
|
|
|
bytenr, 1, NULL, GFP_NOFS);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
case BTRFS_SHARED_DATA_REF_KEY: {
|
2017-07-12 22:20:06 +00:00
|
|
|
/* SHARED DIRECT FULL backref */
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_shared_data_ref *sdref;
|
|
|
|
int count;
|
|
|
|
|
|
|
|
sdref = btrfs_item_ptr(leaf, slot,
|
|
|
|
struct btrfs_shared_data_ref);
|
|
|
|
count = btrfs_shared_data_ref_count(leaf, sdref);
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_direct_ref(fs_info, preftrees, 0,
|
|
|
|
key.offset, bytenr, count,
|
2017-07-12 22:20:10 +00:00
|
|
|
sc, GFP_NOFS);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case BTRFS_TREE_BLOCK_REF_KEY:
|
2017-07-12 22:20:06 +00:00
|
|
|
/* NORMAL INDIRECT METADATA backref */
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_indirect_ref(fs_info, preftrees, key.offset,
|
|
|
|
NULL, info_level + 1, bytenr,
|
2017-07-12 22:20:10 +00:00
|
|
|
1, NULL, GFP_NOFS);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
case BTRFS_EXTENT_DATA_REF_KEY: {
|
2017-07-12 22:20:06 +00:00
|
|
|
/* NORMAL INDIRECT DATA backref */
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_extent_data_ref *dref;
|
|
|
|
int count;
|
|
|
|
u64 root;
|
|
|
|
|
|
|
|
dref = btrfs_item_ptr(leaf, slot,
|
|
|
|
struct btrfs_extent_data_ref);
|
|
|
|
count = btrfs_extent_data_ref_count(leaf, dref);
|
|
|
|
key.objectid = btrfs_extent_data_ref_objectid(leaf,
|
|
|
|
dref);
|
|
|
|
key.type = BTRFS_EXTENT_DATA_KEY;
|
|
|
|
key.offset = btrfs_extent_data_ref_offset(leaf, dref);
|
2014-09-10 20:20:45 +00:00
|
|
|
|
2022-10-11 12:17:00 +00:00
|
|
|
if (sc && key.objectid != sc->inum &&
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
!sc->have_delayed_delete_refs) {
|
2014-09-10 20:20:45 +00:00
|
|
|
ret = BACKREF_FOUND_SHARED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
root = btrfs_extent_data_ref_root(leaf, dref);
|
2017-07-12 22:20:08 +00:00
|
|
|
ret = add_indirect_ref(fs_info, preftrees, root,
|
|
|
|
&key, 0, bytenr, count,
|
2017-07-12 22:20:10 +00:00
|
|
|
sc, GFP_NOFS);
|
2011-11-23 17:55:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
WARN_ON(1);
|
|
|
|
}
|
2013-04-10 11:22:50 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-10-11 12:17:07 +00:00
|
|
|
/*
|
|
|
|
* The caller has joined a transaction or is holding a read lock on the
|
|
|
|
* fs_info->commit_root_sem semaphore, so no need to worry about the root's last
|
|
|
|
* snapshot field changing while updating or checking the cache.
|
|
|
|
*/
|
|
|
|
static bool lookup_backref_shared_cache(struct btrfs_backref_share_check_ctx *ctx,
|
|
|
|
struct btrfs_root *root,
|
|
|
|
u64 bytenr, int level, bool *is_shared)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_shared_cache_entry *entry;
|
|
|
|
|
|
|
|
if (!ctx->use_path_cache)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (WARN_ON_ONCE(level >= BTRFS_MAX_LEVEL))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Level -1 is used for the data extent, which is not reliable to cache
|
|
|
|
* because its reference count can increase or decrease without us
|
|
|
|
* realizing. We cache results only for extent buffers that lead from
|
|
|
|
* the root node down to the leaf with the file extent item.
|
|
|
|
*/
|
|
|
|
ASSERT(level >= 0);
|
|
|
|
|
|
|
|
entry = &ctx->path_cache_entries[level];
|
|
|
|
|
|
|
|
/* Unused cache entry or being used for some other extent buffer. */
|
|
|
|
if (entry->bytenr != bytenr)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We cached a false result, but the last snapshot generation of the
|
|
|
|
* root changed, so we now have a snapshot. Don't trust the result.
|
|
|
|
*/
|
|
|
|
if (!entry->is_shared &&
|
|
|
|
entry->gen != btrfs_root_last_snapshot(&root->root_item))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we cached a true result and the last generation used for dropping
|
|
|
|
* a root changed, we can not trust the result, because the dropped root
|
|
|
|
* could be a snapshot sharing this extent buffer.
|
|
|
|
*/
|
|
|
|
if (entry->is_shared &&
|
|
|
|
entry->gen != btrfs_get_last_root_drop_gen(root->fs_info))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
*is_shared = entry->is_shared;
|
|
|
|
/*
|
|
|
|
* If the node at this level is shared, than all nodes below are also
|
|
|
|
* shared. Currently some of the nodes below may be marked as not shared
|
|
|
|
* because we have just switched from one leaf to another, and switched
|
|
|
|
* also other nodes above the leaf and below the current level, so mark
|
|
|
|
* them as shared.
|
|
|
|
*/
|
|
|
|
if (*is_shared) {
|
|
|
|
for (int i = 0; i < level; i++) {
|
|
|
|
ctx->path_cache_entries[i].is_shared = true;
|
|
|
|
ctx->path_cache_entries[i].gen = entry->gen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The caller has joined a transaction or is holding a read lock on the
|
|
|
|
* fs_info->commit_root_sem semaphore, so no need to worry about the root's last
|
|
|
|
* snapshot field changing while updating or checking the cache.
|
|
|
|
*/
|
|
|
|
static void store_backref_shared_cache(struct btrfs_backref_share_check_ctx *ctx,
|
|
|
|
struct btrfs_root *root,
|
|
|
|
u64 bytenr, int level, bool is_shared)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_shared_cache_entry *entry;
|
|
|
|
u64 gen;
|
|
|
|
|
|
|
|
if (!ctx->use_path_cache)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (WARN_ON_ONCE(level >= BTRFS_MAX_LEVEL))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Level -1 is used for the data extent, which is not reliable to cache
|
|
|
|
* because its reference count can increase or decrease without us
|
|
|
|
* realizing. We cache results only for extent buffers that lead from
|
|
|
|
* the root node down to the leaf with the file extent item.
|
|
|
|
*/
|
|
|
|
ASSERT(level >= 0);
|
|
|
|
|
|
|
|
if (is_shared)
|
|
|
|
gen = btrfs_get_last_root_drop_gen(root->fs_info);
|
|
|
|
else
|
|
|
|
gen = btrfs_root_last_snapshot(&root->root_item);
|
|
|
|
|
|
|
|
entry = &ctx->path_cache_entries[level];
|
|
|
|
entry->bytenr = bytenr;
|
|
|
|
entry->is_shared = is_shared;
|
|
|
|
entry->gen = gen;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we found an extent buffer is shared, set the cache result for all
|
|
|
|
* extent buffers below it to true. As nodes in the path are COWed,
|
|
|
|
* their sharedness is moved to their children, and if a leaf is COWed,
|
|
|
|
* then the sharedness of a data extent becomes direct, the refcount of
|
|
|
|
* data extent is increased in the extent item at the extent tree.
|
|
|
|
*/
|
|
|
|
if (is_shared) {
|
|
|
|
for (int i = 0; i < level; i++) {
|
|
|
|
entry = &ctx->path_cache_entries[i];
|
|
|
|
entry->is_shared = is_shared;
|
|
|
|
entry->gen = gen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
/*
|
|
|
|
* this adds all existing backrefs (inline backrefs, backrefs and delayed
|
|
|
|
* refs) for the given bytenr to the refs list, merges duplicates and resolves
|
|
|
|
* indirect refs to their parent bytenr.
|
|
|
|
* When roots are found, they're added to the roots list
|
|
|
|
*
|
2022-11-01 16:15:47 +00:00
|
|
|
* @ctx: Backref walking context object, must be not NULL.
|
|
|
|
* @sc: If !NULL, then immediately return BACKREF_FOUND_SHARED when a
|
|
|
|
* shared extent is detected.
|
2017-07-12 22:20:10 +00:00
|
|
|
*
|
|
|
|
* Otherwise this returns 0 for success and <0 for an error.
|
|
|
|
*
|
2011-11-23 17:55:04 +00:00
|
|
|
* FIXME some caching might speed things up
|
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
static int find_parent_nodes(struct btrfs_backref_walk_ctx *ctx,
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
struct share_check *sc)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
2022-11-01 16:15:47 +00:00
|
|
|
struct btrfs_root *root = btrfs_extent_root(ctx->fs_info, ctx->bytenr);
|
2011-11-23 17:55:04 +00:00
|
|
|
struct btrfs_key key;
|
|
|
|
struct btrfs_path *path;
|
|
|
|
struct btrfs_delayed_ref_root *delayed_refs = NULL;
|
2012-03-03 12:41:15 +00:00
|
|
|
struct btrfs_delayed_ref_head *head;
|
2011-11-23 17:55:04 +00:00
|
|
|
int info_level = 0;
|
|
|
|
int ret;
|
2017-06-29 03:56:57 +00:00
|
|
|
struct prelim_ref *ref;
|
2017-07-12 22:20:06 +00:00
|
|
|
struct rb_node *node;
|
2014-01-28 11:13:38 +00:00
|
|
|
struct extent_inode_elem *eie = NULL;
|
2017-07-12 22:20:06 +00:00
|
|
|
struct preftrees preftrees = {
|
|
|
|
.direct = PREFTREE_INIT,
|
|
|
|
.indirect = PREFTREE_INIT,
|
|
|
|
.indirect_missing_keys = PREFTREE_INIT
|
|
|
|
};
|
2011-11-23 17:55:04 +00:00
|
|
|
|
btrfs: remove useless logic when finding parent nodes
At find_parent_nodes(), at its last step, when iterating over all direct
references, we are checking if we have a share context and if we have
a reference with a different root from the one in the share context.
However that logic is pointless because of two reasons:
1) After the previous patch in the series (subject "btrfs: remove roots
ulist when checking data extent sharedness"), the roots argument is
always NULL when using a share check context (struct share_check), so
this code is never triggered;
2) Even before that previous patch, we could not hit this code because
if we had a reference with a root different from the one in our share
context, then we would have exited earlier when doing either of the
following:
- Adding a second direct ref to the direct refs red black tree
resulted in extent_is_shared() returning true when called from
add_direct_ref() -> add_prelim_ref(), after processing delayed
references or while processing references in the extent tree;
- When adding a second reference to the indirect refs red black
tree (same as above, extent_is_shared() returns true);
- If we only have one indirect reference and no direct references,
then when resolving it at resolve_indirect_refs() we immediately
return that the target extent is shared, therefore never reaching
that loop that iterates over all direct references at
find_parent_nodes();
- If we have 1 indirect reference and 1 direct reference, then we
also exit early because extent_is_shared() ends up returning true
when called through add_prelim_ref() (by add_direct_ref() or
add_indirect_ref()) or add_delayed_refs(). Same applies as when
having a combination of direct, indirect and indirect with missing
key references.
This logic had been obsoleted since commit 3ec4d3238ab165 ("btrfs:
allow backref search checks for shared extents"), which introduced the
early exits in case an extent is shared.
So just remove that logic, and assert at find_parent_nodes() that when we
have a share context we don't have a roots ulist and that we haven't found
the extent to be directly shared after processing delayed references and
all references from the extent tree.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:05 +00:00
|
|
|
/* Roots ulist is not needed when using a sharedness check context. */
|
|
|
|
if (sc)
|
2022-11-01 16:15:47 +00:00
|
|
|
ASSERT(ctx->roots == NULL);
|
btrfs: remove useless logic when finding parent nodes
At find_parent_nodes(), at its last step, when iterating over all direct
references, we are checking if we have a share context and if we have
a reference with a different root from the one in the share context.
However that logic is pointless because of two reasons:
1) After the previous patch in the series (subject "btrfs: remove roots
ulist when checking data extent sharedness"), the roots argument is
always NULL when using a share check context (struct share_check), so
this code is never triggered;
2) Even before that previous patch, we could not hit this code because
if we had a reference with a root different from the one in our share
context, then we would have exited earlier when doing either of the
following:
- Adding a second direct ref to the direct refs red black tree
resulted in extent_is_shared() returning true when called from
add_direct_ref() -> add_prelim_ref(), after processing delayed
references or while processing references in the extent tree;
- When adding a second reference to the indirect refs red black
tree (same as above, extent_is_shared() returns true);
- If we only have one indirect reference and no direct references,
then when resolving it at resolve_indirect_refs() we immediately
return that the target extent is shared, therefore never reaching
that loop that iterates over all direct references at
find_parent_nodes();
- If we have 1 indirect reference and 1 direct reference, then we
also exit early because extent_is_shared() ends up returning true
when called through add_prelim_ref() (by add_direct_ref() or
add_indirect_ref()) or add_delayed_refs(). Same applies as when
having a combination of direct, indirect and indirect with missing
key references.
This logic had been obsoleted since commit 3ec4d3238ab165 ("btrfs:
allow backref search checks for shared extents"), which introduced the
early exits in case an extent is shared.
So just remove that logic, and assert at find_parent_nodes() that when we
have a share context we don't have a roots ulist and that we haven't found
the extent to be directly shared after processing delayed references and
all references from the extent tree.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:05 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
key.objectid = ctx->bytenr;
|
2011-11-23 17:55:04 +00:00
|
|
|
key.offset = (u64)-1;
|
2022-11-01 16:15:47 +00:00
|
|
|
if (btrfs_fs_incompat(ctx->fs_info, SKINNY_METADATA))
|
2013-06-28 17:11:22 +00:00
|
|
|
key.type = BTRFS_METADATA_ITEM_KEY;
|
|
|
|
else
|
|
|
|
key.type = BTRFS_EXTENT_ITEM_KEY;
|
2011-11-23 17:55:04 +00:00
|
|
|
|
|
|
|
path = btrfs_alloc_path();
|
|
|
|
if (!path)
|
|
|
|
return -ENOMEM;
|
2022-11-01 16:15:47 +00:00
|
|
|
if (!ctx->trans) {
|
2013-06-12 20:20:08 +00:00
|
|
|
path->search_commit_root = 1;
|
2014-02-13 03:19:47 +00:00
|
|
|
path->skip_locking = 1;
|
|
|
|
}
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->time_seq == BTRFS_SEQ_LAST)
|
2015-04-16 06:54:50 +00:00
|
|
|
path->skip_locking = 1;
|
|
|
|
|
2011-11-23 17:55:04 +00:00
|
|
|
again:
|
2012-03-03 12:41:15 +00:00
|
|
|
head = NULL;
|
|
|
|
|
2021-11-05 20:45:31 +00:00
|
|
|
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
2021-11-05 20:45:34 +00:00
|
|
|
if (ret == 0) {
|
|
|
|
/* This shouldn't happen, indicates a bug or fs corruption. */
|
|
|
|
ASSERT(ret != 0);
|
|
|
|
ret = -EUCLEAN;
|
|
|
|
goto out;
|
|
|
|
}
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->trans && likely(ctx->trans->type != __TRANS_DUMMY) &&
|
|
|
|
ctx->time_seq != BTRFS_SEQ_LAST) {
|
2012-03-23 16:32:28 +00:00
|
|
|
/*
|
2021-11-05 20:45:32 +00:00
|
|
|
* We have a specific time_seq we care about and trans which
|
|
|
|
* means we have the path lock, we need to grab the ref head and
|
|
|
|
* lock it so we have a consistent view of the refs at the given
|
|
|
|
* time.
|
2012-03-23 16:32:28 +00:00
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
delayed_refs = &ctx->trans->transaction->delayed_refs;
|
2012-03-23 16:32:28 +00:00
|
|
|
spin_lock(&delayed_refs->lock);
|
2022-11-01 16:15:47 +00:00
|
|
|
head = btrfs_find_delayed_ref_head(delayed_refs, ctx->bytenr);
|
2012-03-23 16:32:28 +00:00
|
|
|
if (head) {
|
|
|
|
if (!mutex_trylock(&head->mutex)) {
|
2017-09-29 19:43:57 +00:00
|
|
|
refcount_inc(&head->refs);
|
2012-03-23 16:32:28 +00:00
|
|
|
spin_unlock(&delayed_refs->lock);
|
|
|
|
|
|
|
|
btrfs_release_path(path);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Mutex was contended, block until it's
|
|
|
|
* released and try again
|
|
|
|
*/
|
|
|
|
mutex_lock(&head->mutex);
|
|
|
|
mutex_unlock(&head->mutex);
|
2017-09-29 19:43:57 +00:00
|
|
|
btrfs_put_delayed_ref_head(head);
|
2012-03-23 16:32:28 +00:00
|
|
|
goto again;
|
|
|
|
}
|
2014-01-23 14:21:38 +00:00
|
|
|
spin_unlock(&delayed_refs->lock);
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = add_delayed_refs(ctx->fs_info, head, ctx->time_seq,
|
btrfs: backref, use correct count to resolve normal data refs
With the following patches:
- btrfs: backref, only collect file extent items matching backref offset
- btrfs: backref, not adding refs from shared block when resolving normal backref
- btrfs: backref, only search backref entries from leaves of the same root
we only collect the normal data refs we want, so the imprecise upper
bound total_refs of that EXTENT_ITEM could now be changed to the count
of the normal backref entry we want to search.
Background and how the patches fit together:
Btrfs has two types of data backref.
For BTRFS_EXTENT_DATA_REF_KEY type of backref, we don't have the
exact block number. Therefore, we need to call resolve_indirect_refs.
It uses btrfs_search_slot to locate the leaf block. Then
we need to walk through the leaves to search for the EXTENT_DATA items
that have disk bytenr matching the extent item (add_all_parents).
When resolving indirect refs, we could take entries that don't
belong to the backref entry we are searching for right now.
For that reason when searching backref entry, we always use total
refs of that EXTENT_ITEM rather than individual count.
For example:
item 11 key (40831553536 EXTENT_ITEM 4194304) itemoff 15460 itemsize
extent refs 24 gen 7302 flags DATA
shared data backref parent 394985472 count 10 #1
extent data backref root 257 objectid 260 offset 1048576 count 3 #2
extent data backref root 256 objectid 260 offset 65536 count 6 #3
extent data backref root 257 objectid 260 offset 65536 count 5 #4
For example, when searching backref entry #4, we'll use total_refs
24, a very loose loop ending condition, instead of total_refs = 5.
But using total_refs = 24 is not accurate. Sometimes, we'll never find
all the refs from specific root. As a result, the loop keeps on going
until we reach the end of that inode.
The first 3 patches, handle 3 different types refs we might encounter.
These refs do not belong to the normal backref we are searching, and
hence need to be skipped.
This patch changes the total_refs to correct number so that we could
end loop as soon as we find all the refs we want.
btrfs send uses backref to find possible clone sources, the following
is a simple test to compare the results with and without this patch:
$ btrfs subvolume create /sub1
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=64K count=1 seek=$((i-1)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot /sub1 /sub2
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=4K count=1 seek=$(((i-1)*16+10)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot -r /sub1 /snap1
$ time btrfs send /snap1 | btrfs receive /volume2
Without this patch:
real 69m48.124s
user 0m50.199s
sys 70m15.600s
With this patch:
real 1m59.683s
user 0m35.421s
sys 2m42.684s
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: ethanwu <ethanwu@synology.com>
[ add patchset cover letter with background and numbers ]
Signed-off-by: David Sterba <dsterba@suse.com>
2020-02-07 09:38:18 +00:00
|
|
|
&preftrees, sc);
|
2012-06-22 12:01:00 +00:00
|
|
|
mutex_unlock(&head->mutex);
|
2014-01-23 14:21:38 +00:00
|
|
|
if (ret)
|
2012-03-23 16:32:28 +00:00
|
|
|
goto out;
|
2014-01-23 14:21:38 +00:00
|
|
|
} else {
|
|
|
|
spin_unlock(&delayed_refs->lock);
|
2012-03-03 12:41:15 +00:00
|
|
|
}
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (path->slots[0]) {
|
|
|
|
struct extent_buffer *leaf;
|
|
|
|
int slot;
|
|
|
|
|
2012-05-22 11:43:25 +00:00
|
|
|
path->slots[0]--;
|
2011-11-23 17:55:04 +00:00
|
|
|
leaf = path->nodes[0];
|
2012-05-22 11:43:25 +00:00
|
|
|
slot = path->slots[0];
|
2011-11-23 17:55:04 +00:00
|
|
|
btrfs_item_key_to_cpu(leaf, &key, slot);
|
2022-11-01 16:15:47 +00:00
|
|
|
if (key.objectid == ctx->bytenr &&
|
2013-06-28 17:11:22 +00:00
|
|
|
(key.type == BTRFS_EXTENT_ITEM_KEY ||
|
|
|
|
key.type == BTRFS_METADATA_ITEM_KEY)) {
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = add_inline_refs(ctx->fs_info, path, ctx->bytenr,
|
btrfs: backref, use correct count to resolve normal data refs
With the following patches:
- btrfs: backref, only collect file extent items matching backref offset
- btrfs: backref, not adding refs from shared block when resolving normal backref
- btrfs: backref, only search backref entries from leaves of the same root
we only collect the normal data refs we want, so the imprecise upper
bound total_refs of that EXTENT_ITEM could now be changed to the count
of the normal backref entry we want to search.
Background and how the patches fit together:
Btrfs has two types of data backref.
For BTRFS_EXTENT_DATA_REF_KEY type of backref, we don't have the
exact block number. Therefore, we need to call resolve_indirect_refs.
It uses btrfs_search_slot to locate the leaf block. Then
we need to walk through the leaves to search for the EXTENT_DATA items
that have disk bytenr matching the extent item (add_all_parents).
When resolving indirect refs, we could take entries that don't
belong to the backref entry we are searching for right now.
For that reason when searching backref entry, we always use total
refs of that EXTENT_ITEM rather than individual count.
For example:
item 11 key (40831553536 EXTENT_ITEM 4194304) itemoff 15460 itemsize
extent refs 24 gen 7302 flags DATA
shared data backref parent 394985472 count 10 #1
extent data backref root 257 objectid 260 offset 1048576 count 3 #2
extent data backref root 256 objectid 260 offset 65536 count 6 #3
extent data backref root 257 objectid 260 offset 65536 count 5 #4
For example, when searching backref entry #4, we'll use total_refs
24, a very loose loop ending condition, instead of total_refs = 5.
But using total_refs = 24 is not accurate. Sometimes, we'll never find
all the refs from specific root. As a result, the loop keeps on going
until we reach the end of that inode.
The first 3 patches, handle 3 different types refs we might encounter.
These refs do not belong to the normal backref we are searching, and
hence need to be skipped.
This patch changes the total_refs to correct number so that we could
end loop as soon as we find all the refs we want.
btrfs send uses backref to find possible clone sources, the following
is a simple test to compare the results with and without this patch:
$ btrfs subvolume create /sub1
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=64K count=1 seek=$((i-1)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot /sub1 /sub2
$ for i in `seq 1 163840`; do
dd if=/dev/zero of=/sub1/file bs=4K count=1 seek=$(((i-1)*16+10)) conv=notrunc oflag=direct
done
$ btrfs subvolume snapshot -r /sub1 /snap1
$ time btrfs send /snap1 | btrfs receive /volume2
Without this patch:
real 69m48.124s
user 0m50.199s
sys 70m15.600s
With this patch:
real 1m59.683s
user 0m35.421s
sys 2m42.684s
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: ethanwu <ethanwu@synology.com>
[ add patchset cover letter with background and numbers ]
Signed-off-by: David Sterba <dsterba@suse.com>
2020-02-07 09:38:18 +00:00
|
|
|
&info_level, &preftrees, sc);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = add_keyed_refs(root, path, ctx->bytenr, info_level,
|
2017-07-12 22:20:10 +00:00
|
|
|
&preftrees, sc);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
btrfs: remove useless logic when finding parent nodes
At find_parent_nodes(), at its last step, when iterating over all direct
references, we are checking if we have a share context and if we have
a reference with a different root from the one in the share context.
However that logic is pointless because of two reasons:
1) After the previous patch in the series (subject "btrfs: remove roots
ulist when checking data extent sharedness"), the roots argument is
always NULL when using a share check context (struct share_check), so
this code is never triggered;
2) Even before that previous patch, we could not hit this code because
if we had a reference with a root different from the one in our share
context, then we would have exited earlier when doing either of the
following:
- Adding a second direct ref to the direct refs red black tree
resulted in extent_is_shared() returning true when called from
add_direct_ref() -> add_prelim_ref(), after processing delayed
references or while processing references in the extent tree;
- When adding a second reference to the indirect refs red black
tree (same as above, extent_is_shared() returns true);
- If we only have one indirect reference and no direct references,
then when resolving it at resolve_indirect_refs() we immediately
return that the target extent is shared, therefore never reaching
that loop that iterates over all direct references at
find_parent_nodes();
- If we have 1 indirect reference and 1 direct reference, then we
also exit early because extent_is_shared() ends up returning true
when called through add_prelim_ref() (by add_direct_ref() or
add_indirect_ref()) or add_delayed_refs(). Same applies as when
having a combination of direct, indirect and indirect with missing
key references.
This logic had been obsoleted since commit 3ec4d3238ab165 ("btrfs:
allow backref search checks for shared extents"), which introduced the
early exits in case an extent is shared.
So just remove that logic, and assert at find_parent_nodes() that when we
have a share context we don't have a roots ulist and that we haven't found
the extent to be directly shared after processing delayed references and
all references from the extent tree.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:05 +00:00
|
|
|
/*
|
|
|
|
* If we have a share context and we reached here, it means the extent
|
|
|
|
* is not directly shared (no multiple reference items for it),
|
|
|
|
* otherwise we would have exited earlier with a return value of
|
|
|
|
* BACKREF_FOUND_SHARED after processing delayed references or while
|
|
|
|
* processing inline or keyed references from the extent tree.
|
|
|
|
* The extent may however be indirectly shared through shared subtrees
|
|
|
|
* as a result from creating snapshots, so we determine below what is
|
|
|
|
* its parent node, in case we are dealing with a metadata extent, or
|
|
|
|
* what's the leaf (or leaves), from a fs tree, that has a file extent
|
|
|
|
* item pointing to it in case we are dealing with a data extent.
|
|
|
|
*/
|
|
|
|
ASSERT(extent_is_shared(sc) == 0);
|
|
|
|
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
/*
|
|
|
|
* If we are here for a data extent and we have a share_check structure
|
|
|
|
* it means the data extent is not directly shared (does not have
|
|
|
|
* multiple reference items), so we have to check if a path in the fs
|
|
|
|
* tree (going from the root node down to the leaf that has the file
|
|
|
|
* extent item pointing to the data extent) is shared, that is, if any
|
|
|
|
* of the extent buffers in the path is referenced by other trees.
|
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
if (sc && ctx->bytenr == sc->data_bytenr) {
|
btrfs: avoid unnecessary resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
However when the generation of the data extent is more recent than the
last generation used to snapshot the root, we don't need to determine
the path, since the data extent can not be shared through snapshots.
For this case we currently still determine the leaf of that path (at
find_parent_nodes(), but then stop determining the other nodes in the
path (at btrfs_is_data_extent_shared()) as it's pointless.
So do the check of the data extent's generation earlier, at
find_parent_nodes(), before trying to resolve the indirect reference to
determine the leaf in the path. This saves us from doing one expensive
b+tree search in the fs tree of our target inode, as well as other minor
work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-fiemap.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1285 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 742 milliseconds (metadata cached)
After applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 689 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 393 milliseconds (metadata cached)
That's a -46.4% total reduction for the metadata not cached case, and
a -47.0% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:09 +00:00
|
|
|
/*
|
|
|
|
* If our data extent is from a generation more recent than the
|
|
|
|
* last generation used to snapshot the root, then we know that
|
|
|
|
* it can not be shared through subtrees, so we can skip
|
|
|
|
* resolving indirect references, there's no point in
|
|
|
|
* determining the extent buffers for the path from the fs tree
|
|
|
|
* root node down to the leaf that has the file extent item that
|
|
|
|
* points to the data extent.
|
|
|
|
*/
|
|
|
|
if (sc->data_extent_gen >
|
|
|
|
btrfs_root_last_snapshot(&sc->root->root_item)) {
|
|
|
|
ret = BACKREF_FOUND_NOT_SHARED;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
/*
|
|
|
|
* If we are only determining if a data extent is shared or not
|
|
|
|
* and the corresponding file extent item is located in the same
|
|
|
|
* leaf as the previous file extent item, we can skip resolving
|
|
|
|
* indirect references for a data extent, since the fs tree path
|
|
|
|
* is the same (same leaf, so same path). We skip as long as the
|
|
|
|
* cached result for the leaf is valid and only if there's only
|
|
|
|
* one file extent item pointing to the data extent, because in
|
|
|
|
* the case of multiple file extent items, they may be located
|
|
|
|
* in different leaves and therefore we have multiple paths.
|
|
|
|
*/
|
|
|
|
if (sc->ctx->curr_leaf_bytenr == sc->ctx->prev_leaf_bytenr &&
|
|
|
|
sc->self_ref_count == 1) {
|
|
|
|
bool cached;
|
|
|
|
bool is_shared;
|
|
|
|
|
|
|
|
cached = lookup_backref_shared_cache(sc->ctx, sc->root,
|
|
|
|
sc->ctx->curr_leaf_bytenr,
|
|
|
|
0, &is_shared);
|
|
|
|
if (cached) {
|
|
|
|
if (is_shared)
|
|
|
|
ret = BACKREF_FOUND_SHARED;
|
|
|
|
else
|
|
|
|
ret = BACKREF_FOUND_NOT_SHARED;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-12 22:20:06 +00:00
|
|
|
btrfs_release_path(path);
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = add_missing_keys(ctx->fs_info, &preftrees, path->skip_locking == 0);
|
2012-05-15 15:55:51 +00:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2018-08-22 19:51:53 +00:00
|
|
|
WARN_ON(!RB_EMPTY_ROOT(&preftrees.indirect_missing_keys.root.rb_root));
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = resolve_indirect_refs(ctx, path, &preftrees, sc);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2018-08-22 19:51:53 +00:00
|
|
|
WARN_ON(!RB_EMPTY_ROOT(&preftrees.indirect.root.rb_root));
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2017-07-12 22:20:06 +00:00
|
|
|
/*
|
|
|
|
* This walks the tree of merged and resolved refs. Tree blocks are
|
|
|
|
* read in as needed. Unique entries are added to the ulist, and
|
|
|
|
* the list of found roots is updated.
|
|
|
|
*
|
|
|
|
* We release the entire tree in one go before returning.
|
|
|
|
*/
|
2018-08-22 19:51:53 +00:00
|
|
|
node = rb_first_cached(&preftrees.direct.root);
|
2017-07-12 22:20:06 +00:00
|
|
|
while (node) {
|
|
|
|
ref = rb_entry(node, struct prelim_ref, rbnode);
|
|
|
|
node = rb_next(&ref->rbnode);
|
2018-01-24 03:22:09 +00:00
|
|
|
/*
|
|
|
|
* ref->count < 0 can happen here if there are delayed
|
|
|
|
* refs with a node->action of BTRFS_DROP_DELAYED_REF.
|
|
|
|
* prelim_ref_insert() relies on this when merging
|
|
|
|
* identical refs to keep the overall count correct.
|
|
|
|
* prelim_ref_insert() will merge only those refs
|
|
|
|
* which compare identically. Any refs having
|
|
|
|
* e.g. different offsets would not be merged,
|
|
|
|
* and would retain their original ref->count < 0.
|
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->roots && ref->count && ref->root_id && ref->parent == 0) {
|
2011-11-23 17:55:04 +00:00
|
|
|
/* no parent == root of tree */
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = ulist_add(ctx->roots, ref->root_id, 0, GFP_NOFS);
|
2013-03-29 23:03:21 +00:00
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
if (ref->count && ref->parent) {
|
2022-11-01 16:15:47 +00:00
|
|
|
if (!ctx->ignore_extent_item_pos && !ref->inode_list &&
|
|
|
|
ref->level == 0) {
|
2012-05-17 14:43:03 +00:00
|
|
|
struct extent_buffer *eb;
|
2014-06-04 17:22:26 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
eb = read_tree_block(ctx->fs_info, ref->parent, 0,
|
2020-11-05 15:45:18 +00:00
|
|
|
0, ref->level, NULL);
|
2015-05-25 09:30:15 +00:00
|
|
|
if (IS_ERR(eb)) {
|
|
|
|
ret = PTR_ERR(eb);
|
|
|
|
goto out;
|
2022-02-22 07:41:19 +00:00
|
|
|
}
|
|
|
|
if (!extent_buffer_uptodate(eb)) {
|
2013-04-23 18:17:42 +00:00
|
|
|
free_extent_buffer(eb);
|
2013-05-08 08:10:25 +00:00
|
|
|
ret = -EIO;
|
|
|
|
goto out;
|
2013-04-23 18:17:42 +00:00
|
|
|
}
|
btrfs: honor path->skip_locking in backref code
Qgroups will do the old roots lookup at delayed ref time, which could be
while walking down the extent root while running a delayed ref. This
should be fine, except we specifically lock eb's in the backref walking
code irrespective of path->skip_locking, which deadlocks the system.
Fix up the backref code to honor path->skip_locking, nobody will be
modifying the commit_root when we're searching so it's completely safe
to do.
This happens since fb235dc06fac ("btrfs: qgroup: Move half of the qgroup
accounting time out of commit trans"), kernel may lockup with quota
enabled.
There is one backref trace triggered by snapshot dropping along with
write operation in the source subvolume. The example can be reliably
reproduced:
btrfs-cleaner D 0 4062 2 0x80000000
Call Trace:
schedule+0x32/0x90
btrfs_tree_read_lock+0x93/0x130 [btrfs]
find_parent_nodes+0x29b/0x1170 [btrfs]
btrfs_find_all_roots_safe+0xa8/0x120 [btrfs]
btrfs_find_all_roots+0x57/0x70 [btrfs]
btrfs_qgroup_trace_extent_post+0x37/0x70 [btrfs]
btrfs_qgroup_trace_leaf_items+0x10b/0x140 [btrfs]
btrfs_qgroup_trace_subtree+0xc8/0xe0 [btrfs]
do_walk_down+0x541/0x5e3 [btrfs]
walk_down_tree+0xab/0xe7 [btrfs]
btrfs_drop_snapshot+0x356/0x71a [btrfs]
btrfs_clean_one_deleted_snapshot+0xb8/0xf0 [btrfs]
cleaner_kthread+0x12b/0x160 [btrfs]
kthread+0x112/0x130
ret_from_fork+0x27/0x50
When dropping snapshots with qgroup enabled, we will trigger backref
walk.
However such backref walk at that timing is pretty dangerous, as if one
of the parent nodes get WRITE locked by other thread, we could cause a
dead lock.
For example:
FS 260 FS 261 (Dropped)
node A node B
/ \ / \
node C node D node E
/ \ / \ / \
leaf F|leaf G|leaf H|leaf I|leaf J|leaf K
The lock sequence would be:
Thread A (cleaner) | Thread B (other writer)
-----------------------------------------------------------------------
write_lock(B) |
write_lock(D) |
^^^ called by walk_down_tree() |
| write_lock(A)
| write_lock(D) << Stall
read_lock(H) << for backref walk |
read_lock(D) << lock owner is |
the same thread A |
so read lock is OK |
read_lock(A) << Stall |
So thread A hold write lock D, and needs read lock A to unlock.
While thread B holds write lock A, while needs lock D to unlock.
This will cause a deadlock.
This is not only limited to snapshot dropping case. As the backref
walk, even only happens on commit trees, is breaking the normal top-down
locking order, makes it deadlock prone.
Fixes: fb235dc06fac ("btrfs: qgroup: Move half of the qgroup accounting time out of commit trans")
CC: stable@vger.kernel.org # 4.14+
Reported-and-tested-by: David Sterba <dsterba@suse.com>
Reported-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
[ rebase to latest branch and fix lock assert bug in btrfs/007 ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
[ copy logs and deadlock analysis from Qu's patch ]
Signed-off-by: David Sterba <dsterba@suse.com>
2019-01-16 16:00:57 +00:00
|
|
|
|
2020-08-20 15:46:10 +00:00
|
|
|
if (!path->skip_locking)
|
btrfs: honor path->skip_locking in backref code
Qgroups will do the old roots lookup at delayed ref time, which could be
while walking down the extent root while running a delayed ref. This
should be fine, except we specifically lock eb's in the backref walking
code irrespective of path->skip_locking, which deadlocks the system.
Fix up the backref code to honor path->skip_locking, nobody will be
modifying the commit_root when we're searching so it's completely safe
to do.
This happens since fb235dc06fac ("btrfs: qgroup: Move half of the qgroup
accounting time out of commit trans"), kernel may lockup with quota
enabled.
There is one backref trace triggered by snapshot dropping along with
write operation in the source subvolume. The example can be reliably
reproduced:
btrfs-cleaner D 0 4062 2 0x80000000
Call Trace:
schedule+0x32/0x90
btrfs_tree_read_lock+0x93/0x130 [btrfs]
find_parent_nodes+0x29b/0x1170 [btrfs]
btrfs_find_all_roots_safe+0xa8/0x120 [btrfs]
btrfs_find_all_roots+0x57/0x70 [btrfs]
btrfs_qgroup_trace_extent_post+0x37/0x70 [btrfs]
btrfs_qgroup_trace_leaf_items+0x10b/0x140 [btrfs]
btrfs_qgroup_trace_subtree+0xc8/0xe0 [btrfs]
do_walk_down+0x541/0x5e3 [btrfs]
walk_down_tree+0xab/0xe7 [btrfs]
btrfs_drop_snapshot+0x356/0x71a [btrfs]
btrfs_clean_one_deleted_snapshot+0xb8/0xf0 [btrfs]
cleaner_kthread+0x12b/0x160 [btrfs]
kthread+0x112/0x130
ret_from_fork+0x27/0x50
When dropping snapshots with qgroup enabled, we will trigger backref
walk.
However such backref walk at that timing is pretty dangerous, as if one
of the parent nodes get WRITE locked by other thread, we could cause a
dead lock.
For example:
FS 260 FS 261 (Dropped)
node A node B
/ \ / \
node C node D node E
/ \ / \ / \
leaf F|leaf G|leaf H|leaf I|leaf J|leaf K
The lock sequence would be:
Thread A (cleaner) | Thread B (other writer)
-----------------------------------------------------------------------
write_lock(B) |
write_lock(D) |
^^^ called by walk_down_tree() |
| write_lock(A)
| write_lock(D) << Stall
read_lock(H) << for backref walk |
read_lock(D) << lock owner is |
the same thread A |
so read lock is OK |
read_lock(A) << Stall |
So thread A hold write lock D, and needs read lock A to unlock.
While thread B holds write lock A, while needs lock D to unlock.
This will cause a deadlock.
This is not only limited to snapshot dropping case. As the backref
walk, even only happens on commit trees, is breaking the normal top-down
locking order, makes it deadlock prone.
Fixes: fb235dc06fac ("btrfs: qgroup: Move half of the qgroup accounting time out of commit trans")
CC: stable@vger.kernel.org # 4.14+
Reported-and-tested-by: David Sterba <dsterba@suse.com>
Reported-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
[ rebase to latest branch and fix lock assert bug in btrfs/007 ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
[ copy logs and deadlock analysis from Qu's patch ]
Signed-off-by: David Sterba <dsterba@suse.com>
2019-01-16 16:00:57 +00:00
|
|
|
btrfs_tree_read_lock(eb);
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = find_extent_in_eb(eb, ctx->bytenr,
|
|
|
|
ctx->extent_item_pos, &eie);
|
btrfs: honor path->skip_locking in backref code
Qgroups will do the old roots lookup at delayed ref time, which could be
while walking down the extent root while running a delayed ref. This
should be fine, except we specifically lock eb's in the backref walking
code irrespective of path->skip_locking, which deadlocks the system.
Fix up the backref code to honor path->skip_locking, nobody will be
modifying the commit_root when we're searching so it's completely safe
to do.
This happens since fb235dc06fac ("btrfs: qgroup: Move half of the qgroup
accounting time out of commit trans"), kernel may lockup with quota
enabled.
There is one backref trace triggered by snapshot dropping along with
write operation in the source subvolume. The example can be reliably
reproduced:
btrfs-cleaner D 0 4062 2 0x80000000
Call Trace:
schedule+0x32/0x90
btrfs_tree_read_lock+0x93/0x130 [btrfs]
find_parent_nodes+0x29b/0x1170 [btrfs]
btrfs_find_all_roots_safe+0xa8/0x120 [btrfs]
btrfs_find_all_roots+0x57/0x70 [btrfs]
btrfs_qgroup_trace_extent_post+0x37/0x70 [btrfs]
btrfs_qgroup_trace_leaf_items+0x10b/0x140 [btrfs]
btrfs_qgroup_trace_subtree+0xc8/0xe0 [btrfs]
do_walk_down+0x541/0x5e3 [btrfs]
walk_down_tree+0xab/0xe7 [btrfs]
btrfs_drop_snapshot+0x356/0x71a [btrfs]
btrfs_clean_one_deleted_snapshot+0xb8/0xf0 [btrfs]
cleaner_kthread+0x12b/0x160 [btrfs]
kthread+0x112/0x130
ret_from_fork+0x27/0x50
When dropping snapshots with qgroup enabled, we will trigger backref
walk.
However such backref walk at that timing is pretty dangerous, as if one
of the parent nodes get WRITE locked by other thread, we could cause a
dead lock.
For example:
FS 260 FS 261 (Dropped)
node A node B
/ \ / \
node C node D node E
/ \ / \ / \
leaf F|leaf G|leaf H|leaf I|leaf J|leaf K
The lock sequence would be:
Thread A (cleaner) | Thread B (other writer)
-----------------------------------------------------------------------
write_lock(B) |
write_lock(D) |
^^^ called by walk_down_tree() |
| write_lock(A)
| write_lock(D) << Stall
read_lock(H) << for backref walk |
read_lock(D) << lock owner is |
the same thread A |
so read lock is OK |
read_lock(A) << Stall |
So thread A hold write lock D, and needs read lock A to unlock.
While thread B holds write lock A, while needs lock D to unlock.
This will cause a deadlock.
This is not only limited to snapshot dropping case. As the backref
walk, even only happens on commit trees, is breaking the normal top-down
locking order, makes it deadlock prone.
Fixes: fb235dc06fac ("btrfs: qgroup: Move half of the qgroup accounting time out of commit trans")
CC: stable@vger.kernel.org # 4.14+
Reported-and-tested-by: David Sterba <dsterba@suse.com>
Reported-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
[ rebase to latest branch and fix lock assert bug in btrfs/007 ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
[ copy logs and deadlock analysis from Qu's patch ]
Signed-off-by: David Sterba <dsterba@suse.com>
2019-01-16 16:00:57 +00:00
|
|
|
if (!path->skip_locking)
|
2020-08-20 15:46:10 +00:00
|
|
|
btrfs_tree_read_unlock(eb);
|
2012-05-17 14:43:03 +00:00
|
|
|
free_extent_buffer(eb);
|
2013-07-30 23:26:35 +00:00
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
ref->inode_list = eie;
|
2022-11-01 16:15:38 +00:00
|
|
|
/*
|
|
|
|
* We transferred the list ownership to the ref,
|
|
|
|
* so set to NULL to avoid a double free in case
|
|
|
|
* an error happens after this.
|
|
|
|
*/
|
|
|
|
eie = NULL;
|
2012-05-17 14:43:03 +00:00
|
|
|
}
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = ulist_add_merge_ptr(ctx->refs, ref->parent,
|
2014-07-28 08:57:04 +00:00
|
|
|
ref->inode_list,
|
|
|
|
(void **)&eie, GFP_NOFS);
|
2013-03-29 23:03:21 +00:00
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
2022-11-01 16:15:47 +00:00
|
|
|
if (!ret && !ctx->ignore_extent_item_pos) {
|
2012-05-30 16:05:21 +00:00
|
|
|
/*
|
2021-11-05 20:45:35 +00:00
|
|
|
* We've recorded that parent, so we must extend
|
|
|
|
* its inode list here.
|
|
|
|
*
|
|
|
|
* However if there was corruption we may not
|
|
|
|
* have found an eie, return an error in this
|
|
|
|
* case.
|
2012-05-30 16:05:21 +00:00
|
|
|
*/
|
2021-11-05 20:45:35 +00:00
|
|
|
ASSERT(eie);
|
|
|
|
if (!eie) {
|
|
|
|
ret = -EUCLEAN;
|
|
|
|
goto out;
|
|
|
|
}
|
2012-05-30 16:05:21 +00:00
|
|
|
while (eie->next)
|
|
|
|
eie = eie->next;
|
|
|
|
eie->next = ref->inode_list;
|
|
|
|
}
|
2014-01-28 11:13:38 +00:00
|
|
|
eie = NULL;
|
2022-11-01 16:15:38 +00:00
|
|
|
/*
|
|
|
|
* We have transferred the inode list ownership from
|
|
|
|
* this ref to the ref we added to the 'refs' ulist.
|
|
|
|
* So set this ref's inode list to NULL to avoid
|
|
|
|
* use-after-free when our caller uses it or double
|
|
|
|
* frees in case an error happens before we return.
|
|
|
|
*/
|
|
|
|
ref->inode_list = NULL;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
2017-07-12 22:20:09 +00:00
|
|
|
cond_resched();
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
btrfs_free_path(path);
|
2017-07-12 22:20:06 +00:00
|
|
|
|
|
|
|
prelim_release(&preftrees.direct);
|
|
|
|
prelim_release(&preftrees.indirect);
|
|
|
|
prelim_release(&preftrees.indirect_missing_keys);
|
|
|
|
|
2014-01-28 11:13:38 +00:00
|
|
|
if (ret < 0)
|
|
|
|
free_inode_elem_list(eie);
|
2011-11-23 17:55:04 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-11-01 16:15:47 +00:00
|
|
|
* Finds all leaves with a reference to the specified combination of
|
|
|
|
* @ctx->bytenr and @ctx->extent_item_pos. The bytenr of the found leaves are
|
|
|
|
* added to the ulist at @ctx->refs, and that ulist is allocated by this
|
|
|
|
* function. The caller should free the ulist with free_leaf_list() if
|
|
|
|
* @ctx->ignore_extent_item_pos is false, otherwise a fimple ulist_free() is
|
|
|
|
* enough.
|
2011-11-23 17:55:04 +00:00
|
|
|
*
|
2022-11-01 16:15:47 +00:00
|
|
|
* Returns 0 on success and < 0 on error. On error @ctx->refs is not allocated.
|
2011-11-23 17:55:04 +00:00
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
int btrfs_find_all_leafs(struct btrfs_backref_walk_ctx *ctx)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ASSERT(ctx->refs == NULL);
|
|
|
|
|
|
|
|
ctx->refs = ulist_alloc(GFP_NOFS);
|
|
|
|
if (!ctx->refs)
|
2011-11-23 17:55:04 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = find_parent_nodes(ctx, NULL);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (ret < 0 && ret != -ENOENT) {
|
2022-11-01 16:15:47 +00:00
|
|
|
free_leaf_list(ctx->refs);
|
|
|
|
ctx->refs = NULL;
|
2011-11-23 17:55:04 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-11-01 16:15:47 +00:00
|
|
|
* Walk all backrefs for a given extent to find all roots that reference this
|
2011-11-23 17:55:04 +00:00
|
|
|
* extent. Walking a backref means finding all extents that reference this
|
|
|
|
* extent and in turn walk the backrefs of those, too. Naturally this is a
|
|
|
|
* recursive process, but here it is implemented in an iterative fashion: We
|
|
|
|
* find all referencing extents for the extent in question and put them on a
|
|
|
|
* list. In turn, we find all referencing extents for those, further appending
|
|
|
|
* to the list. The way we iterate the list allows adding more elements after
|
|
|
|
* the current while iterating. The process stops when we reach the end of the
|
2022-11-01 16:15:47 +00:00
|
|
|
* list.
|
|
|
|
*
|
2022-11-01 16:15:48 +00:00
|
|
|
* Found roots are added to @ctx->roots, which is allocated by this function if
|
|
|
|
* it points to NULL, in which case the caller is responsible for freeing it
|
|
|
|
* after it's not needed anymore.
|
|
|
|
* This function requires @ctx->refs to be NULL, as it uses it for allocating a
|
|
|
|
* ulist to do temporary work, and frees it before returning.
|
2011-11-23 17:55:04 +00:00
|
|
|
*
|
2022-11-01 16:15:48 +00:00
|
|
|
* Returns 0 on success, < 0 on error.
|
2011-11-23 17:55:04 +00:00
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
static int btrfs_find_all_roots_safe(struct btrfs_backref_walk_ctx *ctx)
|
2011-11-23 17:55:04 +00:00
|
|
|
{
|
2022-11-01 16:15:47 +00:00
|
|
|
const u64 orig_bytenr = ctx->bytenr;
|
|
|
|
const bool orig_ignore_extent_item_pos = ctx->ignore_extent_item_pos;
|
2022-11-01 16:15:48 +00:00
|
|
|
bool roots_ulist_allocated = false;
|
2012-05-22 12:56:50 +00:00
|
|
|
struct ulist_iterator uiter;
|
2022-11-01 16:15:47 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
ASSERT(ctx->refs == NULL);
|
2011-11-23 17:55:04 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ctx->refs = ulist_alloc(GFP_NOFS);
|
|
|
|
if (!ctx->refs)
|
2011-11-23 17:55:04 +00:00
|
|
|
return -ENOMEM;
|
2022-11-01 16:15:47 +00:00
|
|
|
|
|
|
|
if (!ctx->roots) {
|
2022-11-01 16:15:48 +00:00
|
|
|
ctx->roots = ulist_alloc(GFP_NOFS);
|
|
|
|
if (!ctx->roots) {
|
|
|
|
ulist_free(ctx->refs);
|
|
|
|
ctx->refs = NULL;
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
roots_ulist_allocated = true;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ctx->ignore_extent_item_pos = true;
|
|
|
|
|
2012-05-22 12:56:50 +00:00
|
|
|
ULIST_ITER_INIT(&uiter);
|
2011-11-23 17:55:04 +00:00
|
|
|
while (1) {
|
2022-11-01 16:15:47 +00:00
|
|
|
struct ulist_node *node;
|
|
|
|
|
|
|
|
ret = find_parent_nodes(ctx, NULL);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (ret < 0 && ret != -ENOENT) {
|
2022-11-01 16:15:48 +00:00
|
|
|
if (roots_ulist_allocated) {
|
|
|
|
ulist_free(ctx->roots);
|
|
|
|
ctx->roots = NULL;
|
|
|
|
}
|
2022-11-01 16:15:47 +00:00
|
|
|
break;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = 0;
|
|
|
|
node = ulist_next(ctx->refs, &uiter);
|
2011-11-23 17:55:04 +00:00
|
|
|
if (!node)
|
|
|
|
break;
|
2022-11-01 16:15:47 +00:00
|
|
|
ctx->bytenr = node->val;
|
2014-01-26 14:32:18 +00:00
|
|
|
cond_resched();
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ulist_free(ctx->refs);
|
|
|
|
ctx->refs = NULL;
|
|
|
|
ctx->bytenr = orig_bytenr;
|
|
|
|
ctx->ignore_extent_item_pos = orig_ignore_extent_item_pos;
|
|
|
|
|
|
|
|
return ret;
|
2011-11-23 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
int btrfs_find_all_roots(struct btrfs_backref_walk_ctx *ctx,
|
2021-07-22 14:58:10 +00:00
|
|
|
bool skip_commit_root_sem)
|
2014-03-13 19:42:13 +00:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
if (!ctx->trans && !skip_commit_root_sem)
|
|
|
|
down_read(&ctx->fs_info->commit_root_sem);
|
|
|
|
ret = btrfs_find_all_roots_safe(ctx);
|
|
|
|
if (!ctx->trans && !skip_commit_root_sem)
|
|
|
|
up_read(&ctx->fs_info->commit_root_sem);
|
2014-03-13 19:42:13 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-10-11 12:17:03 +00:00
|
|
|
struct btrfs_backref_share_check_ctx *btrfs_alloc_backref_share_check_ctx(void)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_share_check_ctx *ctx;
|
|
|
|
|
|
|
|
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
|
|
|
if (!ctx)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ulist_init(&ctx->refs);
|
|
|
|
|
|
|
|
return ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
void btrfs_free_backref_share_ctx(struct btrfs_backref_share_check_ctx *ctx)
|
|
|
|
{
|
|
|
|
if (!ctx)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ulist_release(&ctx->refs);
|
|
|
|
kfree(ctx);
|
|
|
|
}
|
|
|
|
|
2022-09-01 13:18:27 +00:00
|
|
|
/*
|
|
|
|
* Check if a data extent is shared or not.
|
2021-01-22 09:58:00 +00:00
|
|
|
*
|
2022-10-11 12:17:01 +00:00
|
|
|
* @inode: The inode whose extent we are checking.
|
btrfs: skip unnecessary extent buffer sharedness checks during fiemap
During fiemap, for each file extent we find, we must check if it's shared
or not. The sharedness check starts by verifying if the extent is directly
shared (its refcount in the extent tree is > 1), and if it is not directly
shared, then we will check if every node in the subvolume b+tree leading
from the root to the leaf that has the file extent item (in reverse order),
is shared (through snapshots).
However this second step is not needed if our extent was created in a
transaction more recent than the last transaction where a snapshot of the
inode's root happened, because it can't be shared indirectly (through
shared subtrees) without a snapshot created in a more recent transaction.
So grab the generation of the extent from the extent map and pass it to
btrfs_is_data_extent_shared(), which will skip this second phase when the
generation is more recent than the root's last snapshot value. Note that
we skip this optimization if the extent map is the result of merging 2
or more extent maps, because in this case its generation is the maximum
of the generations of all merged extent maps.
The fact the we use extent maps and they can be merged despite the
underlying extents being distinct (different file extent items in the
subvolume b+tree and different extent items in the extent b+tree), can
result in some bugs when reporting shared extents. But this is a problem
of the current implementation of fiemap relying on extent maps.
One example where we get incorrect results is:
$ cat fiemap-bug.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
# Create a file with two 256K extents.
# Since there is no other write activity, they will be contiguous,
# and their extent maps merged, despite having two distinct extents.
xfs_io -f -c "pwrite -S 0xab 0 256K" \
-c "fsync" \
-c "pwrite -S 0xcd 256K 256K" \
-c "fsync" \
$MNT/foo
# Now clone only the second extent into another file.
xfs_io -f -c "reflink $MNT/foo 256K 0 256K" $MNT/bar
# Filefrag will report a single 512K extent, and say it's not shared.
echo
filefrag -v $MNT/foo
umount $MNT
Running the reproducer:
$ ./fiemap-bug.sh
wrote 262144/262144 bytes at offset 0
256 KiB, 64 ops; 0.0038 sec (65.479 MiB/sec and 16762.7030 ops/sec)
wrote 262144/262144 bytes at offset 262144
256 KiB, 64 ops; 0.0040 sec (61.125 MiB/sec and 15647.9218 ops/sec)
linked 262144/262144 bytes at offset 0
256 KiB, 1 ops; 0.0002 sec (1.034 GiB/sec and 4237.2881 ops/sec)
Filesystem type is: 9123683e
File size of /mnt/sdj/foo is 524288 (128 blocks of 4096 bytes)
ext: logical_offset: physical_offset: length: expected: flags:
0: 0.. 127: 3328.. 3455: 128: last,eof
/mnt/sdj/foo: 1 extent found
We end up reporting that we have a single 512K that is not shared, however
we have two 256K extents, and the second one is shared. Changing the
reproducer to clone instead the first extent into file 'bar', makes us
report a single 512K extent that is shared, which is algo incorrect since
we have two 256K extents and only the first one is shared.
This is z problem that existed before this change, and remains after this
change, as it can't be easily fixed. The next patch in the series reworks
fiemap to primarily use file extent items instead of extent maps (except
for checking for delalloc ranges), with the goal of improving its
scalability and performance, but it also ends up fixing this particular
bug caused by extent map merging.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:29 +00:00
|
|
|
* @bytenr: Logical bytenr of the extent we are checking.
|
|
|
|
* @extent_gen: Generation of the extent (file extent item) or 0 if it is
|
|
|
|
* not known.
|
2022-10-11 12:17:02 +00:00
|
|
|
* @ctx: A backref sharedness check context.
|
2015-05-19 19:49:50 +00:00
|
|
|
*
|
2022-09-01 13:18:27 +00:00
|
|
|
* btrfs_is_data_extent_shared uses the backref walking code but will short
|
2015-05-19 19:49:50 +00:00
|
|
|
* circuit as soon as it finds a root or inode that doesn't match the
|
|
|
|
* one passed in. This provides a significant performance benefit for
|
|
|
|
* callers (such as fiemap) which want to know whether the extent is
|
|
|
|
* shared but do not need a ref count.
|
|
|
|
*
|
Btrfs: do not start a transaction during fiemap
During fiemap, for regular extents (non inline) we need to check if they
are shared and if they are, set the shared bit. Checking if an extent is
shared requires checking the delayed references of the currently running
transaction, since some reference might have not yet hit the extent tree
and be only in the in-memory delayed references.
However we were using a transaction join for this, which creates a new
transaction when there is no transaction currently running. That means
that two more potential failures can happen: creating the transaction and
committing it. Further, if no write activity is currently happening in the
system, and fiemap calls keep being done, we end up creating and
committing transactions that do nothing.
In some extreme cases this can result in the commit of the transaction
created by fiemap to fail with ENOSPC when updating the root item of a
subvolume tree because a join does not reserve any space, leading to a
trace like the following:
heisenberg kernel: ------------[ cut here ]------------
heisenberg kernel: BTRFS: Transaction aborted (error -28)
heisenberg kernel: WARNING: CPU: 0 PID: 7137 at fs/btrfs/root-tree.c:136 btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: CPU: 0 PID: 7137 Comm: btrfs-transacti Not tainted 4.19.0-4-amd64 #1 Debian 4.19.28-2
heisenberg kernel: Hardware name: FUJITSU LIFEBOOK U757/FJNB2A5, BIOS Version 1.21 03/19/2018
heisenberg kernel: RIP: 0010:btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: RSP: 0018:ffffb5448828bd40 EFLAGS: 00010286
heisenberg kernel: RAX: 0000000000000000 RBX: ffff8ed56bccef50 RCX: 0000000000000006
heisenberg kernel: RDX: 0000000000000007 RSI: 0000000000000092 RDI: ffff8ed6bda166a0
heisenberg kernel: RBP: 00000000ffffffe4 R08: 00000000000003df R09: 0000000000000007
heisenberg kernel: R10: 0000000000000000 R11: 0000000000000001 R12: ffff8ed63396a078
heisenberg kernel: R13: ffff8ed092d7c800 R14: ffff8ed64f5db028 R15: ffff8ed6bd03d068
heisenberg kernel: FS: 0000000000000000(0000) GS:ffff8ed6bda00000(0000) knlGS:0000000000000000
heisenberg kernel: CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
heisenberg kernel: CR2: 00007f46f75f8000 CR3: 0000000310a0a002 CR4: 00000000003606f0
heisenberg kernel: DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
heisenberg kernel: DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
heisenberg kernel: Call Trace:
heisenberg kernel: commit_fs_roots+0x166/0x1d0 [btrfs]
heisenberg kernel: ? _cond_resched+0x15/0x30
heisenberg kernel: ? btrfs_run_delayed_refs+0xac/0x180 [btrfs]
heisenberg kernel: btrfs_commit_transaction+0x2bd/0x870 [btrfs]
heisenberg kernel: ? start_transaction+0x9d/0x3f0 [btrfs]
heisenberg kernel: transaction_kthread+0x147/0x180 [btrfs]
heisenberg kernel: ? btrfs_cleanup_transaction+0x530/0x530 [btrfs]
heisenberg kernel: kthread+0x112/0x130
heisenberg kernel: ? kthread_bind+0x30/0x30
heisenberg kernel: ret_from_fork+0x35/0x40
heisenberg kernel: ---[ end trace 05de912e30e012d9 ]---
Since fiemap (and btrfs_check_shared()) is a read-only operation, do not do
a transaction join to avoid the overhead of creating a new transaction (if
there is currently no running transaction) and introducing a potential
point of failure when the new transaction gets committed, instead use a
transaction attach to grab a handle for the currently running transaction
if any.
Reported-by: Christoph Anton Mitterer <calestyo@scientia.net>
Link: https://lore.kernel.org/linux-btrfs/b2a668d7124f1d3e410367f587926f622b3f03a4.camel@scientia.net/
Fixes: afce772e87c36c ("btrfs: fix check_shared for fiemap ioctl")
CC: stable@vger.kernel.org # 4.14+
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-04-15 13:50:51 +00:00
|
|
|
* This attempts to attach to the running transaction in order to account for
|
|
|
|
* delayed refs, but continues on even when no running transaction exists.
|
2017-06-29 03:56:58 +00:00
|
|
|
*
|
2015-05-19 19:49:50 +00:00
|
|
|
* Return: 0 if extent is not shared, 1 if it is shared, < 0 on error.
|
|
|
|
*/
|
2022-10-11 12:17:01 +00:00
|
|
|
int btrfs_is_data_extent_shared(struct btrfs_inode *inode, u64 bytenr,
|
btrfs: skip unnecessary extent buffer sharedness checks during fiemap
During fiemap, for each file extent we find, we must check if it's shared
or not. The sharedness check starts by verifying if the extent is directly
shared (its refcount in the extent tree is > 1), and if it is not directly
shared, then we will check if every node in the subvolume b+tree leading
from the root to the leaf that has the file extent item (in reverse order),
is shared (through snapshots).
However this second step is not needed if our extent was created in a
transaction more recent than the last transaction where a snapshot of the
inode's root happened, because it can't be shared indirectly (through
shared subtrees) without a snapshot created in a more recent transaction.
So grab the generation of the extent from the extent map and pass it to
btrfs_is_data_extent_shared(), which will skip this second phase when the
generation is more recent than the root's last snapshot value. Note that
we skip this optimization if the extent map is the result of merging 2
or more extent maps, because in this case its generation is the maximum
of the generations of all merged extent maps.
The fact the we use extent maps and they can be merged despite the
underlying extents being distinct (different file extent items in the
subvolume b+tree and different extent items in the extent b+tree), can
result in some bugs when reporting shared extents. But this is a problem
of the current implementation of fiemap relying on extent maps.
One example where we get incorrect results is:
$ cat fiemap-bug.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
# Create a file with two 256K extents.
# Since there is no other write activity, they will be contiguous,
# and their extent maps merged, despite having two distinct extents.
xfs_io -f -c "pwrite -S 0xab 0 256K" \
-c "fsync" \
-c "pwrite -S 0xcd 256K 256K" \
-c "fsync" \
$MNT/foo
# Now clone only the second extent into another file.
xfs_io -f -c "reflink $MNT/foo 256K 0 256K" $MNT/bar
# Filefrag will report a single 512K extent, and say it's not shared.
echo
filefrag -v $MNT/foo
umount $MNT
Running the reproducer:
$ ./fiemap-bug.sh
wrote 262144/262144 bytes at offset 0
256 KiB, 64 ops; 0.0038 sec (65.479 MiB/sec and 16762.7030 ops/sec)
wrote 262144/262144 bytes at offset 262144
256 KiB, 64 ops; 0.0040 sec (61.125 MiB/sec and 15647.9218 ops/sec)
linked 262144/262144 bytes at offset 0
256 KiB, 1 ops; 0.0002 sec (1.034 GiB/sec and 4237.2881 ops/sec)
Filesystem type is: 9123683e
File size of /mnt/sdj/foo is 524288 (128 blocks of 4096 bytes)
ext: logical_offset: physical_offset: length: expected: flags:
0: 0.. 127: 3328.. 3455: 128: last,eof
/mnt/sdj/foo: 1 extent found
We end up reporting that we have a single 512K that is not shared, however
we have two 256K extents, and the second one is shared. Changing the
reproducer to clone instead the first extent into file 'bar', makes us
report a single 512K extent that is shared, which is algo incorrect since
we have two 256K extents and only the first one is shared.
This is z problem that existed before this change, and remains after this
change, as it can't be easily fixed. The next patch in the series reworks
fiemap to primarily use file extent items instead of extent maps (except
for checking for delalloc ranges), with the goal of improving its
scalability and performance, but it also ends up fixing this particular
bug caused by extent map merging.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:29 +00:00
|
|
|
u64 extent_gen,
|
2022-10-11 12:17:02 +00:00
|
|
|
struct btrfs_backref_share_check_ctx *ctx)
|
2014-09-10 20:20:45 +00:00
|
|
|
{
|
2022-11-01 16:15:47 +00:00
|
|
|
struct btrfs_backref_walk_ctx walk_ctx = { 0 };
|
2022-10-11 12:17:01 +00:00
|
|
|
struct btrfs_root *root = inode->root;
|
2017-06-29 03:56:58 +00:00
|
|
|
struct btrfs_fs_info *fs_info = root->fs_info;
|
|
|
|
struct btrfs_trans_handle *trans;
|
2014-09-10 20:20:45 +00:00
|
|
|
struct ulist_iterator uiter;
|
|
|
|
struct ulist_node *node;
|
2021-03-11 14:31:07 +00:00
|
|
|
struct btrfs_seq_list elem = BTRFS_SEQ_LIST_INIT(elem);
|
2014-09-10 20:20:45 +00:00
|
|
|
int ret = 0;
|
2017-07-12 22:20:10 +00:00
|
|
|
struct share_check shared = {
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
.ctx = ctx,
|
|
|
|
.root = root,
|
2022-10-11 12:17:01 +00:00
|
|
|
.inum = btrfs_ino(inode),
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
.data_bytenr = bytenr,
|
btrfs: avoid unnecessary resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
However when the generation of the data extent is more recent than the
last generation used to snapshot the root, we don't need to determine
the path, since the data extent can not be shared through snapshots.
For this case we currently still determine the leaf of that path (at
find_parent_nodes(), but then stop determining the other nodes in the
path (at btrfs_is_data_extent_shared()) as it's pointless.
So do the check of the data extent's generation earlier, at
find_parent_nodes(), before trying to resolve the indirect reference to
determine the leaf in the path. This saves us from doing one expensive
b+tree search in the fs tree of our target inode, as well as other minor
work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-fiemap.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1285 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 742 milliseconds (metadata cached)
After applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 689 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 393 milliseconds (metadata cached)
That's a -46.4% total reduction for the metadata not cached case, and
a -47.0% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:09 +00:00
|
|
|
.data_extent_gen = extent_gen,
|
2017-07-12 22:20:10 +00:00
|
|
|
.share_count = 0,
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
.self_ref_count = 0,
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
.have_delayed_delete_refs = false,
|
2017-07-12 22:20:10 +00:00
|
|
|
};
|
btrfs: speedup checking for extent sharedness during fiemap
One of the most expensive tasks performed during fiemap is to check if
an extent is shared. This task has two major steps:
1) Check if the data extent is shared. This implies checking the extent
item in the extent tree, checking delayed references, etc. If we
find the data extent is directly shared, we terminate immediately;
2) If the data extent is not directly shared (its extent item has a
refcount of 1), then it may be shared if we have snapshots that share
subtrees of the inode's subvolume b+tree. So we check if the leaf
containing the file extent item is shared, then its parent node, then
the parent node of the parent node, etc, until we reach the root node
or we find one of them is shared - in which case we stop immediately.
During fiemap we process the extents of a file from left to right, from
file offset 0 to EOF. This means that we iterate b+tree leaves from left
to right, and has the implication that we keep repeating that second step
above several times for the same b+tree path of the inode's subvolume
b+tree.
For example, if we have two file extent items in leaf X, and the path to
leaf X is A -> B -> C -> X, then when we try to determine if the data
extent referenced by the first extent item is shared, we check if the data
extent is shared - if it's not, then we check if leaf X is shared, if not,
then we check if node C is shared, if not, then check if node B is shared,
if not than check if node A is shared. When we move to the next file
extent item, after determining the data extent is not shared, we repeat
the checks for X, C, B and A - doing all the expensive searches in the
extent tree, delayed refs, etc. If we have thousands of tile extents, then
we keep repeating the sharedness checks for the same paths over and over.
On a file that has no shared extents or only a small portion, it's easy
to see that this scales terribly with the number of extents in the file
and the sizes of the extent and subvolume b+trees.
This change eliminates the repeated sharedness check on extent buffers
by caching the results of the last path used. The results can be used as
long as no snapshots were created since they were cached (for not shared
extent buffers) or no roots were dropped since they were cached (for
shared extent buffers). This greatly reduces the time spent by fiemap for
files with thousands of extents and/or large extent and subvolume b+trees.
Example performance test:
$ cat fiemap-perf-test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 128K file extents (due to compression).
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 3597 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 2107 milliseconds (metadata cached)
After this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1646 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 698 milliseconds (metadata cached)
That's about 2.2x faster when no metadata is cached, and about 3x faster
when all metadata is cached. On a real filesystem with many other files,
data, directories, etc, the b+trees will be 2 or 3 levels higher,
therefore this optimization will have a higher impact.
Several reports of a slow fiemap show up often, the two Link tags below
refer to two recent reports of such slowness. This patch, together with
the next ones in the series, is meant to address that.
Link: https://lore.kernel.org/linux-btrfs/21dd32c6-f1f9-f44a-466a-e18fdc6788a7@virtuozzo.com/
Link: https://lore.kernel.org/linux-btrfs/Ysace25wh5BbLd5f@atmark-techno.com/
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:28 +00:00
|
|
|
int level;
|
2014-09-10 20:20:45 +00:00
|
|
|
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
for (int i = 0; i < BTRFS_BACKREF_CTX_PREV_EXTENTS_SIZE; i++) {
|
|
|
|
if (ctx->prev_extents_cache[i].bytenr == bytenr)
|
|
|
|
return ctx->prev_extents_cache[i].is_shared;
|
|
|
|
}
|
|
|
|
|
2022-10-11 12:17:03 +00:00
|
|
|
ulist_init(&ctx->refs);
|
2014-09-10 20:20:45 +00:00
|
|
|
|
Btrfs: fix deadlock between fiemap and transaction commits
The fiemap handler locks a file range that can have unflushed delalloc,
and after locking the range, it tries to attach to a running transaction.
If the running transaction started its commit, that is, it is in state
TRANS_STATE_COMMIT_START, and either the filesystem was mounted with the
flushoncommit option or the transaction is creating a snapshot for the
subvolume that contains the file that fiemap is operating on, we end up
deadlocking. This happens because fiemap is blocked on the transaction,
waiting for it to complete, and the transaction is waiting for the flushed
dealloc to complete, which requires locking the file range that the fiemap
task already locked. The following stack traces serve as an example of
when this deadlock happens:
(...)
[404571.515510] Workqueue: btrfs-endio-write btrfs_endio_write_helper [btrfs]
[404571.515956] Call Trace:
[404571.516360] ? __schedule+0x3ae/0x7b0
[404571.516730] schedule+0x3a/0xb0
[404571.517104] lock_extent_bits+0x1ec/0x2a0 [btrfs]
[404571.517465] ? remove_wait_queue+0x60/0x60
[404571.517832] btrfs_finish_ordered_io+0x292/0x800 [btrfs]
[404571.518202] normal_work_helper+0xea/0x530 [btrfs]
[404571.518566] process_one_work+0x21e/0x5c0
[404571.518990] worker_thread+0x4f/0x3b0
[404571.519413] ? process_one_work+0x5c0/0x5c0
[404571.519829] kthread+0x103/0x140
[404571.520191] ? kthread_create_worker_on_cpu+0x70/0x70
[404571.520565] ret_from_fork+0x3a/0x50
[404571.520915] kworker/u8:6 D 0 31651 2 0x80004000
[404571.521290] Workqueue: btrfs-flush_delalloc btrfs_flush_delalloc_helper [btrfs]
(...)
[404571.537000] fsstress D 0 13117 13115 0x00004000
[404571.537263] Call Trace:
[404571.537524] ? __schedule+0x3ae/0x7b0
[404571.537788] schedule+0x3a/0xb0
[404571.538066] wait_current_trans+0xc8/0x100 [btrfs]
[404571.538349] ? remove_wait_queue+0x60/0x60
[404571.538680] start_transaction+0x33c/0x500 [btrfs]
[404571.539076] btrfs_check_shared+0xa3/0x1f0 [btrfs]
[404571.539513] ? extent_fiemap+0x2ce/0x650 [btrfs]
[404571.539866] extent_fiemap+0x2ce/0x650 [btrfs]
[404571.540170] do_vfs_ioctl+0x526/0x6f0
[404571.540436] ksys_ioctl+0x70/0x80
[404571.540734] __x64_sys_ioctl+0x16/0x20
[404571.540997] do_syscall_64+0x60/0x1d0
[404571.541279] entry_SYSCALL_64_after_hwframe+0x49/0xbe
(...)
[404571.543729] btrfs D 0 14210 14208 0x00004000
[404571.544023] Call Trace:
[404571.544275] ? __schedule+0x3ae/0x7b0
[404571.544526] ? wait_for_completion+0x112/0x1a0
[404571.544795] schedule+0x3a/0xb0
[404571.545064] schedule_timeout+0x1ff/0x390
[404571.545351] ? lock_acquire+0xa6/0x190
[404571.545638] ? wait_for_completion+0x49/0x1a0
[404571.545890] ? wait_for_completion+0x112/0x1a0
[404571.546228] wait_for_completion+0x131/0x1a0
[404571.546503] ? wake_up_q+0x70/0x70
[404571.546775] btrfs_wait_ordered_extents+0x27c/0x400 [btrfs]
[404571.547159] btrfs_commit_transaction+0x3b0/0xae0 [btrfs]
[404571.547449] ? btrfs_mksubvol+0x4a4/0x640 [btrfs]
[404571.547703] ? remove_wait_queue+0x60/0x60
[404571.547969] btrfs_mksubvol+0x605/0x640 [btrfs]
[404571.548226] ? __sb_start_write+0xd4/0x1c0
[404571.548512] ? mnt_want_write_file+0x24/0x50
[404571.548789] btrfs_ioctl_snap_create_transid+0x169/0x1a0 [btrfs]
[404571.549048] btrfs_ioctl_snap_create_v2+0x11d/0x170 [btrfs]
[404571.549307] btrfs_ioctl+0x133f/0x3150 [btrfs]
[404571.549549] ? mem_cgroup_charge_statistics+0x4c/0xd0
[404571.549792] ? mem_cgroup_commit_charge+0x84/0x4b0
[404571.550064] ? __handle_mm_fault+0xe3e/0x11f0
[404571.550306] ? do_raw_spin_unlock+0x49/0xc0
[404571.550608] ? _raw_spin_unlock+0x24/0x30
[404571.550976] ? __handle_mm_fault+0xedf/0x11f0
[404571.551319] ? do_vfs_ioctl+0xa2/0x6f0
[404571.551659] ? btrfs_ioctl_get_supported_features+0x30/0x30 [btrfs]
[404571.552087] do_vfs_ioctl+0xa2/0x6f0
[404571.552355] ksys_ioctl+0x70/0x80
[404571.552621] __x64_sys_ioctl+0x16/0x20
[404571.552864] do_syscall_64+0x60/0x1d0
[404571.553104] entry_SYSCALL_64_after_hwframe+0x49/0xbe
(...)
If we were joining the transaction instead of attaching to it, we would
not risk a deadlock because a join only blocks if the transaction is in a
state greater then or equals to TRANS_STATE_COMMIT_DOING, and the delalloc
flush performed by a transaction is done before it reaches that state,
when it is in the state TRANS_STATE_COMMIT_START. However a transaction
join is intended for use cases where we do modify the filesystem, and
fiemap only needs to peek at delayed references from the current
transaction in order to determine if extents are shared, and, besides
that, when there is no current transaction or when it blocks to wait for
a current committing transaction to complete, it creates a new transaction
without reserving any space. Such unnecessary transactions, besides doing
unnecessary IO, can cause transaction aborts (-ENOSPC) and unnecessary
rotation of the precious backup roots.
So fix this by adding a new transaction join variant, named join_nostart,
which behaves like the regular join, but it does not create a transaction
when none currently exists or after waiting for a committing transaction
to complete.
Fixes: 03628cdbc64db6 ("Btrfs: do not start a transaction during fiemap")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-07-29 08:37:10 +00:00
|
|
|
trans = btrfs_join_transaction_nostart(root);
|
2017-06-29 03:56:58 +00:00
|
|
|
if (IS_ERR(trans)) {
|
Btrfs: do not start a transaction during fiemap
During fiemap, for regular extents (non inline) we need to check if they
are shared and if they are, set the shared bit. Checking if an extent is
shared requires checking the delayed references of the currently running
transaction, since some reference might have not yet hit the extent tree
and be only in the in-memory delayed references.
However we were using a transaction join for this, which creates a new
transaction when there is no transaction currently running. That means
that two more potential failures can happen: creating the transaction and
committing it. Further, if no write activity is currently happening in the
system, and fiemap calls keep being done, we end up creating and
committing transactions that do nothing.
In some extreme cases this can result in the commit of the transaction
created by fiemap to fail with ENOSPC when updating the root item of a
subvolume tree because a join does not reserve any space, leading to a
trace like the following:
heisenberg kernel: ------------[ cut here ]------------
heisenberg kernel: BTRFS: Transaction aborted (error -28)
heisenberg kernel: WARNING: CPU: 0 PID: 7137 at fs/btrfs/root-tree.c:136 btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: CPU: 0 PID: 7137 Comm: btrfs-transacti Not tainted 4.19.0-4-amd64 #1 Debian 4.19.28-2
heisenberg kernel: Hardware name: FUJITSU LIFEBOOK U757/FJNB2A5, BIOS Version 1.21 03/19/2018
heisenberg kernel: RIP: 0010:btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: RSP: 0018:ffffb5448828bd40 EFLAGS: 00010286
heisenberg kernel: RAX: 0000000000000000 RBX: ffff8ed56bccef50 RCX: 0000000000000006
heisenberg kernel: RDX: 0000000000000007 RSI: 0000000000000092 RDI: ffff8ed6bda166a0
heisenberg kernel: RBP: 00000000ffffffe4 R08: 00000000000003df R09: 0000000000000007
heisenberg kernel: R10: 0000000000000000 R11: 0000000000000001 R12: ffff8ed63396a078
heisenberg kernel: R13: ffff8ed092d7c800 R14: ffff8ed64f5db028 R15: ffff8ed6bd03d068
heisenberg kernel: FS: 0000000000000000(0000) GS:ffff8ed6bda00000(0000) knlGS:0000000000000000
heisenberg kernel: CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
heisenberg kernel: CR2: 00007f46f75f8000 CR3: 0000000310a0a002 CR4: 00000000003606f0
heisenberg kernel: DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
heisenberg kernel: DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
heisenberg kernel: Call Trace:
heisenberg kernel: commit_fs_roots+0x166/0x1d0 [btrfs]
heisenberg kernel: ? _cond_resched+0x15/0x30
heisenberg kernel: ? btrfs_run_delayed_refs+0xac/0x180 [btrfs]
heisenberg kernel: btrfs_commit_transaction+0x2bd/0x870 [btrfs]
heisenberg kernel: ? start_transaction+0x9d/0x3f0 [btrfs]
heisenberg kernel: transaction_kthread+0x147/0x180 [btrfs]
heisenberg kernel: ? btrfs_cleanup_transaction+0x530/0x530 [btrfs]
heisenberg kernel: kthread+0x112/0x130
heisenberg kernel: ? kthread_bind+0x30/0x30
heisenberg kernel: ret_from_fork+0x35/0x40
heisenberg kernel: ---[ end trace 05de912e30e012d9 ]---
Since fiemap (and btrfs_check_shared()) is a read-only operation, do not do
a transaction join to avoid the overhead of creating a new transaction (if
there is currently no running transaction) and introducing a potential
point of failure when the new transaction gets committed, instead use a
transaction attach to grab a handle for the currently running transaction
if any.
Reported-by: Christoph Anton Mitterer <calestyo@scientia.net>
Link: https://lore.kernel.org/linux-btrfs/b2a668d7124f1d3e410367f587926f622b3f03a4.camel@scientia.net/
Fixes: afce772e87c36c ("btrfs: fix check_shared for fiemap ioctl")
CC: stable@vger.kernel.org # 4.14+
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-04-15 13:50:51 +00:00
|
|
|
if (PTR_ERR(trans) != -ENOENT && PTR_ERR(trans) != -EROFS) {
|
|
|
|
ret = PTR_ERR(trans);
|
|
|
|
goto out;
|
|
|
|
}
|
2017-06-29 03:56:58 +00:00
|
|
|
trans = NULL;
|
2014-09-10 20:20:45 +00:00
|
|
|
down_read(&fs_info->commit_root_sem);
|
2017-06-29 03:56:58 +00:00
|
|
|
} else {
|
|
|
|
btrfs_get_tree_mod_seq(fs_info, &elem);
|
2022-11-01 16:15:47 +00:00
|
|
|
walk_ctx.time_seq = elem.seq;
|
2017-06-29 03:56:58 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
walk_ctx.ignore_extent_item_pos = true;
|
|
|
|
walk_ctx.trans = trans;
|
|
|
|
walk_ctx.fs_info = fs_info;
|
|
|
|
walk_ctx.refs = &ctx->refs;
|
|
|
|
|
btrfs: speedup checking for extent sharedness during fiemap
One of the most expensive tasks performed during fiemap is to check if
an extent is shared. This task has two major steps:
1) Check if the data extent is shared. This implies checking the extent
item in the extent tree, checking delayed references, etc. If we
find the data extent is directly shared, we terminate immediately;
2) If the data extent is not directly shared (its extent item has a
refcount of 1), then it may be shared if we have snapshots that share
subtrees of the inode's subvolume b+tree. So we check if the leaf
containing the file extent item is shared, then its parent node, then
the parent node of the parent node, etc, until we reach the root node
or we find one of them is shared - in which case we stop immediately.
During fiemap we process the extents of a file from left to right, from
file offset 0 to EOF. This means that we iterate b+tree leaves from left
to right, and has the implication that we keep repeating that second step
above several times for the same b+tree path of the inode's subvolume
b+tree.
For example, if we have two file extent items in leaf X, and the path to
leaf X is A -> B -> C -> X, then when we try to determine if the data
extent referenced by the first extent item is shared, we check if the data
extent is shared - if it's not, then we check if leaf X is shared, if not,
then we check if node C is shared, if not, then check if node B is shared,
if not than check if node A is shared. When we move to the next file
extent item, after determining the data extent is not shared, we repeat
the checks for X, C, B and A - doing all the expensive searches in the
extent tree, delayed refs, etc. If we have thousands of tile extents, then
we keep repeating the sharedness checks for the same paths over and over.
On a file that has no shared extents or only a small portion, it's easy
to see that this scales terribly with the number of extents in the file
and the sizes of the extent and subvolume b+trees.
This change eliminates the repeated sharedness check on extent buffers
by caching the results of the last path used. The results can be used as
long as no snapshots were created since they were cached (for not shared
extent buffers) or no roots were dropped since they were cached (for
shared extent buffers). This greatly reduces the time spent by fiemap for
files with thousands of extents and/or large extent and subvolume b+trees.
Example performance test:
$ cat fiemap-perf-test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 128K file extents (due to compression).
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 3597 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 2107 milliseconds (metadata cached)
After this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1646 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 698 milliseconds (metadata cached)
That's about 2.2x faster when no metadata is cached, and about 3x faster
when all metadata is cached. On a real filesystem with many other files,
data, directories, etc, the b+trees will be 2 or 3 levels higher,
therefore this optimization will have a higher impact.
Several reports of a slow fiemap show up often, the two Link tags below
refer to two recent reports of such slowness. This patch, together with
the next ones in the series, is meant to address that.
Link: https://lore.kernel.org/linux-btrfs/21dd32c6-f1f9-f44a-466a-e18fdc6788a7@virtuozzo.com/
Link: https://lore.kernel.org/linux-btrfs/Ysace25wh5BbLd5f@atmark-techno.com/
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:28 +00:00
|
|
|
/* -1 means we are in the bytenr of the data extent. */
|
|
|
|
level = -1;
|
2014-09-10 20:20:45 +00:00
|
|
|
ULIST_ITER_INIT(&uiter);
|
2022-10-11 12:17:02 +00:00
|
|
|
ctx->use_path_cache = true;
|
2014-09-10 20:20:45 +00:00
|
|
|
while (1) {
|
btrfs: speedup checking for extent sharedness during fiemap
One of the most expensive tasks performed during fiemap is to check if
an extent is shared. This task has two major steps:
1) Check if the data extent is shared. This implies checking the extent
item in the extent tree, checking delayed references, etc. If we
find the data extent is directly shared, we terminate immediately;
2) If the data extent is not directly shared (its extent item has a
refcount of 1), then it may be shared if we have snapshots that share
subtrees of the inode's subvolume b+tree. So we check if the leaf
containing the file extent item is shared, then its parent node, then
the parent node of the parent node, etc, until we reach the root node
or we find one of them is shared - in which case we stop immediately.
During fiemap we process the extents of a file from left to right, from
file offset 0 to EOF. This means that we iterate b+tree leaves from left
to right, and has the implication that we keep repeating that second step
above several times for the same b+tree path of the inode's subvolume
b+tree.
For example, if we have two file extent items in leaf X, and the path to
leaf X is A -> B -> C -> X, then when we try to determine if the data
extent referenced by the first extent item is shared, we check if the data
extent is shared - if it's not, then we check if leaf X is shared, if not,
then we check if node C is shared, if not, then check if node B is shared,
if not than check if node A is shared. When we move to the next file
extent item, after determining the data extent is not shared, we repeat
the checks for X, C, B and A - doing all the expensive searches in the
extent tree, delayed refs, etc. If we have thousands of tile extents, then
we keep repeating the sharedness checks for the same paths over and over.
On a file that has no shared extents or only a small portion, it's easy
to see that this scales terribly with the number of extents in the file
and the sizes of the extent and subvolume b+trees.
This change eliminates the repeated sharedness check on extent buffers
by caching the results of the last path used. The results can be used as
long as no snapshots were created since they were cached (for not shared
extent buffers) or no roots were dropped since they were cached (for
shared extent buffers). This greatly reduces the time spent by fiemap for
files with thousands of extents and/or large extent and subvolume b+trees.
Example performance test:
$ cat fiemap-perf-test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 128K file extents (due to compression).
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 3597 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 2107 milliseconds (metadata cached)
After this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1646 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 698 milliseconds (metadata cached)
That's about 2.2x faster when no metadata is cached, and about 3x faster
when all metadata is cached. On a real filesystem with many other files,
data, directories, etc, the b+trees will be 2 or 3 levels higher,
therefore this optimization will have a higher impact.
Several reports of a slow fiemap show up often, the two Link tags below
refer to two recent reports of such slowness. This patch, together with
the next ones in the series, is meant to address that.
Link: https://lore.kernel.org/linux-btrfs/21dd32c6-f1f9-f44a-466a-e18fdc6788a7@virtuozzo.com/
Link: https://lore.kernel.org/linux-btrfs/Ysace25wh5BbLd5f@atmark-techno.com/
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:28 +00:00
|
|
|
bool is_shared;
|
|
|
|
bool cached;
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
walk_ctx.bytenr = bytenr;
|
|
|
|
ret = find_parent_nodes(&walk_ctx, &shared);
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
if (ret == BACKREF_FOUND_SHARED ||
|
|
|
|
ret == BACKREF_FOUND_NOT_SHARED) {
|
|
|
|
/* If shared must return 1, otherwise return 0. */
|
|
|
|
ret = (ret == BACKREF_FOUND_SHARED) ? 1 : 0;
|
btrfs: speedup checking for extent sharedness during fiemap
One of the most expensive tasks performed during fiemap is to check if
an extent is shared. This task has two major steps:
1) Check if the data extent is shared. This implies checking the extent
item in the extent tree, checking delayed references, etc. If we
find the data extent is directly shared, we terminate immediately;
2) If the data extent is not directly shared (its extent item has a
refcount of 1), then it may be shared if we have snapshots that share
subtrees of the inode's subvolume b+tree. So we check if the leaf
containing the file extent item is shared, then its parent node, then
the parent node of the parent node, etc, until we reach the root node
or we find one of them is shared - in which case we stop immediately.
During fiemap we process the extents of a file from left to right, from
file offset 0 to EOF. This means that we iterate b+tree leaves from left
to right, and has the implication that we keep repeating that second step
above several times for the same b+tree path of the inode's subvolume
b+tree.
For example, if we have two file extent items in leaf X, and the path to
leaf X is A -> B -> C -> X, then when we try to determine if the data
extent referenced by the first extent item is shared, we check if the data
extent is shared - if it's not, then we check if leaf X is shared, if not,
then we check if node C is shared, if not, then check if node B is shared,
if not than check if node A is shared. When we move to the next file
extent item, after determining the data extent is not shared, we repeat
the checks for X, C, B and A - doing all the expensive searches in the
extent tree, delayed refs, etc. If we have thousands of tile extents, then
we keep repeating the sharedness checks for the same paths over and over.
On a file that has no shared extents or only a small portion, it's easy
to see that this scales terribly with the number of extents in the file
and the sizes of the extent and subvolume b+trees.
This change eliminates the repeated sharedness check on extent buffers
by caching the results of the last path used. The results can be used as
long as no snapshots were created since they were cached (for not shared
extent buffers) or no roots were dropped since they were cached (for
shared extent buffers). This greatly reduces the time spent by fiemap for
files with thousands of extents and/or large extent and subvolume b+trees.
Example performance test:
$ cat fiemap-perf-test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 128K file extents (due to compression).
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 3597 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 2107 milliseconds (metadata cached)
After this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1646 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 698 milliseconds (metadata cached)
That's about 2.2x faster when no metadata is cached, and about 3x faster
when all metadata is cached. On a real filesystem with many other files,
data, directories, etc, the b+trees will be 2 or 3 levels higher,
therefore this optimization will have a higher impact.
Several reports of a slow fiemap show up often, the two Link tags below
refer to two recent reports of such slowness. This patch, together with
the next ones in the series, is meant to address that.
Link: https://lore.kernel.org/linux-btrfs/21dd32c6-f1f9-f44a-466a-e18fdc6788a7@virtuozzo.com/
Link: https://lore.kernel.org/linux-btrfs/Ysace25wh5BbLd5f@atmark-techno.com/
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:28 +00:00
|
|
|
if (level >= 0)
|
2022-10-11 12:17:02 +00:00
|
|
|
store_backref_shared_cache(ctx, root, bytenr,
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
level, ret == 1);
|
2014-09-10 20:20:45 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (ret < 0 && ret != -ENOENT)
|
|
|
|
break;
|
2015-05-19 19:49:50 +00:00
|
|
|
ret = 0;
|
btrfs: skip unnecessary extent buffer sharedness checks during fiemap
During fiemap, for each file extent we find, we must check if it's shared
or not. The sharedness check starts by verifying if the extent is directly
shared (its refcount in the extent tree is > 1), and if it is not directly
shared, then we will check if every node in the subvolume b+tree leading
from the root to the leaf that has the file extent item (in reverse order),
is shared (through snapshots).
However this second step is not needed if our extent was created in a
transaction more recent than the last transaction where a snapshot of the
inode's root happened, because it can't be shared indirectly (through
shared subtrees) without a snapshot created in a more recent transaction.
So grab the generation of the extent from the extent map and pass it to
btrfs_is_data_extent_shared(), which will skip this second phase when the
generation is more recent than the root's last snapshot value. Note that
we skip this optimization if the extent map is the result of merging 2
or more extent maps, because in this case its generation is the maximum
of the generations of all merged extent maps.
The fact the we use extent maps and they can be merged despite the
underlying extents being distinct (different file extent items in the
subvolume b+tree and different extent items in the extent b+tree), can
result in some bugs when reporting shared extents. But this is a problem
of the current implementation of fiemap relying on extent maps.
One example where we get incorrect results is:
$ cat fiemap-bug.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
# Create a file with two 256K extents.
# Since there is no other write activity, they will be contiguous,
# and their extent maps merged, despite having two distinct extents.
xfs_io -f -c "pwrite -S 0xab 0 256K" \
-c "fsync" \
-c "pwrite -S 0xcd 256K 256K" \
-c "fsync" \
$MNT/foo
# Now clone only the second extent into another file.
xfs_io -f -c "reflink $MNT/foo 256K 0 256K" $MNT/bar
# Filefrag will report a single 512K extent, and say it's not shared.
echo
filefrag -v $MNT/foo
umount $MNT
Running the reproducer:
$ ./fiemap-bug.sh
wrote 262144/262144 bytes at offset 0
256 KiB, 64 ops; 0.0038 sec (65.479 MiB/sec and 16762.7030 ops/sec)
wrote 262144/262144 bytes at offset 262144
256 KiB, 64 ops; 0.0040 sec (61.125 MiB/sec and 15647.9218 ops/sec)
linked 262144/262144 bytes at offset 0
256 KiB, 1 ops; 0.0002 sec (1.034 GiB/sec and 4237.2881 ops/sec)
Filesystem type is: 9123683e
File size of /mnt/sdj/foo is 524288 (128 blocks of 4096 bytes)
ext: logical_offset: physical_offset: length: expected: flags:
0: 0.. 127: 3328.. 3455: 128: last,eof
/mnt/sdj/foo: 1 extent found
We end up reporting that we have a single 512K that is not shared, however
we have two 256K extents, and the second one is shared. Changing the
reproducer to clone instead the first extent into file 'bar', makes us
report a single 512K extent that is shared, which is algo incorrect since
we have two 256K extents and only the first one is shared.
This is z problem that existed before this change, and remains after this
change, as it can't be easily fixed. The next patch in the series reworks
fiemap to primarily use file extent items instead of extent maps (except
for checking for delalloc ranges), with the goal of improving its
scalability and performance, but it also ends up fixing this particular
bug caused by extent map merging.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:29 +00:00
|
|
|
|
btrfs: ignore fiemap path cache if we have multiple leaves for a data extent
The path cache used during fiemap used to determine the sharedness of
extent buffers in a path from a leaf containing a file extent item
pointing to our data extent up to the root node of the tree, is meant to
be used for a single path. Having a single path is by far the most common
case, and therefore worth to optimize for, but it's possible to actually
have multiple paths because we have 2 or more leaves.
If we have multiple leaves, the 'level' variable keeps getting incremented
in each iteration of the while loop at btrfs_is_data_extent_shared(),
which means we will treat the second leaf in the 'tmp' ulist as a level 1
node, and so forth. In the worst case this can lead to getting a level
greater than or equals to BTRFS_MAX_LEVEL (8), which will trigger a
WARN_ON_ONCE() in the functions to lookup from or store in the path cache
(lookup_backref_shared_cache() and store_backref_shared_cache()). If the
current level never goes beyond 8, due to shared nodes in the paths and
a fs tree height smaller than 8, it can still result in incorrectly
marking one leaf as shared because some other leaf is shared and is stored
one level below that other leaf, as when storing a true sharedness value
in the cache results in updating the sharedness to true of all entries in
the cache below the current level.
Having multiple leaves happens in a case like the following:
- We have a file extent item point to data extent at bytenr X, for
a file range [0, 1M[ for example;
- At this moment we have an extent data ref for the extent, with
an offset of 0 and a count of 1;
- A write into the middle of the extent happens, file range [64K, 128K)
so the file extent item is split into two (at btrfs_drop_extents()):
1) One for file range [0, 64K), with a length (num_bytes field) of
64K and an extent offset of 0;
2) Another one for file range [128K, 1M), with a length of 896K
(1M - 128K) and an extent offset of 128K.
- At this moment the two file extent items are located in the same
leaf;
- A new file extent item for the range [64K, 128K), pointing to a new
data extent, is inserted in the leaf. This results in a leaf split
and now those two file extent items pointing to data extent X end
up located in different leaves;
- Once delayed refs are run, we still have a single extent data ref
item for our data extent at bytenr X, for offset 0, but now with a
count of 2 instead of 1;
- So during fiemap, at btrfs_is_data_extent_shared(), after we call
find_parent_nodes() for the data extent, we get two leaves, since
we have two file extent items point to data extent at bytenr X that
are located in two different leaves.
So skip the use of the path cache when we get more than one leaf.
Fixes: 12a824dc67a61e ("btrfs: speedup checking for extent sharedness during fiemap")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:53 +00:00
|
|
|
/*
|
|
|
|
* If our data extent was not directly shared (without multiple
|
|
|
|
* reference items), than it might have a single reference item
|
|
|
|
* with a count > 1 for the same offset, which means there are 2
|
|
|
|
* (or more) file extent items that point to the data extent -
|
|
|
|
* this happens when a file extent item needs to be split and
|
|
|
|
* then one item gets moved to another leaf due to a b+tree leaf
|
|
|
|
* split when inserting some item. In this case the file extent
|
|
|
|
* items may be located in different leaves and therefore some
|
|
|
|
* of the leaves may be referenced through shared subtrees while
|
|
|
|
* others are not. Since our extent buffer cache only works for
|
|
|
|
* a single path (by far the most common case and simpler to
|
|
|
|
* deal with), we can not use it if we have multiple leaves
|
|
|
|
* (which implies multiple paths).
|
|
|
|
*/
|
2022-10-11 12:17:03 +00:00
|
|
|
if (level == -1 && ctx->refs.nnodes > 1)
|
2022-10-11 12:17:02 +00:00
|
|
|
ctx->use_path_cache = false;
|
btrfs: ignore fiemap path cache if we have multiple leaves for a data extent
The path cache used during fiemap used to determine the sharedness of
extent buffers in a path from a leaf containing a file extent item
pointing to our data extent up to the root node of the tree, is meant to
be used for a single path. Having a single path is by far the most common
case, and therefore worth to optimize for, but it's possible to actually
have multiple paths because we have 2 or more leaves.
If we have multiple leaves, the 'level' variable keeps getting incremented
in each iteration of the while loop at btrfs_is_data_extent_shared(),
which means we will treat the second leaf in the 'tmp' ulist as a level 1
node, and so forth. In the worst case this can lead to getting a level
greater than or equals to BTRFS_MAX_LEVEL (8), which will trigger a
WARN_ON_ONCE() in the functions to lookup from or store in the path cache
(lookup_backref_shared_cache() and store_backref_shared_cache()). If the
current level never goes beyond 8, due to shared nodes in the paths and
a fs tree height smaller than 8, it can still result in incorrectly
marking one leaf as shared because some other leaf is shared and is stored
one level below that other leaf, as when storing a true sharedness value
in the cache results in updating the sharedness to true of all entries in
the cache below the current level.
Having multiple leaves happens in a case like the following:
- We have a file extent item point to data extent at bytenr X, for
a file range [0, 1M[ for example;
- At this moment we have an extent data ref for the extent, with
an offset of 0 and a count of 1;
- A write into the middle of the extent happens, file range [64K, 128K)
so the file extent item is split into two (at btrfs_drop_extents()):
1) One for file range [0, 64K), with a length (num_bytes field) of
64K and an extent offset of 0;
2) Another one for file range [128K, 1M), with a length of 896K
(1M - 128K) and an extent offset of 128K.
- At this moment the two file extent items are located in the same
leaf;
- A new file extent item for the range [64K, 128K), pointing to a new
data extent, is inserted in the leaf. This results in a leaf split
and now those two file extent items pointing to data extent X end
up located in different leaves;
- Once delayed refs are run, we still have a single extent data ref
item for our data extent at bytenr X, for offset 0, but now with a
count of 2 instead of 1;
- So during fiemap, at btrfs_is_data_extent_shared(), after we call
find_parent_nodes() for the data extent, we get two leaves, since
we have two file extent items point to data extent at bytenr X that
are located in two different leaves.
So skip the use of the path cache when we get more than one leaf.
Fixes: 12a824dc67a61e ("btrfs: speedup checking for extent sharedness during fiemap")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:53 +00:00
|
|
|
|
btrfs: speedup checking for extent sharedness during fiemap
One of the most expensive tasks performed during fiemap is to check if
an extent is shared. This task has two major steps:
1) Check if the data extent is shared. This implies checking the extent
item in the extent tree, checking delayed references, etc. If we
find the data extent is directly shared, we terminate immediately;
2) If the data extent is not directly shared (its extent item has a
refcount of 1), then it may be shared if we have snapshots that share
subtrees of the inode's subvolume b+tree. So we check if the leaf
containing the file extent item is shared, then its parent node, then
the parent node of the parent node, etc, until we reach the root node
or we find one of them is shared - in which case we stop immediately.
During fiemap we process the extents of a file from left to right, from
file offset 0 to EOF. This means that we iterate b+tree leaves from left
to right, and has the implication that we keep repeating that second step
above several times for the same b+tree path of the inode's subvolume
b+tree.
For example, if we have two file extent items in leaf X, and the path to
leaf X is A -> B -> C -> X, then when we try to determine if the data
extent referenced by the first extent item is shared, we check if the data
extent is shared - if it's not, then we check if leaf X is shared, if not,
then we check if node C is shared, if not, then check if node B is shared,
if not than check if node A is shared. When we move to the next file
extent item, after determining the data extent is not shared, we repeat
the checks for X, C, B and A - doing all the expensive searches in the
extent tree, delayed refs, etc. If we have thousands of tile extents, then
we keep repeating the sharedness checks for the same paths over and over.
On a file that has no shared extents or only a small portion, it's easy
to see that this scales terribly with the number of extents in the file
and the sizes of the extent and subvolume b+trees.
This change eliminates the repeated sharedness check on extent buffers
by caching the results of the last path used. The results can be used as
long as no snapshots were created since they were cached (for not shared
extent buffers) or no roots were dropped since they were cached (for
shared extent buffers). This greatly reduces the time spent by fiemap for
files with thousands of extents and/or large extent and subvolume b+trees.
Example performance test:
$ cat fiemap-perf-test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 128K file extents (due to compression).
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 3597 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 2107 milliseconds (metadata cached)
After this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1646 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 698 milliseconds (metadata cached)
That's about 2.2x faster when no metadata is cached, and about 3x faster
when all metadata is cached. On a real filesystem with many other files,
data, directories, etc, the b+trees will be 2 or 3 levels higher,
therefore this optimization will have a higher impact.
Several reports of a slow fiemap show up often, the two Link tags below
refer to two recent reports of such slowness. This patch, together with
the next ones in the series, is meant to address that.
Link: https://lore.kernel.org/linux-btrfs/21dd32c6-f1f9-f44a-466a-e18fdc6788a7@virtuozzo.com/
Link: https://lore.kernel.org/linux-btrfs/Ysace25wh5BbLd5f@atmark-techno.com/
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:28 +00:00
|
|
|
if (level >= 0)
|
2022-10-11 12:17:02 +00:00
|
|
|
store_backref_shared_cache(ctx, root, bytenr,
|
btrfs: speedup checking for extent sharedness during fiemap
One of the most expensive tasks performed during fiemap is to check if
an extent is shared. This task has two major steps:
1) Check if the data extent is shared. This implies checking the extent
item in the extent tree, checking delayed references, etc. If we
find the data extent is directly shared, we terminate immediately;
2) If the data extent is not directly shared (its extent item has a
refcount of 1), then it may be shared if we have snapshots that share
subtrees of the inode's subvolume b+tree. So we check if the leaf
containing the file extent item is shared, then its parent node, then
the parent node of the parent node, etc, until we reach the root node
or we find one of them is shared - in which case we stop immediately.
During fiemap we process the extents of a file from left to right, from
file offset 0 to EOF. This means that we iterate b+tree leaves from left
to right, and has the implication that we keep repeating that second step
above several times for the same b+tree path of the inode's subvolume
b+tree.
For example, if we have two file extent items in leaf X, and the path to
leaf X is A -> B -> C -> X, then when we try to determine if the data
extent referenced by the first extent item is shared, we check if the data
extent is shared - if it's not, then we check if leaf X is shared, if not,
then we check if node C is shared, if not, then check if node B is shared,
if not than check if node A is shared. When we move to the next file
extent item, after determining the data extent is not shared, we repeat
the checks for X, C, B and A - doing all the expensive searches in the
extent tree, delayed refs, etc. If we have thousands of tile extents, then
we keep repeating the sharedness checks for the same paths over and over.
On a file that has no shared extents or only a small portion, it's easy
to see that this scales terribly with the number of extents in the file
and the sizes of the extent and subvolume b+trees.
This change eliminates the repeated sharedness check on extent buffers
by caching the results of the last path used. The results can be used as
long as no snapshots were created since they were cached (for not shared
extent buffers) or no roots were dropped since they were cached (for
shared extent buffers). This greatly reduces the time spent by fiemap for
files with thousands of extents and/or large extent and subvolume b+trees.
Example performance test:
$ cat fiemap-perf-test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 128K file extents (due to compression).
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 3597 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 2107 milliseconds (metadata cached)
After this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1646 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 698 milliseconds (metadata cached)
That's about 2.2x faster when no metadata is cached, and about 3x faster
when all metadata is cached. On a real filesystem with many other files,
data, directories, etc, the b+trees will be 2 or 3 levels higher,
therefore this optimization will have a higher impact.
Several reports of a slow fiemap show up often, the two Link tags below
refer to two recent reports of such slowness. This patch, together with
the next ones in the series, is meant to address that.
Link: https://lore.kernel.org/linux-btrfs/21dd32c6-f1f9-f44a-466a-e18fdc6788a7@virtuozzo.com/
Link: https://lore.kernel.org/linux-btrfs/Ysace25wh5BbLd5f@atmark-techno.com/
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:28 +00:00
|
|
|
level, false);
|
2022-10-11 12:17:03 +00:00
|
|
|
node = ulist_next(&ctx->refs, &uiter);
|
2014-09-10 20:20:45 +00:00
|
|
|
if (!node)
|
|
|
|
break;
|
|
|
|
bytenr = node->val;
|
btrfs: speedup checking for extent sharedness during fiemap
One of the most expensive tasks performed during fiemap is to check if
an extent is shared. This task has two major steps:
1) Check if the data extent is shared. This implies checking the extent
item in the extent tree, checking delayed references, etc. If we
find the data extent is directly shared, we terminate immediately;
2) If the data extent is not directly shared (its extent item has a
refcount of 1), then it may be shared if we have snapshots that share
subtrees of the inode's subvolume b+tree. So we check if the leaf
containing the file extent item is shared, then its parent node, then
the parent node of the parent node, etc, until we reach the root node
or we find one of them is shared - in which case we stop immediately.
During fiemap we process the extents of a file from left to right, from
file offset 0 to EOF. This means that we iterate b+tree leaves from left
to right, and has the implication that we keep repeating that second step
above several times for the same b+tree path of the inode's subvolume
b+tree.
For example, if we have two file extent items in leaf X, and the path to
leaf X is A -> B -> C -> X, then when we try to determine if the data
extent referenced by the first extent item is shared, we check if the data
extent is shared - if it's not, then we check if leaf X is shared, if not,
then we check if node C is shared, if not, then check if node B is shared,
if not than check if node A is shared. When we move to the next file
extent item, after determining the data extent is not shared, we repeat
the checks for X, C, B and A - doing all the expensive searches in the
extent tree, delayed refs, etc. If we have thousands of tile extents, then
we keep repeating the sharedness checks for the same paths over and over.
On a file that has no shared extents or only a small portion, it's easy
to see that this scales terribly with the number of extents in the file
and the sizes of the extent and subvolume b+trees.
This change eliminates the repeated sharedness check on extent buffers
by caching the results of the last path used. The results can be used as
long as no snapshots were created since they were cached (for not shared
extent buffers) or no roots were dropped since they were cached (for
shared extent buffers). This greatly reduces the time spent by fiemap for
files with thousands of extents and/or large extent and subvolume b+trees.
Example performance test:
$ cat fiemap-perf-test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 128K file extents (due to compression).
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 3597 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 2107 milliseconds (metadata cached)
After this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1646 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 698 milliseconds (metadata cached)
That's about 2.2x faster when no metadata is cached, and about 3x faster
when all metadata is cached. On a real filesystem with many other files,
data, directories, etc, the b+trees will be 2 or 3 levels higher,
therefore this optimization will have a higher impact.
Several reports of a slow fiemap show up often, the two Link tags below
refer to two recent reports of such slowness. This patch, together with
the next ones in the series, is meant to address that.
Link: https://lore.kernel.org/linux-btrfs/21dd32c6-f1f9-f44a-466a-e18fdc6788a7@virtuozzo.com/
Link: https://lore.kernel.org/linux-btrfs/Ysace25wh5BbLd5f@atmark-techno.com/
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:28 +00:00
|
|
|
level++;
|
2022-10-11 12:17:02 +00:00
|
|
|
cached = lookup_backref_shared_cache(ctx, root, bytenr, level,
|
btrfs: speedup checking for extent sharedness during fiemap
One of the most expensive tasks performed during fiemap is to check if
an extent is shared. This task has two major steps:
1) Check if the data extent is shared. This implies checking the extent
item in the extent tree, checking delayed references, etc. If we
find the data extent is directly shared, we terminate immediately;
2) If the data extent is not directly shared (its extent item has a
refcount of 1), then it may be shared if we have snapshots that share
subtrees of the inode's subvolume b+tree. So we check if the leaf
containing the file extent item is shared, then its parent node, then
the parent node of the parent node, etc, until we reach the root node
or we find one of them is shared - in which case we stop immediately.
During fiemap we process the extents of a file from left to right, from
file offset 0 to EOF. This means that we iterate b+tree leaves from left
to right, and has the implication that we keep repeating that second step
above several times for the same b+tree path of the inode's subvolume
b+tree.
For example, if we have two file extent items in leaf X, and the path to
leaf X is A -> B -> C -> X, then when we try to determine if the data
extent referenced by the first extent item is shared, we check if the data
extent is shared - if it's not, then we check if leaf X is shared, if not,
then we check if node C is shared, if not, then check if node B is shared,
if not than check if node A is shared. When we move to the next file
extent item, after determining the data extent is not shared, we repeat
the checks for X, C, B and A - doing all the expensive searches in the
extent tree, delayed refs, etc. If we have thousands of tile extents, then
we keep repeating the sharedness checks for the same paths over and over.
On a file that has no shared extents or only a small portion, it's easy
to see that this scales terribly with the number of extents in the file
and the sizes of the extent and subvolume b+trees.
This change eliminates the repeated sharedness check on extent buffers
by caching the results of the last path used. The results can be used as
long as no snapshots were created since they were cached (for not shared
extent buffers) or no roots were dropped since they were cached (for
shared extent buffers). This greatly reduces the time spent by fiemap for
files with thousands of extents and/or large extent and subvolume b+trees.
Example performance test:
$ cat fiemap-perf-test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 128K file extents (due to compression).
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Before this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 3597 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 2107 milliseconds (metadata cached)
After this patch:
$ ./fiemap-perf-test.sh
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1646 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 698 milliseconds (metadata cached)
That's about 2.2x faster when no metadata is cached, and about 3x faster
when all metadata is cached. On a real filesystem with many other files,
data, directories, etc, the b+trees will be 2 or 3 levels higher,
therefore this optimization will have a higher impact.
Several reports of a slow fiemap show up often, the two Link tags below
refer to two recent reports of such slowness. This patch, together with
the next ones in the series, is meant to address that.
Link: https://lore.kernel.org/linux-btrfs/21dd32c6-f1f9-f44a-466a-e18fdc6788a7@virtuozzo.com/
Link: https://lore.kernel.org/linux-btrfs/Ysace25wh5BbLd5f@atmark-techno.com/
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-01 13:18:28 +00:00
|
|
|
&is_shared);
|
|
|
|
if (cached) {
|
|
|
|
ret = (is_shared ? 1 : 0);
|
|
|
|
break;
|
|
|
|
}
|
2018-03-14 15:03:11 +00:00
|
|
|
shared.share_count = 0;
|
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are
using a share context (we are being called through fiemap), whenever we
find a delayed data reference for an inode different from the one we are
interested in, then we immediately exit and consider the data extent as
shared. This is wrong, because:
1) This might be a DROP reference that will cancel out a reference in the
extent tree;
2) Even if it's an ADD reference, it may be followed by a DROP reference
that cancels it out.
In either case we should not exit immediately.
Fix this by never exiting when we find a delayed data reference for
another inode - instead add the reference and if it does not cancel out
other delayed reference, we will exit early when we call
extent_is_shared() after processing all delayed references. If we find
a drop reference, then signal the code that processes references from
the extent tree (add_inline_refs() and add_keyed_refs()) to not exit
immediately if it finds there a reference for another inode, since we
have delayed drop references that may cancel it out. In this later case
we exit once we don't have references in the rb trees that cancel out
each other and have two references for different inodes.
Example reproducer for case 1):
$ cat test-1.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
Example reproducer for case 2):
$ cat test-2.sh
#!/bin/bash
DEV=/dev/sdj
MNT=/mnt/sdj
mkfs.btrfs -f $DEV
mount $DEV $MNT
xfs_io -f -c "pwrite 0 64K" $MNT/foo
cp --reflink=always $MNT/foo $MNT/bar
# Flush delayed references to the extent tree and commit current
# transaction.
sync
echo
echo "fiemap after cloning:"
xfs_io -c "fiemap -v" $MNT/foo
rm -f $MNT/bar
echo
echo "fiemap after removing file bar:"
xfs_io -c "fiemap -v" $MNT/foo
umount $MNT
Running it before this patch, the extent is still listed as shared, it has
the flag 0x2000 (FIEMAP_EXTENT_SHARED) set:
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
After this patch, after deleting bar in both tests, the extent is not
reported with the 0x2000 flag anymore, it gets only the flag 0x1
(which is FIEMAP_EXTENT_LAST):
$ ./test-1.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
$ ./test-2.sh
fiemap after cloning:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x2001
fiemap after removing file bar:
/mnt/sdj/foo:
EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS
0: [0..127]: 26624..26751 128 0x1
These tests will later be converted to a test case for fstests.
Fixes: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:16:51 +00:00
|
|
|
shared.have_delayed_delete_refs = false;
|
2014-09-10 20:20:45 +00:00
|
|
|
cond_resched();
|
|
|
|
}
|
2017-06-29 03:56:58 +00:00
|
|
|
|
btrfs: cache sharedness of the last few data extents during fiemap
During fiemap we process all the file extent items of an inode, by their
file offset order (left to right b+tree order), and then check if the data
extent they point at is shared or not. Until now we didn't cache those
results, we only did it for b+tree nodes/leaves since for each unique
b+tree path we have access to hundreds of file extent items. However, it
is also common to repeat checking the sharedness of a particular data
extent in a very short time window, and the cases that lead to that are
the following:
1) COW writes.
If have a file extent item like this:
[ bytenr X, offset = 0, num_bytes = 512K ]
file offset 0 512K
Then a 4K write into file offset 64K happens, we end up with the
following file extent item layout:
[ bytenr X, offset = 0, num_bytes = 64K ]
file offset 0 64K
[ bytenr Y, offset = 0, num_bytes = 4K ]
file offset 64K 68K
[ bytenr X, offset = 68K, num_bytes = 444K ]
file offset 68K 512K
So during fiemap we well check for the sharedness of the data extent
with bytenr X twice. Typically for COW writes and for at least
moderately updated files, we end up with many file extent items that
point to different sections of the same data extent.
2) Writing into a NOCOW file after a snapshot is taken.
This happens if the target extent was created in a generation older
than the generation where the last snapshot for the root (the tree the
inode belongs to) was made.
This leads to a scenario like the previous one.
3) Writing into sections of a preallocated extent.
For example if a file has the following layout:
[ bytenr X, offset = 0, num_bytes = 1M, type = prealloc ]
0 1M
After doing a 4K write into file offset 0 and another 4K write into
offset 512K, we get the following layout:
[ bytenr X, offset = 0, num_bytes = 4K, type = regular ]
0 4K
[ bytenr X, offset = 4K, num_bytes = 508K, type = prealloc ]
4K 512K
[ bytenr X, offset = 512K, num_bytes = 4K, type = regular ]
512K 516K
[ bytenr X, offset = 516K, num_bytes = 508K, type = prealloc ]
516K 1M
So we end up with 4 consecutive file extent items pointing to the data
extent at bytenr X.
4) Hole punching in the middle of an extent.
For example if a file has the following file extent item:
[ bytenr X, offset = 0, num_bytes = 8M ]
0 8M
And then hole is punched for the file range [4M, 6M[, we our file
extent item split into two:
[ bytenr X, offset = 0, num_bytes = 4M ]
0 4M
[ 2M hole, implicit or explicit depending on NO_HOLES feature ]
4M 6M
[ bytenr X, offset = 6M, num_bytes = 2M ]
6M 8M
Again, we end up with two file extent items pointing to the same
data extent.
5) When reflinking (clone and deduplication) within the same file.
This is probably the least common case of all.
In cases 1, 2, 4 and 4, when we have multiple file extent items that point
to the same data extent, their distance is usually short, typically
separated by a few slots in a b+tree leaf (or across sibling leaves). For
case 5, the distance can vary a lot, but it's typically the less common
case.
This change caches the result of the sharedness checks for data extents,
but only for the last 8 extents that we notice that our inode refers to
with multiple file extent items. Whenever we want to check if a data
extent is shared, we lookup the cache which consists of doing a linear
scan of an 8 elements array, and if we find the data extent there, we
return the result and don't check the extent tree and delayed refs.
The array/cache is small so that doing the search has no noticeable
negative impact on the performance in case we don't have file extent items
within a distance of 8 slots that point to the same data extent.
Slots in the cache/array are overwritten in a simple round robin fashion,
as that approach fits very well.
Using this simple approach with only the last 8 data extents seen is
effective as usually when multiple file extents items point to the same
data extent, their distance is within 8 slots. It also uses very little
memory and the time to cache a result or lookup the cache is negligible.
The following test was run on non-debug kernel (Debian's default kernel
config) to measure the impact in the case of COW writes (first example
given above), where we run fiemap after overwriting 33% of the blocks of
a file:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
FILE_SIZE=$((1 * 1024 * 1024 * 1024))
# Create the file full of 1M extents.
xfs_io -f -s -c "pwrite -b 1M -S 0xab 0 $FILE_SIZE" $MNT/foobar
block_count=$((FILE_SIZE / 4096))
# Overwrite about 33% of the file blocks.
overwrite_count=$((block_count / 3))
echo -e "\nOverwriting $overwrite_count 4K blocks (out of $block_count)..."
RANDOM=123
for ((i = 1; i <= $overwrite_count; i++)); do
off=$(((RANDOM % block_count) * 4096))
xfs_io -c "pwrite -S 0xcd $off 4K" $MNT/foobar > /dev/null
echo -ne "\r$i blocks overwritten..."
done
echo -e "\n"
# Unmount and mount to clear all cached metadata.
umount $MNT
mount $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds"
umount $MNT
Result before applying this patch:
fiemap took 128 milliseconds
Result after applying this patch:
fiemap took 92 milliseconds (-28.1%)
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:06 +00:00
|
|
|
/*
|
|
|
|
* Cache the sharedness result for the data extent if we know our inode
|
|
|
|
* has more than 1 file extent item that refers to the data extent.
|
|
|
|
*/
|
|
|
|
if (ret >= 0 && shared.self_ref_count > 1) {
|
|
|
|
int slot = ctx->prev_extents_cache_slot;
|
|
|
|
|
|
|
|
ctx->prev_extents_cache[slot].bytenr = shared.data_bytenr;
|
|
|
|
ctx->prev_extents_cache[slot].is_shared = (ret == 1);
|
|
|
|
|
|
|
|
slot = (slot + 1) % BTRFS_BACKREF_CTX_PREV_EXTENTS_SIZE;
|
|
|
|
ctx->prev_extents_cache_slot = slot;
|
|
|
|
}
|
|
|
|
|
2017-06-29 03:56:58 +00:00
|
|
|
if (trans) {
|
2014-09-10 20:20:45 +00:00
|
|
|
btrfs_put_tree_mod_seq(fs_info, &elem);
|
2017-06-29 03:56:58 +00:00
|
|
|
btrfs_end_transaction(trans);
|
|
|
|
} else {
|
2014-09-10 20:20:45 +00:00
|
|
|
up_read(&fs_info->commit_root_sem);
|
2017-06-29 03:56:58 +00:00
|
|
|
}
|
Btrfs: do not start a transaction during fiemap
During fiemap, for regular extents (non inline) we need to check if they
are shared and if they are, set the shared bit. Checking if an extent is
shared requires checking the delayed references of the currently running
transaction, since some reference might have not yet hit the extent tree
and be only in the in-memory delayed references.
However we were using a transaction join for this, which creates a new
transaction when there is no transaction currently running. That means
that two more potential failures can happen: creating the transaction and
committing it. Further, if no write activity is currently happening in the
system, and fiemap calls keep being done, we end up creating and
committing transactions that do nothing.
In some extreme cases this can result in the commit of the transaction
created by fiemap to fail with ENOSPC when updating the root item of a
subvolume tree because a join does not reserve any space, leading to a
trace like the following:
heisenberg kernel: ------------[ cut here ]------------
heisenberg kernel: BTRFS: Transaction aborted (error -28)
heisenberg kernel: WARNING: CPU: 0 PID: 7137 at fs/btrfs/root-tree.c:136 btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: CPU: 0 PID: 7137 Comm: btrfs-transacti Not tainted 4.19.0-4-amd64 #1 Debian 4.19.28-2
heisenberg kernel: Hardware name: FUJITSU LIFEBOOK U757/FJNB2A5, BIOS Version 1.21 03/19/2018
heisenberg kernel: RIP: 0010:btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: RSP: 0018:ffffb5448828bd40 EFLAGS: 00010286
heisenberg kernel: RAX: 0000000000000000 RBX: ffff8ed56bccef50 RCX: 0000000000000006
heisenberg kernel: RDX: 0000000000000007 RSI: 0000000000000092 RDI: ffff8ed6bda166a0
heisenberg kernel: RBP: 00000000ffffffe4 R08: 00000000000003df R09: 0000000000000007
heisenberg kernel: R10: 0000000000000000 R11: 0000000000000001 R12: ffff8ed63396a078
heisenberg kernel: R13: ffff8ed092d7c800 R14: ffff8ed64f5db028 R15: ffff8ed6bd03d068
heisenberg kernel: FS: 0000000000000000(0000) GS:ffff8ed6bda00000(0000) knlGS:0000000000000000
heisenberg kernel: CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
heisenberg kernel: CR2: 00007f46f75f8000 CR3: 0000000310a0a002 CR4: 00000000003606f0
heisenberg kernel: DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
heisenberg kernel: DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
heisenberg kernel: Call Trace:
heisenberg kernel: commit_fs_roots+0x166/0x1d0 [btrfs]
heisenberg kernel: ? _cond_resched+0x15/0x30
heisenberg kernel: ? btrfs_run_delayed_refs+0xac/0x180 [btrfs]
heisenberg kernel: btrfs_commit_transaction+0x2bd/0x870 [btrfs]
heisenberg kernel: ? start_transaction+0x9d/0x3f0 [btrfs]
heisenberg kernel: transaction_kthread+0x147/0x180 [btrfs]
heisenberg kernel: ? btrfs_cleanup_transaction+0x530/0x530 [btrfs]
heisenberg kernel: kthread+0x112/0x130
heisenberg kernel: ? kthread_bind+0x30/0x30
heisenberg kernel: ret_from_fork+0x35/0x40
heisenberg kernel: ---[ end trace 05de912e30e012d9 ]---
Since fiemap (and btrfs_check_shared()) is a read-only operation, do not do
a transaction join to avoid the overhead of creating a new transaction (if
there is currently no running transaction) and introducing a potential
point of failure when the new transaction gets committed, instead use a
transaction attach to grab a handle for the currently running transaction
if any.
Reported-by: Christoph Anton Mitterer <calestyo@scientia.net>
Link: https://lore.kernel.org/linux-btrfs/b2a668d7124f1d3e410367f587926f622b3f03a4.camel@scientia.net/
Fixes: afce772e87c36c ("btrfs: fix check_shared for fiemap ioctl")
CC: stable@vger.kernel.org # 4.14+
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-04-15 13:50:51 +00:00
|
|
|
out:
|
2022-10-11 12:17:03 +00:00
|
|
|
ulist_release(&ctx->refs);
|
btrfs: avoid duplicated resolution of indirect backrefs during fiemap
During fiemap, when determining if a data extent is shared or not, if we
don't find the extent is directly shared, then we need to determine if
it's shared through subtrees. For that we need to resolve the indirect
reference we found in order to figure out the path in the inode's fs tree,
which is a path starting at the fs tree's root node and going down to the
leaf that contains the file extent item that points to the data extent.
We then proceed to determine if any extent buffer in that path is shared
with other trees or not.
Currently whenever we find the data extent that a file extent item points
to is not directly shared, we always resolve the path in the fs tree, and
then check if any extent buffer in the path is shared. This is a lot of
work and when we have file extent items that belong to the same leaf, we
have the same path, so we only need to calculate it once.
This change does that, it keeps track of the current and previous leaf,
and when we find that a data extent is not directly shared, we try to
compute the fs tree path only once and then use it for every other file
extent item in the same leaf, using the existing cached path result for
the leaf as long as the cache results are valid.
This saves us from doing expensive b+tree searches in the fs tree of our
target inode, as well as other minor work.
The following test was run on a non-debug kernel (Debian's default kernel
config):
$ cat test-with-snapshots.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
umount $DEV &> /dev/null
mkfs.btrfs -f $DEV
# Use compression to quickly create files with a lot of extents
# (each with a size of 128K).
mount -o compress=lzo $DEV $MNT
# 40G gives 327680 extents, each with a size of 128K.
xfs_io -f -c "pwrite -S 0xab -b 1M 0 40G" $MNT/foobar
# Add some more files to increase the size of the fs and extent
# trees (in the real world there's a lot of files and extents
# from other files).
xfs_io -f -c "pwrite -S 0xcd -b 1M 0 20G" $MNT/file1
xfs_io -f -c "pwrite -S 0xef -b 1M 0 20G" $MNT/file2
xfs_io -f -c "pwrite -S 0x73 -b 1M 0 20G" $MNT/file3
# Create a snapshot so all the extents become indirectly shared
# through subtrees, with a generation less than or equals to the
# generation used to create the snapshot.
btrfs subvolume snapshot -r $MNT $MNT/snap1
umount $MNT
mount -o compress=lzo $DEV $MNT
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata not cached)"
echo
start=$(date +%s%N)
filefrag $MNT/foobar
end=$(date +%s%N)
dur=$(( (end - start) / 1000000 ))
echo "fiemap took $dur milliseconds (metadata cached)"
umount $MNT
Result before applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 1204 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 729 milliseconds (metadata cached)
Result after applying this patch:
(...)
/mnt/sdi/foobar: 327680 extents found
fiemap took 732 milliseconds (metadata not cached)
/mnt/sdi/foobar: 327680 extents found
fiemap took 421 milliseconds (metadata cached)
That's a -46.1% total reduction for the metadata not cached case, and
a -42.2% reduction for the cached metadata case.
The test is somewhat limited in the sense the gains may be higher in
practice, because in the test the filesystem is small, so we have small
fs and extent trees, plus there's no concurrent access to the trees as
well, therefore no lock contention there.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-10-11 12:17:08 +00:00
|
|
|
ctx->prev_leaf_bytenr = ctx->curr_leaf_bytenr;
|
|
|
|
|
2014-09-10 20:20:45 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-08-08 18:32:27 +00:00
|
|
|
int btrfs_find_one_extref(struct btrfs_root *root, u64 inode_objectid,
|
|
|
|
u64 start_off, struct btrfs_path *path,
|
|
|
|
struct btrfs_inode_extref **ret_extref,
|
|
|
|
u64 *found_off)
|
|
|
|
{
|
|
|
|
int ret, slot;
|
|
|
|
struct btrfs_key key;
|
|
|
|
struct btrfs_key found_key;
|
|
|
|
struct btrfs_inode_extref *extref;
|
2017-06-29 03:56:55 +00:00
|
|
|
const struct extent_buffer *leaf;
|
2012-08-08 18:32:27 +00:00
|
|
|
unsigned long ptr;
|
|
|
|
|
|
|
|
key.objectid = inode_objectid;
|
2014-06-04 16:41:45 +00:00
|
|
|
key.type = BTRFS_INODE_EXTREF_KEY;
|
2012-08-08 18:32:27 +00:00
|
|
|
key.offset = start_off;
|
|
|
|
|
|
|
|
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
leaf = path->nodes[0];
|
|
|
|
slot = path->slots[0];
|
|
|
|
if (slot >= btrfs_header_nritems(leaf)) {
|
|
|
|
/*
|
|
|
|
* If the item at offset is not found,
|
|
|
|
* btrfs_search_slot will point us to the slot
|
|
|
|
* where it should be inserted. In our case
|
|
|
|
* that will be the slot directly before the
|
|
|
|
* next INODE_REF_KEY_V2 item. In the case
|
|
|
|
* that we're pointing to the last slot in a
|
|
|
|
* leaf, we must move one leaf over.
|
|
|
|
*/
|
|
|
|
ret = btrfs_next_leaf(root, path);
|
|
|
|
if (ret) {
|
|
|
|
if (ret >= 1)
|
|
|
|
ret = -ENOENT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
btrfs_item_key_to_cpu(leaf, &found_key, slot);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that we're still looking at an extended ref key for
|
|
|
|
* this particular objectid. If we have different
|
|
|
|
* objectid or type then there are no more to be found
|
|
|
|
* in the tree and we can exit.
|
|
|
|
*/
|
|
|
|
ret = -ENOENT;
|
|
|
|
if (found_key.objectid != inode_objectid)
|
|
|
|
break;
|
2014-06-04 16:41:45 +00:00
|
|
|
if (found_key.type != BTRFS_INODE_EXTREF_KEY)
|
2012-08-08 18:32:27 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
|
|
|
|
extref = (struct btrfs_inode_extref *)ptr;
|
|
|
|
*ret_extref = extref;
|
|
|
|
if (found_off)
|
|
|
|
*found_off = found_key.offset;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-04-25 20:41:01 +00:00
|
|
|
/*
|
|
|
|
* this iterates to turn a name (from iref/extref) into a full filesystem path.
|
|
|
|
* Elements of the path are separated by '/' and the path is guaranteed to be
|
|
|
|
* 0-terminated. the path is only given within the current file system.
|
|
|
|
* Therefore, it never starts with a '/'. the caller is responsible to provide
|
|
|
|
* "size" bytes in "dest". the dest buffer will be filled backwards. finally,
|
|
|
|
* the start point of the resulting string is returned. this pointer is within
|
|
|
|
* dest, normally.
|
|
|
|
* in case the path buffer would overflow, the pointer is decremented further
|
|
|
|
* as if output was written to the buffer, though no more output is actually
|
|
|
|
* generated. that way, the caller can determine how much space would be
|
|
|
|
* required for the path to fit into the buffer. in that case, the returned
|
|
|
|
* value will be smaller than dest. callers must check this!
|
|
|
|
*/
|
2012-10-15 08:30:45 +00:00
|
|
|
char *btrfs_ref_to_path(struct btrfs_root *fs_root, struct btrfs_path *path,
|
|
|
|
u32 name_len, unsigned long name_off,
|
|
|
|
struct extent_buffer *eb_in, u64 parent,
|
|
|
|
char *dest, u32 size)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
|
|
|
int slot;
|
|
|
|
u64 next_inum;
|
|
|
|
int ret;
|
2012-10-10 14:50:47 +00:00
|
|
|
s64 bytes_left = ((s64)size) - 1;
|
2011-06-13 17:52:59 +00:00
|
|
|
struct extent_buffer *eb = eb_in;
|
|
|
|
struct btrfs_key found_key;
|
2012-08-08 18:33:54 +00:00
|
|
|
struct btrfs_inode_ref *iref;
|
2011-06-13 17:52:59 +00:00
|
|
|
|
|
|
|
if (bytes_left >= 0)
|
|
|
|
dest[bytes_left] = '\0';
|
|
|
|
|
|
|
|
while (1) {
|
2012-08-08 18:33:54 +00:00
|
|
|
bytes_left -= name_len;
|
2011-06-13 17:52:59 +00:00
|
|
|
if (bytes_left >= 0)
|
|
|
|
read_extent_buffer(eb, dest + bytes_left,
|
2012-08-08 18:33:54 +00:00
|
|
|
name_off, name_len);
|
2012-04-13 10:28:08 +00:00
|
|
|
if (eb != eb_in) {
|
2016-02-03 19:17:27 +00:00
|
|
|
if (!path->skip_locking)
|
2020-08-20 15:46:10 +00:00
|
|
|
btrfs_tree_read_unlock(eb);
|
2011-06-13 17:52:59 +00:00
|
|
|
free_extent_buffer(eb);
|
2012-04-13 10:28:08 +00:00
|
|
|
}
|
2015-01-02 18:03:17 +00:00
|
|
|
ret = btrfs_find_item(fs_root, path, parent, 0,
|
|
|
|
BTRFS_INODE_REF_KEY, &found_key);
|
2012-02-08 15:01:01 +00:00
|
|
|
if (ret > 0)
|
|
|
|
ret = -ENOENT;
|
2011-06-13 17:52:59 +00:00
|
|
|
if (ret)
|
|
|
|
break;
|
2012-08-08 18:33:54 +00:00
|
|
|
|
2011-06-13 17:52:59 +00:00
|
|
|
next_inum = found_key.offset;
|
|
|
|
|
|
|
|
/* regular exit ahead */
|
|
|
|
if (parent == next_inum)
|
|
|
|
break;
|
|
|
|
|
|
|
|
slot = path->slots[0];
|
|
|
|
eb = path->nodes[0];
|
|
|
|
/* make sure we can use eb after releasing the path */
|
2012-04-13 10:28:08 +00:00
|
|
|
if (eb != eb_in) {
|
2016-02-03 19:17:27 +00:00
|
|
|
path->nodes[0] = NULL;
|
|
|
|
path->locks[0] = 0;
|
2012-04-13 10:28:08 +00:00
|
|
|
}
|
2011-06-13 17:52:59 +00:00
|
|
|
btrfs_release_path(path);
|
|
|
|
iref = btrfs_item_ptr(eb, slot, struct btrfs_inode_ref);
|
2012-08-08 18:33:54 +00:00
|
|
|
|
|
|
|
name_len = btrfs_inode_ref_name_len(eb, iref);
|
|
|
|
name_off = (unsigned long)(iref + 1);
|
|
|
|
|
2011-06-13 17:52:59 +00:00
|
|
|
parent = next_inum;
|
|
|
|
--bytes_left;
|
|
|
|
if (bytes_left >= 0)
|
|
|
|
dest[bytes_left] = '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
btrfs_release_path(path);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ERR_PTR(ret);
|
|
|
|
|
|
|
|
return dest + bytes_left;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this makes the path point to (logical EXTENT_ITEM *)
|
|
|
|
* returns BTRFS_EXTENT_FLAG_DATA for data, BTRFS_EXTENT_FLAG_TREE_BLOCK for
|
|
|
|
* tree blocks and <0 on error.
|
|
|
|
*/
|
|
|
|
int extent_from_logical(struct btrfs_fs_info *fs_info, u64 logical,
|
2012-09-08 02:01:28 +00:00
|
|
|
struct btrfs_path *path, struct btrfs_key *found_key,
|
|
|
|
u64 *flags_ret)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
2021-11-05 20:45:45 +00:00
|
|
|
struct btrfs_root *extent_root = btrfs_extent_root(fs_info, logical);
|
2011-06-13 17:52:59 +00:00
|
|
|
int ret;
|
|
|
|
u64 flags;
|
2013-06-28 17:11:22 +00:00
|
|
|
u64 size = 0;
|
2011-06-13 17:52:59 +00:00
|
|
|
u32 item_size;
|
2017-06-29 03:56:55 +00:00
|
|
|
const struct extent_buffer *eb;
|
2011-06-13 17:52:59 +00:00
|
|
|
struct btrfs_extent_item *ei;
|
|
|
|
struct btrfs_key key;
|
|
|
|
|
2013-06-28 17:11:22 +00:00
|
|
|
if (btrfs_fs_incompat(fs_info, SKINNY_METADATA))
|
|
|
|
key.type = BTRFS_METADATA_ITEM_KEY;
|
|
|
|
else
|
|
|
|
key.type = BTRFS_EXTENT_ITEM_KEY;
|
2011-06-13 17:52:59 +00:00
|
|
|
key.objectid = logical;
|
|
|
|
key.offset = (u64)-1;
|
|
|
|
|
2021-11-05 20:45:45 +00:00
|
|
|
ret = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0);
|
2011-06-13 17:52:59 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2021-11-05 20:45:45 +00:00
|
|
|
ret = btrfs_previous_extent_item(extent_root, path, 0);
|
2014-02-06 12:02:29 +00:00
|
|
|
if (ret) {
|
|
|
|
if (ret > 0)
|
|
|
|
ret = -ENOENT;
|
|
|
|
return ret;
|
2014-01-23 21:03:45 +00:00
|
|
|
}
|
2014-02-06 12:02:29 +00:00
|
|
|
btrfs_item_key_to_cpu(path->nodes[0], found_key, path->slots[0]);
|
2013-06-28 17:11:22 +00:00
|
|
|
if (found_key->type == BTRFS_METADATA_ITEM_KEY)
|
2016-06-15 13:22:56 +00:00
|
|
|
size = fs_info->nodesize;
|
2013-06-28 17:11:22 +00:00
|
|
|
else if (found_key->type == BTRFS_EXTENT_ITEM_KEY)
|
|
|
|
size = found_key->offset;
|
|
|
|
|
2014-01-23 21:03:45 +00:00
|
|
|
if (found_key->objectid > logical ||
|
2013-06-28 17:11:22 +00:00
|
|
|
found_key->objectid + size <= logical) {
|
2016-09-20 14:05:02 +00:00
|
|
|
btrfs_debug(fs_info,
|
|
|
|
"logical %llu is not within any extent", logical);
|
2011-06-13 17:52:59 +00:00
|
|
|
return -ENOENT;
|
2011-12-02 13:56:41 +00:00
|
|
|
}
|
2011-06-13 17:52:59 +00:00
|
|
|
|
|
|
|
eb = path->nodes[0];
|
2021-10-21 18:58:35 +00:00
|
|
|
item_size = btrfs_item_size(eb, path->slots[0]);
|
2011-06-13 17:52:59 +00:00
|
|
|
BUG_ON(item_size < sizeof(*ei));
|
|
|
|
|
|
|
|
ei = btrfs_item_ptr(eb, path->slots[0], struct btrfs_extent_item);
|
|
|
|
flags = btrfs_extent_flags(eb, ei);
|
|
|
|
|
2016-09-20 14:05:02 +00:00
|
|
|
btrfs_debug(fs_info,
|
|
|
|
"logical %llu is at position %llu within the extent (%llu EXTENT_ITEM %llu) flags %#llx size %u",
|
2013-08-20 11:20:07 +00:00
|
|
|
logical, logical - found_key->objectid, found_key->objectid,
|
|
|
|
found_key->offset, flags, item_size);
|
2012-09-08 02:01:28 +00:00
|
|
|
|
|
|
|
WARN_ON(!flags_ret);
|
|
|
|
if (flags_ret) {
|
|
|
|
if (flags & BTRFS_EXTENT_FLAG_TREE_BLOCK)
|
|
|
|
*flags_ret = BTRFS_EXTENT_FLAG_TREE_BLOCK;
|
|
|
|
else if (flags & BTRFS_EXTENT_FLAG_DATA)
|
|
|
|
*flags_ret = BTRFS_EXTENT_FLAG_DATA;
|
|
|
|
else
|
btrfs: use BUG() instead of BUG_ON(1)
BUG_ON(1) leads to bogus warnings from clang when
CONFIG_PROFILE_ANNOTATED_BRANCHES is set:
fs/btrfs/volumes.c:5041:3: error: variable 'max_chunk_size' is used uninitialized whenever 'if' condition is false
[-Werror,-Wsometimes-uninitialized]
BUG_ON(1);
^~~~~~~~~
include/asm-generic/bug.h:61:36: note: expanded from macro 'BUG_ON'
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
^~~~~~~~~~~~~~~~~~~
include/linux/compiler.h:48:23: note: expanded from macro 'unlikely'
# define unlikely(x) (__branch_check__(x, 0, __builtin_constant_p(x)))
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
fs/btrfs/volumes.c:5046:9: note: uninitialized use occurs here
max_chunk_size);
^~~~~~~~~~~~~~
include/linux/kernel.h:860:36: note: expanded from macro 'min'
#define min(x, y) __careful_cmp(x, y, <)
^
include/linux/kernel.h:853:17: note: expanded from macro '__careful_cmp'
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
^
include/linux/kernel.h:847:25: note: expanded from macro '__cmp_once'
typeof(y) unique_y = (y); \
^
fs/btrfs/volumes.c:5041:3: note: remove the 'if' if its condition is always true
BUG_ON(1);
^
include/asm-generic/bug.h:61:32: note: expanded from macro 'BUG_ON'
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
^
fs/btrfs/volumes.c:4993:20: note: initialize the variable 'max_chunk_size' to silence this warning
u64 max_chunk_size;
^
= 0
Change it to BUG() so clang can see that this code path can never
continue.
Reviewed-by: Nikolay Borisov <nborisov@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-03-25 13:02:25 +00:00
|
|
|
BUG();
|
2012-09-08 02:01:28 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2011-06-13 17:52:59 +00:00
|
|
|
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* helper function to iterate extent inline refs. ptr must point to a 0 value
|
|
|
|
* for the first call and may be modified. it is used to track state.
|
|
|
|
* if more refs exist, 0 is returned and the next call to
|
2017-06-29 03:56:57 +00:00
|
|
|
* get_extent_inline_ref must pass the modified ptr parameter to get the
|
2011-06-13 17:52:59 +00:00
|
|
|
* next ref. after the last ref was processed, 1 is returned.
|
|
|
|
* returns <0 on error
|
|
|
|
*/
|
2017-06-29 03:56:57 +00:00
|
|
|
static int get_extent_inline_ref(unsigned long *ptr,
|
|
|
|
const struct extent_buffer *eb,
|
|
|
|
const struct btrfs_key *key,
|
|
|
|
const struct btrfs_extent_item *ei,
|
|
|
|
u32 item_size,
|
|
|
|
struct btrfs_extent_inline_ref **out_eiref,
|
|
|
|
int *out_type)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
|
|
|
unsigned long end;
|
|
|
|
u64 flags;
|
|
|
|
struct btrfs_tree_block_info *info;
|
|
|
|
|
|
|
|
if (!*ptr) {
|
|
|
|
/* first call */
|
|
|
|
flags = btrfs_extent_flags(eb, ei);
|
|
|
|
if (flags & BTRFS_EXTENT_FLAG_TREE_BLOCK) {
|
2014-06-09 02:54:07 +00:00
|
|
|
if (key->type == BTRFS_METADATA_ITEM_KEY) {
|
|
|
|
/* a skinny metadata extent */
|
|
|
|
*out_eiref =
|
|
|
|
(struct btrfs_extent_inline_ref *)(ei + 1);
|
|
|
|
} else {
|
|
|
|
WARN_ON(key->type != BTRFS_EXTENT_ITEM_KEY);
|
|
|
|
info = (struct btrfs_tree_block_info *)(ei + 1);
|
|
|
|
*out_eiref =
|
|
|
|
(struct btrfs_extent_inline_ref *)(info + 1);
|
|
|
|
}
|
2011-06-13 17:52:59 +00:00
|
|
|
} else {
|
|
|
|
*out_eiref = (struct btrfs_extent_inline_ref *)(ei + 1);
|
|
|
|
}
|
|
|
|
*ptr = (unsigned long)*out_eiref;
|
2014-06-08 11:04:13 +00:00
|
|
|
if ((unsigned long)(*ptr) >= (unsigned long)ei + item_size)
|
2011-06-13 17:52:59 +00:00
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
end = (unsigned long)ei + item_size;
|
2014-06-09 02:54:07 +00:00
|
|
|
*out_eiref = (struct btrfs_extent_inline_ref *)(*ptr);
|
2017-08-18 21:15:19 +00:00
|
|
|
*out_type = btrfs_get_extent_inline_ref_type(eb, *out_eiref,
|
|
|
|
BTRFS_REF_TYPE_ANY);
|
|
|
|
if (*out_type == BTRFS_REF_TYPE_INVALID)
|
2018-06-22 08:18:01 +00:00
|
|
|
return -EUCLEAN;
|
2011-06-13 17:52:59 +00:00
|
|
|
|
|
|
|
*ptr += btrfs_extent_inline_ref_size(*out_type);
|
|
|
|
WARN_ON(*ptr > end);
|
|
|
|
if (*ptr == end)
|
|
|
|
return 1; /* last */
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* reads the tree block backref for an extent. tree level and root are returned
|
|
|
|
* through out_level and out_root. ptr must point to a 0 value for the first
|
2017-06-29 03:56:57 +00:00
|
|
|
* call and may be modified (see get_extent_inline_ref comment).
|
2011-06-13 17:52:59 +00:00
|
|
|
* returns 0 if data was provided, 1 if there was no more data to provide or
|
|
|
|
* <0 on error.
|
|
|
|
*/
|
|
|
|
int tree_backref_for_extent(unsigned long *ptr, struct extent_buffer *eb,
|
2014-06-09 02:54:07 +00:00
|
|
|
struct btrfs_key *key, struct btrfs_extent_item *ei,
|
|
|
|
u32 item_size, u64 *out_root, u8 *out_level)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
int type;
|
|
|
|
struct btrfs_extent_inline_ref *eiref;
|
|
|
|
|
|
|
|
if (*ptr == (unsigned long)-1)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
while (1) {
|
2017-06-29 03:56:57 +00:00
|
|
|
ret = get_extent_inline_ref(ptr, eb, key, ei, item_size,
|
2014-06-09 02:54:07 +00:00
|
|
|
&eiref, &type);
|
2011-06-13 17:52:59 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (type == BTRFS_TREE_BLOCK_REF_KEY ||
|
|
|
|
type == BTRFS_SHARED_BLOCK_REF_KEY)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (ret == 1)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* we can treat both ref types equally here */
|
|
|
|
*out_root = btrfs_extent_inline_ref_offset(eb, eiref);
|
2014-12-15 16:04:42 +00:00
|
|
|
|
|
|
|
if (key->type == BTRFS_EXTENT_ITEM_KEY) {
|
|
|
|
struct btrfs_tree_block_info *info;
|
|
|
|
|
|
|
|
info = (struct btrfs_tree_block_info *)(ei + 1);
|
|
|
|
*out_level = btrfs_tree_block_level(eb, info);
|
|
|
|
} else {
|
|
|
|
ASSERT(key->type == BTRFS_METADATA_ITEM_KEY);
|
|
|
|
*out_level = (u8)key->offset;
|
|
|
|
}
|
2011-06-13 17:52:59 +00:00
|
|
|
|
|
|
|
if (ret == 1)
|
|
|
|
*ptr = (unsigned long)-1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-09-20 14:05:02 +00:00
|
|
|
static int iterate_leaf_refs(struct btrfs_fs_info *fs_info,
|
|
|
|
struct extent_inode_elem *inode_list,
|
|
|
|
u64 root, u64 extent_item_objectid,
|
|
|
|
iterate_extent_inodes_t *iterate, void *ctx)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
2012-05-17 14:43:03 +00:00
|
|
|
struct extent_inode_elem *eie;
|
2011-12-02 13:56:41 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
2012-05-17 14:43:03 +00:00
|
|
|
for (eie = inode_list; eie; eie = eie->next) {
|
2016-09-20 14:05:02 +00:00
|
|
|
btrfs_debug(fs_info,
|
|
|
|
"ref for %llu resolved, key (%llu EXTEND_DATA %llu), root %llu",
|
|
|
|
extent_item_objectid, eie->inum,
|
|
|
|
eie->offset, root);
|
2022-11-01 16:15:45 +00:00
|
|
|
ret = iterate(eie->inum, eie->offset, eie->num_bytes, root, ctx);
|
2011-12-02 13:56:41 +00:00
|
|
|
if (ret) {
|
2016-09-20 14:05:02 +00:00
|
|
|
btrfs_debug(fs_info,
|
|
|
|
"stopping iteration for %llu due to ret=%d",
|
|
|
|
extent_item_objectid, ret);
|
2011-12-02 13:56:41 +00:00
|
|
|
break;
|
|
|
|
}
|
2011-06-13 17:52:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* calls iterate() for every inode that references the extent identified by
|
2011-12-02 13:56:41 +00:00
|
|
|
* the given parameters.
|
2011-06-13 17:52:59 +00:00
|
|
|
* when the iterator function returns a non-zero value, iteration stops.
|
|
|
|
*/
|
2022-11-01 16:15:47 +00:00
|
|
|
int iterate_extent_inodes(struct btrfs_backref_walk_ctx *ctx,
|
|
|
|
bool search_commit_root,
|
|
|
|
iterate_extent_inodes_t *iterate, void *user_ctx)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
|
|
|
int ret;
|
2022-11-01 16:15:47 +00:00
|
|
|
struct ulist *refs;
|
|
|
|
struct ulist_node *ref_node;
|
2021-03-11 14:31:07 +00:00
|
|
|
struct btrfs_seq_list seq_elem = BTRFS_SEQ_LIST_INIT(seq_elem);
|
2012-05-22 12:56:50 +00:00
|
|
|
struct ulist_iterator ref_uiter;
|
2011-06-13 17:52:59 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
btrfs_debug(ctx->fs_info, "resolving all inodes for extent %llu",
|
|
|
|
ctx->bytenr);
|
|
|
|
|
|
|
|
ASSERT(ctx->trans == NULL);
|
2022-11-01 16:15:48 +00:00
|
|
|
ASSERT(ctx->roots == NULL);
|
|
|
|
|
|
|
|
ctx->roots = ulist_alloc(GFP_NOFS);
|
|
|
|
if (!ctx->roots)
|
|
|
|
return -ENOMEM;
|
2011-06-13 17:52:59 +00:00
|
|
|
|
2013-06-12 20:20:08 +00:00
|
|
|
if (!search_commit_root) {
|
2022-11-01 16:15:47 +00:00
|
|
|
struct btrfs_trans_handle *trans;
|
|
|
|
|
|
|
|
trans = btrfs_attach_transaction(ctx->fs_info->tree_root);
|
Btrfs: do not start a transaction at iterate_extent_inodes()
When finding out which inodes have references on a particular extent, done
by backref.c:iterate_extent_inodes(), from the BTRFS_IOC_LOGICAL_INO (both
v1 and v2) ioctl and from scrub we use the transaction join API to grab a
reference on the currently running transaction, since in order to give
accurate results we need to inspect the delayed references of the currently
running transaction.
However, if there is currently no running transaction, the join operation
will create a new transaction. This is inefficient as the transaction will
eventually be committed, doing unnecessary IO and introducing a potential
point of failure that will lead to a transaction abort due to -ENOSPC, as
recently reported [1].
That's because the join, creates the transaction but does not reserve any
space, so when attempting to update the root item of the root passed to
btrfs_join_transaction(), during the transaction commit, we can end up
failling with -ENOSPC. Users of a join operation are supposed to actually
do some filesystem changes and reserve space by some means, which is not
the case of iterate_extent_inodes(), it is a read-only operation for all
contextes from which it is called.
The reported [1] -ENOSPC failure stack trace is the following:
heisenberg kernel: ------------[ cut here ]------------
heisenberg kernel: BTRFS: Transaction aborted (error -28)
heisenberg kernel: WARNING: CPU: 0 PID: 7137 at fs/btrfs/root-tree.c:136 btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: CPU: 0 PID: 7137 Comm: btrfs-transacti Not tainted 4.19.0-4-amd64 #1 Debian 4.19.28-2
heisenberg kernel: Hardware name: FUJITSU LIFEBOOK U757/FJNB2A5, BIOS Version 1.21 03/19/2018
heisenberg kernel: RIP: 0010:btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: RSP: 0018:ffffb5448828bd40 EFLAGS: 00010286
heisenberg kernel: RAX: 0000000000000000 RBX: ffff8ed56bccef50 RCX: 0000000000000006
heisenberg kernel: RDX: 0000000000000007 RSI: 0000000000000092 RDI: ffff8ed6bda166a0
heisenberg kernel: RBP: 00000000ffffffe4 R08: 00000000000003df R09: 0000000000000007
heisenberg kernel: R10: 0000000000000000 R11: 0000000000000001 R12: ffff8ed63396a078
heisenberg kernel: R13: ffff8ed092d7c800 R14: ffff8ed64f5db028 R15: ffff8ed6bd03d068
heisenberg kernel: FS: 0000000000000000(0000) GS:ffff8ed6bda00000(0000) knlGS:0000000000000000
heisenberg kernel: CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
heisenberg kernel: CR2: 00007f46f75f8000 CR3: 0000000310a0a002 CR4: 00000000003606f0
heisenberg kernel: DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
heisenberg kernel: DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
heisenberg kernel: Call Trace:
heisenberg kernel: commit_fs_roots+0x166/0x1d0 [btrfs]
heisenberg kernel: ? _cond_resched+0x15/0x30
heisenberg kernel: ? btrfs_run_delayed_refs+0xac/0x180 [btrfs]
heisenberg kernel: btrfs_commit_transaction+0x2bd/0x870 [btrfs]
heisenberg kernel: ? start_transaction+0x9d/0x3f0 [btrfs]
heisenberg kernel: transaction_kthread+0x147/0x180 [btrfs]
heisenberg kernel: ? btrfs_cleanup_transaction+0x530/0x530 [btrfs]
heisenberg kernel: kthread+0x112/0x130
heisenberg kernel: ? kthread_bind+0x30/0x30
heisenberg kernel: ret_from_fork+0x35/0x40
heisenberg kernel: ---[ end trace 05de912e30e012d9 ]---
So fix that by using the attach API, which does not create a transaction
when there is currently no running transaction.
[1] https://lore.kernel.org/linux-btrfs/b2a668d7124f1d3e410367f587926f622b3f03a4.camel@scientia.net/
Reported-by: Zygo Blaxell <ce3g8jdj@umail.furryterror.org>
CC: stable@vger.kernel.org # 4.4+
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-04-17 10:30:30 +00:00
|
|
|
if (IS_ERR(trans)) {
|
|
|
|
if (PTR_ERR(trans) != -ENOENT &&
|
2022-11-01 16:15:48 +00:00
|
|
|
PTR_ERR(trans) != -EROFS) {
|
|
|
|
ulist_free(ctx->roots);
|
|
|
|
ctx->roots = NULL;
|
Btrfs: do not start a transaction at iterate_extent_inodes()
When finding out which inodes have references on a particular extent, done
by backref.c:iterate_extent_inodes(), from the BTRFS_IOC_LOGICAL_INO (both
v1 and v2) ioctl and from scrub we use the transaction join API to grab a
reference on the currently running transaction, since in order to give
accurate results we need to inspect the delayed references of the currently
running transaction.
However, if there is currently no running transaction, the join operation
will create a new transaction. This is inefficient as the transaction will
eventually be committed, doing unnecessary IO and introducing a potential
point of failure that will lead to a transaction abort due to -ENOSPC, as
recently reported [1].
That's because the join, creates the transaction but does not reserve any
space, so when attempting to update the root item of the root passed to
btrfs_join_transaction(), during the transaction commit, we can end up
failling with -ENOSPC. Users of a join operation are supposed to actually
do some filesystem changes and reserve space by some means, which is not
the case of iterate_extent_inodes(), it is a read-only operation for all
contextes from which it is called.
The reported [1] -ENOSPC failure stack trace is the following:
heisenberg kernel: ------------[ cut here ]------------
heisenberg kernel: BTRFS: Transaction aborted (error -28)
heisenberg kernel: WARNING: CPU: 0 PID: 7137 at fs/btrfs/root-tree.c:136 btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: CPU: 0 PID: 7137 Comm: btrfs-transacti Not tainted 4.19.0-4-amd64 #1 Debian 4.19.28-2
heisenberg kernel: Hardware name: FUJITSU LIFEBOOK U757/FJNB2A5, BIOS Version 1.21 03/19/2018
heisenberg kernel: RIP: 0010:btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: RSP: 0018:ffffb5448828bd40 EFLAGS: 00010286
heisenberg kernel: RAX: 0000000000000000 RBX: ffff8ed56bccef50 RCX: 0000000000000006
heisenberg kernel: RDX: 0000000000000007 RSI: 0000000000000092 RDI: ffff8ed6bda166a0
heisenberg kernel: RBP: 00000000ffffffe4 R08: 00000000000003df R09: 0000000000000007
heisenberg kernel: R10: 0000000000000000 R11: 0000000000000001 R12: ffff8ed63396a078
heisenberg kernel: R13: ffff8ed092d7c800 R14: ffff8ed64f5db028 R15: ffff8ed6bd03d068
heisenberg kernel: FS: 0000000000000000(0000) GS:ffff8ed6bda00000(0000) knlGS:0000000000000000
heisenberg kernel: CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
heisenberg kernel: CR2: 00007f46f75f8000 CR3: 0000000310a0a002 CR4: 00000000003606f0
heisenberg kernel: DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
heisenberg kernel: DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
heisenberg kernel: Call Trace:
heisenberg kernel: commit_fs_roots+0x166/0x1d0 [btrfs]
heisenberg kernel: ? _cond_resched+0x15/0x30
heisenberg kernel: ? btrfs_run_delayed_refs+0xac/0x180 [btrfs]
heisenberg kernel: btrfs_commit_transaction+0x2bd/0x870 [btrfs]
heisenberg kernel: ? start_transaction+0x9d/0x3f0 [btrfs]
heisenberg kernel: transaction_kthread+0x147/0x180 [btrfs]
heisenberg kernel: ? btrfs_cleanup_transaction+0x530/0x530 [btrfs]
heisenberg kernel: kthread+0x112/0x130
heisenberg kernel: ? kthread_bind+0x30/0x30
heisenberg kernel: ret_from_fork+0x35/0x40
heisenberg kernel: ---[ end trace 05de912e30e012d9 ]---
So fix that by using the attach API, which does not create a transaction
when there is currently no running transaction.
[1] https://lore.kernel.org/linux-btrfs/b2a668d7124f1d3e410367f587926f622b3f03a4.camel@scientia.net/
Reported-by: Zygo Blaxell <ce3g8jdj@umail.furryterror.org>
CC: stable@vger.kernel.org # 4.4+
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-04-17 10:30:30 +00:00
|
|
|
return PTR_ERR(trans);
|
2022-11-01 16:15:48 +00:00
|
|
|
}
|
Btrfs: do not start a transaction at iterate_extent_inodes()
When finding out which inodes have references on a particular extent, done
by backref.c:iterate_extent_inodes(), from the BTRFS_IOC_LOGICAL_INO (both
v1 and v2) ioctl and from scrub we use the transaction join API to grab a
reference on the currently running transaction, since in order to give
accurate results we need to inspect the delayed references of the currently
running transaction.
However, if there is currently no running transaction, the join operation
will create a new transaction. This is inefficient as the transaction will
eventually be committed, doing unnecessary IO and introducing a potential
point of failure that will lead to a transaction abort due to -ENOSPC, as
recently reported [1].
That's because the join, creates the transaction but does not reserve any
space, so when attempting to update the root item of the root passed to
btrfs_join_transaction(), during the transaction commit, we can end up
failling with -ENOSPC. Users of a join operation are supposed to actually
do some filesystem changes and reserve space by some means, which is not
the case of iterate_extent_inodes(), it is a read-only operation for all
contextes from which it is called.
The reported [1] -ENOSPC failure stack trace is the following:
heisenberg kernel: ------------[ cut here ]------------
heisenberg kernel: BTRFS: Transaction aborted (error -28)
heisenberg kernel: WARNING: CPU: 0 PID: 7137 at fs/btrfs/root-tree.c:136 btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: CPU: 0 PID: 7137 Comm: btrfs-transacti Not tainted 4.19.0-4-amd64 #1 Debian 4.19.28-2
heisenberg kernel: Hardware name: FUJITSU LIFEBOOK U757/FJNB2A5, BIOS Version 1.21 03/19/2018
heisenberg kernel: RIP: 0010:btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: RSP: 0018:ffffb5448828bd40 EFLAGS: 00010286
heisenberg kernel: RAX: 0000000000000000 RBX: ffff8ed56bccef50 RCX: 0000000000000006
heisenberg kernel: RDX: 0000000000000007 RSI: 0000000000000092 RDI: ffff8ed6bda166a0
heisenberg kernel: RBP: 00000000ffffffe4 R08: 00000000000003df R09: 0000000000000007
heisenberg kernel: R10: 0000000000000000 R11: 0000000000000001 R12: ffff8ed63396a078
heisenberg kernel: R13: ffff8ed092d7c800 R14: ffff8ed64f5db028 R15: ffff8ed6bd03d068
heisenberg kernel: FS: 0000000000000000(0000) GS:ffff8ed6bda00000(0000) knlGS:0000000000000000
heisenberg kernel: CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
heisenberg kernel: CR2: 00007f46f75f8000 CR3: 0000000310a0a002 CR4: 00000000003606f0
heisenberg kernel: DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
heisenberg kernel: DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
heisenberg kernel: Call Trace:
heisenberg kernel: commit_fs_roots+0x166/0x1d0 [btrfs]
heisenberg kernel: ? _cond_resched+0x15/0x30
heisenberg kernel: ? btrfs_run_delayed_refs+0xac/0x180 [btrfs]
heisenberg kernel: btrfs_commit_transaction+0x2bd/0x870 [btrfs]
heisenberg kernel: ? start_transaction+0x9d/0x3f0 [btrfs]
heisenberg kernel: transaction_kthread+0x147/0x180 [btrfs]
heisenberg kernel: ? btrfs_cleanup_transaction+0x530/0x530 [btrfs]
heisenberg kernel: kthread+0x112/0x130
heisenberg kernel: ? kthread_bind+0x30/0x30
heisenberg kernel: ret_from_fork+0x35/0x40
heisenberg kernel: ---[ end trace 05de912e30e012d9 ]---
So fix that by using the attach API, which does not create a transaction
when there is currently no running transaction.
[1] https://lore.kernel.org/linux-btrfs/b2a668d7124f1d3e410367f587926f622b3f03a4.camel@scientia.net/
Reported-by: Zygo Blaxell <ce3g8jdj@umail.furryterror.org>
CC: stable@vger.kernel.org # 4.4+
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-04-17 10:30:30 +00:00
|
|
|
trans = NULL;
|
|
|
|
}
|
2022-11-01 16:15:47 +00:00
|
|
|
ctx->trans = trans;
|
Btrfs: do not start a transaction at iterate_extent_inodes()
When finding out which inodes have references on a particular extent, done
by backref.c:iterate_extent_inodes(), from the BTRFS_IOC_LOGICAL_INO (both
v1 and v2) ioctl and from scrub we use the transaction join API to grab a
reference on the currently running transaction, since in order to give
accurate results we need to inspect the delayed references of the currently
running transaction.
However, if there is currently no running transaction, the join operation
will create a new transaction. This is inefficient as the transaction will
eventually be committed, doing unnecessary IO and introducing a potential
point of failure that will lead to a transaction abort due to -ENOSPC, as
recently reported [1].
That's because the join, creates the transaction but does not reserve any
space, so when attempting to update the root item of the root passed to
btrfs_join_transaction(), during the transaction commit, we can end up
failling with -ENOSPC. Users of a join operation are supposed to actually
do some filesystem changes and reserve space by some means, which is not
the case of iterate_extent_inodes(), it is a read-only operation for all
contextes from which it is called.
The reported [1] -ENOSPC failure stack trace is the following:
heisenberg kernel: ------------[ cut here ]------------
heisenberg kernel: BTRFS: Transaction aborted (error -28)
heisenberg kernel: WARNING: CPU: 0 PID: 7137 at fs/btrfs/root-tree.c:136 btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: CPU: 0 PID: 7137 Comm: btrfs-transacti Not tainted 4.19.0-4-amd64 #1 Debian 4.19.28-2
heisenberg kernel: Hardware name: FUJITSU LIFEBOOK U757/FJNB2A5, BIOS Version 1.21 03/19/2018
heisenberg kernel: RIP: 0010:btrfs_update_root+0x22b/0x320 [btrfs]
(...)
heisenberg kernel: RSP: 0018:ffffb5448828bd40 EFLAGS: 00010286
heisenberg kernel: RAX: 0000000000000000 RBX: ffff8ed56bccef50 RCX: 0000000000000006
heisenberg kernel: RDX: 0000000000000007 RSI: 0000000000000092 RDI: ffff8ed6bda166a0
heisenberg kernel: RBP: 00000000ffffffe4 R08: 00000000000003df R09: 0000000000000007
heisenberg kernel: R10: 0000000000000000 R11: 0000000000000001 R12: ffff8ed63396a078
heisenberg kernel: R13: ffff8ed092d7c800 R14: ffff8ed64f5db028 R15: ffff8ed6bd03d068
heisenberg kernel: FS: 0000000000000000(0000) GS:ffff8ed6bda00000(0000) knlGS:0000000000000000
heisenberg kernel: CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
heisenberg kernel: CR2: 00007f46f75f8000 CR3: 0000000310a0a002 CR4: 00000000003606f0
heisenberg kernel: DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
heisenberg kernel: DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
heisenberg kernel: Call Trace:
heisenberg kernel: commit_fs_roots+0x166/0x1d0 [btrfs]
heisenberg kernel: ? _cond_resched+0x15/0x30
heisenberg kernel: ? btrfs_run_delayed_refs+0xac/0x180 [btrfs]
heisenberg kernel: btrfs_commit_transaction+0x2bd/0x870 [btrfs]
heisenberg kernel: ? start_transaction+0x9d/0x3f0 [btrfs]
heisenberg kernel: transaction_kthread+0x147/0x180 [btrfs]
heisenberg kernel: ? btrfs_cleanup_transaction+0x530/0x530 [btrfs]
heisenberg kernel: kthread+0x112/0x130
heisenberg kernel: ? kthread_bind+0x30/0x30
heisenberg kernel: ret_from_fork+0x35/0x40
heisenberg kernel: ---[ end trace 05de912e30e012d9 ]---
So fix that by using the attach API, which does not create a transaction
when there is currently no running transaction.
[1] https://lore.kernel.org/linux-btrfs/b2a668d7124f1d3e410367f587926f622b3f03a4.camel@scientia.net/
Reported-by: Zygo Blaxell <ce3g8jdj@umail.furryterror.org>
CC: stable@vger.kernel.org # 4.4+
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-04-17 10:30:30 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->trans) {
|
|
|
|
btrfs_get_tree_mod_seq(ctx->fs_info, &seq_elem);
|
|
|
|
ctx->time_seq = seq_elem.seq;
|
|
|
|
} else {
|
|
|
|
down_read(&ctx->fs_info->commit_root_sem);
|
|
|
|
}
|
2011-06-13 17:52:59 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = btrfs_find_all_leafs(ctx);
|
2011-12-02 13:56:41 +00:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
2022-11-01 16:15:47 +00:00
|
|
|
refs = ctx->refs;
|
|
|
|
ctx->refs = NULL;
|
2011-06-13 17:52:59 +00:00
|
|
|
|
2012-05-22 12:56:50 +00:00
|
|
|
ULIST_ITER_INIT(&ref_uiter);
|
|
|
|
while (!ret && (ref_node = ulist_next(refs, &ref_uiter))) {
|
2022-11-01 16:15:47 +00:00
|
|
|
struct ulist_node *root_node;
|
|
|
|
struct ulist_iterator root_uiter;
|
|
|
|
|
|
|
|
ctx->bytenr = ref_node->val;
|
|
|
|
ret = btrfs_find_all_roots_safe(ctx);
|
2011-12-02 13:56:41 +00:00
|
|
|
if (ret)
|
|
|
|
break;
|
2022-11-01 16:15:47 +00:00
|
|
|
|
2012-05-22 12:56:50 +00:00
|
|
|
ULIST_ITER_INIT(&root_uiter);
|
2022-11-01 16:15:47 +00:00
|
|
|
while (!ret && (root_node = ulist_next(ctx->roots, &root_uiter))) {
|
|
|
|
btrfs_debug(ctx->fs_info,
|
2016-09-20 14:05:02 +00:00
|
|
|
"root %llu references leaf %llu, data list %#llx",
|
|
|
|
root_node->val, ref_node->val,
|
|
|
|
ref_node->aux);
|
2022-11-01 16:15:47 +00:00
|
|
|
ret = iterate_leaf_refs(ctx->fs_info,
|
2016-09-20 14:05:02 +00:00
|
|
|
(struct extent_inode_elem *)
|
2012-08-13 08:52:38 +00:00
|
|
|
(uintptr_t)ref_node->aux,
|
2022-11-01 16:15:47 +00:00
|
|
|
root_node->val, ctx->bytenr,
|
|
|
|
iterate, user_ctx);
|
2011-12-02 13:56:41 +00:00
|
|
|
}
|
2022-11-01 16:15:48 +00:00
|
|
|
ulist_reinit(ctx->roots);
|
2011-06-13 17:52:59 +00:00
|
|
|
}
|
|
|
|
|
2012-05-17 14:43:03 +00:00
|
|
|
free_leaf_list(refs);
|
2011-12-02 13:56:41 +00:00
|
|
|
out:
|
2022-11-01 16:15:47 +00:00
|
|
|
if (ctx->trans) {
|
|
|
|
btrfs_put_tree_mod_seq(ctx->fs_info, &seq_elem);
|
|
|
|
btrfs_end_transaction(ctx->trans);
|
|
|
|
ctx->trans = NULL;
|
2014-03-13 19:42:13 +00:00
|
|
|
} else {
|
2022-11-01 16:15:47 +00:00
|
|
|
up_read(&ctx->fs_info->commit_root_sem);
|
2012-03-23 16:32:28 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:48 +00:00
|
|
|
ulist_free(ctx->roots);
|
|
|
|
ctx->roots = NULL;
|
|
|
|
|
2011-06-13 17:52:59 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-11-01 16:15:45 +00:00
|
|
|
static int build_ino_list(u64 inum, u64 offset, u64 num_bytes, u64 root, void *ctx)
|
2022-06-06 17:32:59 +00:00
|
|
|
{
|
|
|
|
struct btrfs_data_container *inodes = ctx;
|
|
|
|
const size_t c = 3 * sizeof(u64);
|
|
|
|
|
|
|
|
if (inodes->bytes_left >= c) {
|
|
|
|
inodes->bytes_left -= c;
|
|
|
|
inodes->val[inodes->elem_cnt] = inum;
|
|
|
|
inodes->val[inodes->elem_cnt + 1] = offset;
|
|
|
|
inodes->val[inodes->elem_cnt + 2] = root;
|
|
|
|
inodes->elem_cnt += 3;
|
|
|
|
} else {
|
|
|
|
inodes->bytes_missing += c - inodes->bytes_left;
|
|
|
|
inodes->bytes_left = 0;
|
|
|
|
inodes->elem_missed += 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-13 17:52:59 +00:00
|
|
|
int iterate_inodes_from_logical(u64 logical, struct btrfs_fs_info *fs_info,
|
|
|
|
struct btrfs_path *path,
|
2022-06-06 17:32:59 +00:00
|
|
|
void *ctx, bool ignore_offset)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
2022-11-01 16:15:47 +00:00
|
|
|
struct btrfs_backref_walk_ctx walk_ctx = { 0 };
|
2011-06-13 17:52:59 +00:00
|
|
|
int ret;
|
2012-09-08 02:01:28 +00:00
|
|
|
u64 flags = 0;
|
2011-06-13 17:52:59 +00:00
|
|
|
struct btrfs_key found_key;
|
2012-03-23 16:32:28 +00:00
|
|
|
int search_commit_root = path->search_commit_root;
|
2011-06-13 17:52:59 +00:00
|
|
|
|
2012-09-08 02:01:28 +00:00
|
|
|
ret = extent_from_logical(fs_info, logical, path, &found_key, &flags);
|
2011-12-02 13:56:41 +00:00
|
|
|
btrfs_release_path(path);
|
2011-06-13 17:52:59 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2012-09-08 02:01:28 +00:00
|
|
|
if (flags & BTRFS_EXTENT_FLAG_TREE_BLOCK)
|
2012-08-01 10:28:01 +00:00
|
|
|
return -EINVAL;
|
2011-06-13 17:52:59 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
walk_ctx.bytenr = found_key.objectid;
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
if (ignore_offset)
|
2022-11-01 16:15:47 +00:00
|
|
|
walk_ctx.ignore_extent_item_pos = true;
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
else
|
2022-11-01 16:15:47 +00:00
|
|
|
walk_ctx.extent_item_pos = logical - found_key.objectid;
|
|
|
|
walk_ctx.fs_info = fs_info;
|
btrfs: use a single argument for extent offset in backref walking functions
The interface for find_parent_nodes() has two extent offset related
arguments:
1) One u64 pointer argument for the extent offset;
2) One boolean argument to tell if the extent offset should be ignored or
not.
These are confusing, becase the extent offset pointer can be NULL and in
some cases callers pass a NULL value as a way to tell the backref walking
code to ignore offsets in file extent items (and simply consider all file
extent items that point to the target data extent).
The boolean argument was added in commit c995ab3cda3f ("btrfs: add a flag
to iterate_inodes_from_logical to find all extent refs for uncompressed
extents"), but it was never really necessary, it was enough if it could
find a way to get a NULL value passed to the "extent_item_pos" argument of
find_parent_nodes(). The arguments are also passed to functions called
by find_parent_nodes() and respective helper functions, which further
makes everything more complicated than needed.
Then we have several backref walking related functions that end up calling
find_parent_nodes(), either directly or through some other function that
they call, and for many we have to use an "extent_item_pos" (u64) argument
and a boolean "ignore_offset" argument too.
This is confusing and not really necessary. So use a single argument to
specify the extent offset, as a simple u64 and not as a pointer, but
using a special value of (u64)-1, defined as a documented constant, to
indicate when the extent offset should be ignored.
This is also preparation work for the upcoming patches in the series that
add other arguments to find_parent_nodes() and other related functions
that use it.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-11-01 16:15:46 +00:00
|
|
|
|
2022-11-01 16:15:47 +00:00
|
|
|
return iterate_extent_inodes(&walk_ctx, search_commit_root,
|
|
|
|
build_ino_list, ctx);
|
2011-06-13 17:52:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-06 16:52:24 +00:00
|
|
|
static int inode_to_path(u64 inum, u32 name_len, unsigned long name_off,
|
2022-06-06 17:06:17 +00:00
|
|
|
struct extent_buffer *eb, struct inode_fs_paths *ipath);
|
2012-08-08 18:33:54 +00:00
|
|
|
|
2022-06-06 17:06:17 +00:00
|
|
|
static int iterate_inode_refs(u64 inum, struct inode_fs_paths *ipath)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
2012-04-13 10:28:00 +00:00
|
|
|
int ret = 0;
|
2011-06-13 17:52:59 +00:00
|
|
|
int slot;
|
|
|
|
u32 cur;
|
|
|
|
u32 len;
|
|
|
|
u32 name_len;
|
|
|
|
u64 parent = 0;
|
|
|
|
int found = 0;
|
2022-06-06 17:06:17 +00:00
|
|
|
struct btrfs_root *fs_root = ipath->fs_root;
|
|
|
|
struct btrfs_path *path = ipath->btrfs_path;
|
2011-06-13 17:52:59 +00:00
|
|
|
struct extent_buffer *eb;
|
|
|
|
struct btrfs_inode_ref *iref;
|
|
|
|
struct btrfs_key found_key;
|
|
|
|
|
2012-04-13 10:28:00 +00:00
|
|
|
while (!ret) {
|
2015-01-02 18:03:17 +00:00
|
|
|
ret = btrfs_find_item(fs_root, path, inum,
|
|
|
|
parent ? parent + 1 : 0, BTRFS_INODE_REF_KEY,
|
|
|
|
&found_key);
|
|
|
|
|
2011-06-13 17:52:59 +00:00
|
|
|
if (ret < 0)
|
|
|
|
break;
|
|
|
|
if (ret) {
|
|
|
|
ret = found ? 0 : -ENOENT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
++found;
|
|
|
|
|
|
|
|
parent = found_key.offset;
|
|
|
|
slot = path->slots[0];
|
2013-12-15 12:43:58 +00:00
|
|
|
eb = btrfs_clone_extent_buffer(path->nodes[0]);
|
|
|
|
if (!eb) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
break;
|
|
|
|
}
|
2011-06-13 17:52:59 +00:00
|
|
|
btrfs_release_path(path);
|
|
|
|
|
|
|
|
iref = btrfs_item_ptr(eb, slot, struct btrfs_inode_ref);
|
|
|
|
|
2021-10-21 18:58:35 +00:00
|
|
|
for (cur = 0; cur < btrfs_item_size(eb, slot); cur += len) {
|
2011-06-13 17:52:59 +00:00
|
|
|
name_len = btrfs_inode_ref_name_len(eb, iref);
|
|
|
|
/* path must be released before calling iterate()! */
|
2016-09-20 14:05:02 +00:00
|
|
|
btrfs_debug(fs_root->fs_info,
|
|
|
|
"following ref at offset %u for inode %llu in tree %llu",
|
2018-08-06 05:25:24 +00:00
|
|
|
cur, found_key.objectid,
|
|
|
|
fs_root->root_key.objectid);
|
2022-06-06 16:52:24 +00:00
|
|
|
ret = inode_to_path(parent, name_len,
|
2022-06-06 17:06:17 +00:00
|
|
|
(unsigned long)(iref + 1), eb, ipath);
|
2012-04-13 10:28:00 +00:00
|
|
|
if (ret)
|
2011-06-13 17:52:59 +00:00
|
|
|
break;
|
|
|
|
len = sizeof(*iref) + name_len;
|
|
|
|
iref = (struct btrfs_inode_ref *)((char *)iref + len);
|
|
|
|
}
|
|
|
|
free_extent_buffer(eb);
|
|
|
|
}
|
|
|
|
|
|
|
|
btrfs_release_path(path);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-06-06 17:06:17 +00:00
|
|
|
static int iterate_inode_extrefs(u64 inum, struct inode_fs_paths *ipath)
|
2012-08-08 18:33:54 +00:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
int slot;
|
|
|
|
u64 offset = 0;
|
|
|
|
u64 parent;
|
|
|
|
int found = 0;
|
2022-06-06 17:06:17 +00:00
|
|
|
struct btrfs_root *fs_root = ipath->fs_root;
|
|
|
|
struct btrfs_path *path = ipath->btrfs_path;
|
2012-08-08 18:33:54 +00:00
|
|
|
struct extent_buffer *eb;
|
|
|
|
struct btrfs_inode_extref *extref;
|
|
|
|
u32 item_size;
|
|
|
|
u32 cur_offset;
|
|
|
|
unsigned long ptr;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
ret = btrfs_find_one_extref(fs_root, inum, offset, path, &extref,
|
|
|
|
&offset);
|
|
|
|
if (ret < 0)
|
|
|
|
break;
|
|
|
|
if (ret) {
|
|
|
|
ret = found ? 0 : -ENOENT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
++found;
|
|
|
|
|
|
|
|
slot = path->slots[0];
|
2013-12-15 12:43:58 +00:00
|
|
|
eb = btrfs_clone_extent_buffer(path->nodes[0]);
|
|
|
|
if (!eb) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
break;
|
|
|
|
}
|
2012-08-08 18:33:54 +00:00
|
|
|
btrfs_release_path(path);
|
|
|
|
|
2021-10-21 18:58:35 +00:00
|
|
|
item_size = btrfs_item_size(eb, slot);
|
2015-10-13 18:06:48 +00:00
|
|
|
ptr = btrfs_item_ptr_offset(eb, slot);
|
2012-08-08 18:33:54 +00:00
|
|
|
cur_offset = 0;
|
|
|
|
|
|
|
|
while (cur_offset < item_size) {
|
|
|
|
u32 name_len;
|
|
|
|
|
|
|
|
extref = (struct btrfs_inode_extref *)(ptr + cur_offset);
|
|
|
|
parent = btrfs_inode_extref_parent(eb, extref);
|
|
|
|
name_len = btrfs_inode_extref_name_len(eb, extref);
|
2022-06-06 16:52:24 +00:00
|
|
|
ret = inode_to_path(parent, name_len,
|
2022-06-06 17:06:17 +00:00
|
|
|
(unsigned long)&extref->name, eb, ipath);
|
2012-08-08 18:33:54 +00:00
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
|
2015-10-13 18:06:48 +00:00
|
|
|
cur_offset += btrfs_inode_extref_name_len(eb, extref);
|
2012-08-08 18:33:54 +00:00
|
|
|
cur_offset += sizeof(*extref);
|
|
|
|
}
|
|
|
|
free_extent_buffer(eb);
|
|
|
|
|
|
|
|
offset++;
|
|
|
|
}
|
|
|
|
|
|
|
|
btrfs_release_path(path);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-06-13 17:52:59 +00:00
|
|
|
/*
|
|
|
|
* returns 0 if the path could be dumped (probably truncated)
|
|
|
|
* returns <0 in case of an error
|
|
|
|
*/
|
2012-08-08 18:33:54 +00:00
|
|
|
static int inode_to_path(u64 inum, u32 name_len, unsigned long name_off,
|
2022-06-06 17:06:17 +00:00
|
|
|
struct extent_buffer *eb, struct inode_fs_paths *ipath)
|
2011-06-13 17:52:59 +00:00
|
|
|
{
|
|
|
|
char *fspath;
|
|
|
|
char *fspath_min;
|
|
|
|
int i = ipath->fspath->elem_cnt;
|
|
|
|
const int s_ptr = sizeof(char *);
|
|
|
|
u32 bytes_left;
|
|
|
|
|
|
|
|
bytes_left = ipath->fspath->bytes_left > s_ptr ?
|
|
|
|
ipath->fspath->bytes_left - s_ptr : 0;
|
|
|
|
|
2011-11-02 19:48:34 +00:00
|
|
|
fspath_min = (char *)ipath->fspath->val + (i + 1) * s_ptr;
|
2012-10-15 08:30:45 +00:00
|
|
|
fspath = btrfs_ref_to_path(ipath->fs_root, ipath->btrfs_path, name_len,
|
|
|
|
name_off, eb, inum, fspath_min, bytes_left);
|
2011-06-13 17:52:59 +00:00
|
|
|
if (IS_ERR(fspath))
|
|
|
|
return PTR_ERR(fspath);
|
|
|
|
|
|
|
|
if (fspath > fspath_min) {
|
2011-11-20 12:31:57 +00:00
|
|
|
ipath->fspath->val[i] = (u64)(unsigned long)fspath;
|
2011-06-13 17:52:59 +00:00
|
|
|
++ipath->fspath->elem_cnt;
|
|
|
|
ipath->fspath->bytes_left = fspath - fspath_min;
|
|
|
|
} else {
|
|
|
|
++ipath->fspath->elem_missed;
|
|
|
|
ipath->fspath->bytes_missing += fspath_min - fspath;
|
|
|
|
ipath->fspath->bytes_left = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this dumps all file system paths to the inode into the ipath struct, provided
|
|
|
|
* is has been created large enough. each path is zero-terminated and accessed
|
2011-11-02 19:48:34 +00:00
|
|
|
* from ipath->fspath->val[i].
|
2011-06-13 17:52:59 +00:00
|
|
|
* when it returns, there are ipath->fspath->elem_cnt number of paths available
|
2011-11-02 19:48:34 +00:00
|
|
|
* in ipath->fspath->val[]. when the allocated space wasn't sufficient, the
|
2016-05-20 01:18:45 +00:00
|
|
|
* number of missed paths is recorded in ipath->fspath->elem_missed, otherwise,
|
2011-06-13 17:52:59 +00:00
|
|
|
* it's zero. ipath->fspath->bytes_missing holds the number of bytes that would
|
|
|
|
* have been needed to return all paths.
|
|
|
|
*/
|
|
|
|
int paths_from_inode(u64 inum, struct inode_fs_paths *ipath)
|
|
|
|
{
|
2022-06-06 16:52:24 +00:00
|
|
|
int ret;
|
|
|
|
int found_refs = 0;
|
|
|
|
|
2022-06-06 17:06:17 +00:00
|
|
|
ret = iterate_inode_refs(inum, ipath);
|
2022-06-06 16:52:24 +00:00
|
|
|
if (!ret)
|
|
|
|
++found_refs;
|
|
|
|
else if (ret != -ENOENT)
|
|
|
|
return ret;
|
|
|
|
|
2022-06-06 17:06:17 +00:00
|
|
|
ret = iterate_inode_extrefs(inum, ipath);
|
2022-06-06 16:52:24 +00:00
|
|
|
if (ret == -ENOENT && found_refs)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return ret;
|
2011-06-13 17:52:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct btrfs_data_container *init_data_container(u32 total_bytes)
|
|
|
|
{
|
|
|
|
struct btrfs_data_container *data;
|
|
|
|
size_t alloc_bytes;
|
|
|
|
|
|
|
|
alloc_bytes = max_t(size_t, total_bytes, sizeof(*data));
|
2017-05-31 17:32:09 +00:00
|
|
|
data = kvmalloc(alloc_bytes, GFP_KERNEL);
|
2011-06-13 17:52:59 +00:00
|
|
|
if (!data)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
if (total_bytes >= sizeof(*data)) {
|
|
|
|
data->bytes_left = total_bytes - sizeof(*data);
|
|
|
|
data->bytes_missing = 0;
|
|
|
|
} else {
|
|
|
|
data->bytes_missing = sizeof(*data) - total_bytes;
|
|
|
|
data->bytes_left = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
data->elem_cnt = 0;
|
|
|
|
data->elem_missed = 0;
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* allocates space to return multiple file system paths for an inode.
|
|
|
|
* total_bytes to allocate are passed, note that space usable for actual path
|
|
|
|
* information will be total_bytes - sizeof(struct inode_fs_paths).
|
|
|
|
* the returned pointer must be freed with free_ipath() in the end.
|
|
|
|
*/
|
|
|
|
struct inode_fs_paths *init_ipath(s32 total_bytes, struct btrfs_root *fs_root,
|
|
|
|
struct btrfs_path *path)
|
|
|
|
{
|
|
|
|
struct inode_fs_paths *ifp;
|
|
|
|
struct btrfs_data_container *fspath;
|
|
|
|
|
|
|
|
fspath = init_data_container(total_bytes);
|
|
|
|
if (IS_ERR(fspath))
|
2018-07-26 01:22:58 +00:00
|
|
|
return ERR_CAST(fspath);
|
2011-06-13 17:52:59 +00:00
|
|
|
|
2017-05-31 17:32:09 +00:00
|
|
|
ifp = kmalloc(sizeof(*ifp), GFP_KERNEL);
|
2011-06-13 17:52:59 +00:00
|
|
|
if (!ifp) {
|
2017-05-31 17:32:09 +00:00
|
|
|
kvfree(fspath);
|
2011-06-13 17:52:59 +00:00
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
}
|
|
|
|
|
|
|
|
ifp->btrfs_path = path;
|
|
|
|
ifp->fspath = fspath;
|
|
|
|
ifp->fs_root = fs_root;
|
|
|
|
|
|
|
|
return ifp;
|
|
|
|
}
|
|
|
|
|
|
|
|
void free_ipath(struct inode_fs_paths *ipath)
|
|
|
|
{
|
2012-04-12 20:47:52 +00:00
|
|
|
if (!ipath)
|
|
|
|
return;
|
2017-05-31 17:32:09 +00:00
|
|
|
kvfree(ipath->fspath);
|
2011-06-13 17:52:59 +00:00
|
|
|
kfree(ipath);
|
|
|
|
}
|
2020-02-13 06:11:04 +00:00
|
|
|
|
2022-10-14 13:45:37 +00:00
|
|
|
struct btrfs_backref_iter *btrfs_backref_iter_alloc(struct btrfs_fs_info *fs_info)
|
2020-02-13 06:11:04 +00:00
|
|
|
{
|
|
|
|
struct btrfs_backref_iter *ret;
|
|
|
|
|
2022-10-14 13:45:37 +00:00
|
|
|
ret = kzalloc(sizeof(*ret), GFP_NOFS);
|
2020-02-13 06:11:04 +00:00
|
|
|
if (!ret)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ret->path = btrfs_alloc_path();
|
2020-08-06 06:31:44 +00:00
|
|
|
if (!ret->path) {
|
2020-02-13 06:11:04 +00:00
|
|
|
kfree(ret);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Current backref iterator only supports iteration in commit root */
|
|
|
|
ret->path->search_commit_root = 1;
|
|
|
|
ret->path->skip_locking = 1;
|
|
|
|
ret->fs_info = fs_info;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int btrfs_backref_iter_start(struct btrfs_backref_iter *iter, u64 bytenr)
|
|
|
|
{
|
|
|
|
struct btrfs_fs_info *fs_info = iter->fs_info;
|
2021-11-05 20:45:45 +00:00
|
|
|
struct btrfs_root *extent_root = btrfs_extent_root(fs_info, bytenr);
|
2020-02-13 06:11:04 +00:00
|
|
|
struct btrfs_path *path = iter->path;
|
|
|
|
struct btrfs_extent_item *ei;
|
|
|
|
struct btrfs_key key;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
key.objectid = bytenr;
|
|
|
|
key.type = BTRFS_METADATA_ITEM_KEY;
|
|
|
|
key.offset = (u64)-1;
|
|
|
|
iter->bytenr = bytenr;
|
|
|
|
|
2021-11-05 20:45:45 +00:00
|
|
|
ret = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0);
|
2020-02-13 06:11:04 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
if (ret == 0) {
|
|
|
|
ret = -EUCLEAN;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
if (path->slots[0] == 0) {
|
|
|
|
WARN_ON(IS_ENABLED(CONFIG_BTRFS_DEBUG));
|
|
|
|
ret = -EUCLEAN;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
path->slots[0]--;
|
|
|
|
|
|
|
|
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
|
|
|
|
if ((key.type != BTRFS_EXTENT_ITEM_KEY &&
|
|
|
|
key.type != BTRFS_METADATA_ITEM_KEY) || key.objectid != bytenr) {
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
memcpy(&iter->cur_key, &key, sizeof(key));
|
|
|
|
iter->item_ptr = (u32)btrfs_item_ptr_offset(path->nodes[0],
|
|
|
|
path->slots[0]);
|
|
|
|
iter->end_ptr = (u32)(iter->item_ptr +
|
2021-10-21 18:58:35 +00:00
|
|
|
btrfs_item_size(path->nodes[0], path->slots[0]));
|
2020-02-13 06:11:04 +00:00
|
|
|
ei = btrfs_item_ptr(path->nodes[0], path->slots[0],
|
|
|
|
struct btrfs_extent_item);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Only support iteration on tree backref yet.
|
|
|
|
*
|
|
|
|
* This is an extra precaution for non skinny-metadata, where
|
|
|
|
* EXTENT_ITEM is also used for tree blocks, that we can only use
|
|
|
|
* extent flags to determine if it's a tree block.
|
|
|
|
*/
|
|
|
|
if (btrfs_extent_flags(path->nodes[0], ei) & BTRFS_EXTENT_FLAG_DATA) {
|
|
|
|
ret = -ENOTSUPP;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
iter->cur_ptr = (u32)(iter->item_ptr + sizeof(*ei));
|
|
|
|
|
|
|
|
/* If there is no inline backref, go search for keyed backref */
|
|
|
|
if (iter->cur_ptr >= iter->end_ptr) {
|
2021-11-05 20:45:45 +00:00
|
|
|
ret = btrfs_next_item(extent_root, path);
|
2020-02-13 06:11:04 +00:00
|
|
|
|
|
|
|
/* No inline nor keyed ref */
|
|
|
|
if (ret > 0) {
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
if (ret < 0)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
btrfs_item_key_to_cpu(path->nodes[0], &iter->cur_key,
|
|
|
|
path->slots[0]);
|
|
|
|
if (iter->cur_key.objectid != bytenr ||
|
|
|
|
(iter->cur_key.type != BTRFS_SHARED_BLOCK_REF_KEY &&
|
|
|
|
iter->cur_key.type != BTRFS_TREE_BLOCK_REF_KEY)) {
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
iter->cur_ptr = (u32)btrfs_item_ptr_offset(path->nodes[0],
|
|
|
|
path->slots[0]);
|
|
|
|
iter->item_ptr = iter->cur_ptr;
|
2021-10-21 18:58:35 +00:00
|
|
|
iter->end_ptr = (u32)(iter->item_ptr + btrfs_item_size(
|
2020-02-13 06:11:04 +00:00
|
|
|
path->nodes[0], path->slots[0]));
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
release:
|
|
|
|
btrfs_backref_iter_release(iter);
|
|
|
|
return ret;
|
|
|
|
}
|
2020-02-13 07:04:04 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Go to the next backref item of current bytenr, can be either inlined or
|
|
|
|
* keyed.
|
|
|
|
*
|
|
|
|
* Caller needs to check whether it's inline ref or not by iter->cur_key.
|
|
|
|
*
|
|
|
|
* Return 0 if we get next backref without problem.
|
|
|
|
* Return >0 if there is no extra backref for this bytenr.
|
|
|
|
* Return <0 if there is something wrong happened.
|
|
|
|
*/
|
|
|
|
int btrfs_backref_iter_next(struct btrfs_backref_iter *iter)
|
|
|
|
{
|
|
|
|
struct extent_buffer *eb = btrfs_backref_get_eb(iter);
|
2021-11-05 20:45:45 +00:00
|
|
|
struct btrfs_root *extent_root;
|
2020-02-13 07:04:04 +00:00
|
|
|
struct btrfs_path *path = iter->path;
|
|
|
|
struct btrfs_extent_inline_ref *iref;
|
|
|
|
int ret;
|
|
|
|
u32 size;
|
|
|
|
|
|
|
|
if (btrfs_backref_iter_is_inline_ref(iter)) {
|
|
|
|
/* We're still inside the inline refs */
|
|
|
|
ASSERT(iter->cur_ptr < iter->end_ptr);
|
|
|
|
|
|
|
|
if (btrfs_backref_has_tree_block_info(iter)) {
|
|
|
|
/* First tree block info */
|
|
|
|
size = sizeof(struct btrfs_tree_block_info);
|
|
|
|
} else {
|
|
|
|
/* Use inline ref type to determine the size */
|
|
|
|
int type;
|
|
|
|
|
|
|
|
iref = (struct btrfs_extent_inline_ref *)
|
|
|
|
((unsigned long)iter->cur_ptr);
|
|
|
|
type = btrfs_extent_inline_ref_type(eb, iref);
|
|
|
|
|
|
|
|
size = btrfs_extent_inline_ref_size(type);
|
|
|
|
}
|
|
|
|
iter->cur_ptr += size;
|
|
|
|
if (iter->cur_ptr < iter->end_ptr)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* All inline items iterated, fall through */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We're at keyed items, there is no inline item, go to the next one */
|
2021-11-05 20:45:45 +00:00
|
|
|
extent_root = btrfs_extent_root(iter->fs_info, iter->bytenr);
|
|
|
|
ret = btrfs_next_item(extent_root, iter->path);
|
2020-02-13 07:04:04 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
btrfs_item_key_to_cpu(path->nodes[0], &iter->cur_key, path->slots[0]);
|
|
|
|
if (iter->cur_key.objectid != iter->bytenr ||
|
|
|
|
(iter->cur_key.type != BTRFS_TREE_BLOCK_REF_KEY &&
|
|
|
|
iter->cur_key.type != BTRFS_SHARED_BLOCK_REF_KEY))
|
|
|
|
return 1;
|
|
|
|
iter->item_ptr = (u32)btrfs_item_ptr_offset(path->nodes[0],
|
|
|
|
path->slots[0]);
|
|
|
|
iter->cur_ptr = iter->item_ptr;
|
2021-10-21 18:58:35 +00:00
|
|
|
iter->end_ptr = iter->item_ptr + (u32)btrfs_item_size(path->nodes[0],
|
2020-02-13 07:04:04 +00:00
|
|
|
path->slots[0]);
|
|
|
|
return 0;
|
|
|
|
}
|
2020-03-03 05:14:41 +00:00
|
|
|
|
|
|
|
void btrfs_backref_init_cache(struct btrfs_fs_info *fs_info,
|
|
|
|
struct btrfs_backref_cache *cache, int is_reloc)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
cache->rb_root = RB_ROOT;
|
|
|
|
for (i = 0; i < BTRFS_MAX_LEVEL; i++)
|
|
|
|
INIT_LIST_HEAD(&cache->pending[i]);
|
|
|
|
INIT_LIST_HEAD(&cache->changed);
|
|
|
|
INIT_LIST_HEAD(&cache->detached);
|
|
|
|
INIT_LIST_HEAD(&cache->leaves);
|
|
|
|
INIT_LIST_HEAD(&cache->pending_edge);
|
|
|
|
INIT_LIST_HEAD(&cache->useless_node);
|
|
|
|
cache->fs_info = fs_info;
|
|
|
|
cache->is_reloc = is_reloc;
|
|
|
|
}
|
2020-03-03 05:21:30 +00:00
|
|
|
|
|
|
|
struct btrfs_backref_node *btrfs_backref_alloc_node(
|
|
|
|
struct btrfs_backref_cache *cache, u64 bytenr, int level)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_node *node;
|
|
|
|
|
|
|
|
ASSERT(level >= 0 && level < BTRFS_MAX_LEVEL);
|
|
|
|
node = kzalloc(sizeof(*node), GFP_NOFS);
|
|
|
|
if (!node)
|
|
|
|
return node;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&node->list);
|
|
|
|
INIT_LIST_HEAD(&node->upper);
|
|
|
|
INIT_LIST_HEAD(&node->lower);
|
|
|
|
RB_CLEAR_NODE(&node->rb_node);
|
|
|
|
cache->nr_nodes++;
|
|
|
|
node->level = level;
|
|
|
|
node->bytenr = bytenr;
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
2020-03-03 05:22:57 +00:00
|
|
|
|
|
|
|
struct btrfs_backref_edge *btrfs_backref_alloc_edge(
|
|
|
|
struct btrfs_backref_cache *cache)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_edge *edge;
|
|
|
|
|
|
|
|
edge = kzalloc(sizeof(*edge), GFP_NOFS);
|
|
|
|
if (edge)
|
|
|
|
cache->nr_edges++;
|
|
|
|
return edge;
|
|
|
|
}
|
2020-03-23 07:42:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Drop the backref node from cache, also cleaning up all its
|
|
|
|
* upper edges and any uncached nodes in the path.
|
|
|
|
*
|
|
|
|
* This cleanup happens bottom up, thus the node should either
|
|
|
|
* be the lowest node in the cache or a detached node.
|
|
|
|
*/
|
|
|
|
void btrfs_backref_cleanup_node(struct btrfs_backref_cache *cache,
|
|
|
|
struct btrfs_backref_node *node)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_node *upper;
|
|
|
|
struct btrfs_backref_edge *edge;
|
|
|
|
|
|
|
|
if (!node)
|
|
|
|
return;
|
|
|
|
|
|
|
|
BUG_ON(!node->lowest && !node->detached);
|
|
|
|
while (!list_empty(&node->upper)) {
|
|
|
|
edge = list_entry(node->upper.next, struct btrfs_backref_edge,
|
|
|
|
list[LOWER]);
|
|
|
|
upper = edge->node[UPPER];
|
|
|
|
list_del(&edge->list[LOWER]);
|
|
|
|
list_del(&edge->list[UPPER]);
|
|
|
|
btrfs_backref_free_edge(cache, edge);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add the node to leaf node list if no other child block
|
|
|
|
* cached.
|
|
|
|
*/
|
|
|
|
if (list_empty(&upper->lower)) {
|
|
|
|
list_add_tail(&upper->lower, &cache->leaves);
|
|
|
|
upper->lowest = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
btrfs_backref_drop_node(cache, node);
|
|
|
|
}
|
2020-03-03 05:55:12 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Release all nodes/edges from current cache
|
|
|
|
*/
|
|
|
|
void btrfs_backref_release_cache(struct btrfs_backref_cache *cache)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_node *node;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
while (!list_empty(&cache->detached)) {
|
|
|
|
node = list_entry(cache->detached.next,
|
|
|
|
struct btrfs_backref_node, list);
|
|
|
|
btrfs_backref_cleanup_node(cache, node);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!list_empty(&cache->leaves)) {
|
|
|
|
node = list_entry(cache->leaves.next,
|
|
|
|
struct btrfs_backref_node, lower);
|
|
|
|
btrfs_backref_cleanup_node(cache, node);
|
|
|
|
}
|
|
|
|
|
|
|
|
cache->last_trans = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < BTRFS_MAX_LEVEL; i++)
|
|
|
|
ASSERT(list_empty(&cache->pending[i]));
|
|
|
|
ASSERT(list_empty(&cache->pending_edge));
|
|
|
|
ASSERT(list_empty(&cache->useless_node));
|
|
|
|
ASSERT(list_empty(&cache->changed));
|
|
|
|
ASSERT(list_empty(&cache->detached));
|
|
|
|
ASSERT(RB_EMPTY_ROOT(&cache->rb_root));
|
|
|
|
ASSERT(!cache->nr_nodes);
|
|
|
|
ASSERT(!cache->nr_edges);
|
|
|
|
}
|
2020-03-23 08:08:34 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle direct tree backref
|
|
|
|
*
|
|
|
|
* Direct tree backref means, the backref item shows its parent bytenr
|
|
|
|
* directly. This is for SHARED_BLOCK_REF backref (keyed or inlined).
|
|
|
|
*
|
|
|
|
* @ref_key: The converted backref key.
|
|
|
|
* For keyed backref, it's the item key.
|
|
|
|
* For inlined backref, objectid is the bytenr,
|
|
|
|
* type is btrfs_inline_ref_type, offset is
|
|
|
|
* btrfs_inline_ref_offset.
|
|
|
|
*/
|
|
|
|
static int handle_direct_tree_backref(struct btrfs_backref_cache *cache,
|
|
|
|
struct btrfs_key *ref_key,
|
|
|
|
struct btrfs_backref_node *cur)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_edge *edge;
|
|
|
|
struct btrfs_backref_node *upper;
|
|
|
|
struct rb_node *rb_node;
|
|
|
|
|
|
|
|
ASSERT(ref_key->type == BTRFS_SHARED_BLOCK_REF_KEY);
|
|
|
|
|
|
|
|
/* Only reloc root uses backref pointing to itself */
|
|
|
|
if (ref_key->objectid == ref_key->offset) {
|
|
|
|
struct btrfs_root *root;
|
|
|
|
|
|
|
|
cur->is_reloc_root = 1;
|
|
|
|
/* Only reloc backref cache cares about a specific root */
|
|
|
|
if (cache->is_reloc) {
|
|
|
|
root = find_reloc_root(cache->fs_info, cur->bytenr);
|
2021-01-14 19:02:44 +00:00
|
|
|
if (!root)
|
2020-03-23 08:08:34 +00:00
|
|
|
return -ENOENT;
|
|
|
|
cur->root = root;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* For generic purpose backref cache, reloc root node
|
|
|
|
* is useless.
|
|
|
|
*/
|
|
|
|
list_add(&cur->list, &cache->useless_node);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
edge = btrfs_backref_alloc_edge(cache);
|
|
|
|
if (!edge)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
rb_node = rb_simple_search(&cache->rb_root, ref_key->offset);
|
|
|
|
if (!rb_node) {
|
|
|
|
/* Parent node not yet cached */
|
|
|
|
upper = btrfs_backref_alloc_node(cache, ref_key->offset,
|
|
|
|
cur->level + 1);
|
|
|
|
if (!upper) {
|
|
|
|
btrfs_backref_free_edge(cache, edge);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Backrefs for the upper level block isn't cached, add the
|
|
|
|
* block to pending list
|
|
|
|
*/
|
|
|
|
list_add_tail(&edge->list[UPPER], &cache->pending_edge);
|
|
|
|
} else {
|
|
|
|
/* Parent node already cached */
|
|
|
|
upper = rb_entry(rb_node, struct btrfs_backref_node, rb_node);
|
|
|
|
ASSERT(upper->checked);
|
|
|
|
INIT_LIST_HEAD(&edge->list[UPPER]);
|
|
|
|
}
|
|
|
|
btrfs_backref_link_edge(edge, cur, upper, LINK_LOWER);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle indirect tree backref
|
|
|
|
*
|
|
|
|
* Indirect tree backref means, we only know which tree the node belongs to.
|
|
|
|
* We still need to do a tree search to find out the parents. This is for
|
|
|
|
* TREE_BLOCK_REF backref (keyed or inlined).
|
|
|
|
*
|
|
|
|
* @ref_key: The same as @ref_key in handle_direct_tree_backref()
|
|
|
|
* @tree_key: The first key of this tree block.
|
2021-05-21 15:42:23 +00:00
|
|
|
* @path: A clean (released) path, to avoid allocating path every time
|
2020-03-23 08:08:34 +00:00
|
|
|
* the function get called.
|
|
|
|
*/
|
|
|
|
static int handle_indirect_tree_backref(struct btrfs_backref_cache *cache,
|
|
|
|
struct btrfs_path *path,
|
|
|
|
struct btrfs_key *ref_key,
|
|
|
|
struct btrfs_key *tree_key,
|
|
|
|
struct btrfs_backref_node *cur)
|
|
|
|
{
|
|
|
|
struct btrfs_fs_info *fs_info = cache->fs_info;
|
|
|
|
struct btrfs_backref_node *upper;
|
|
|
|
struct btrfs_backref_node *lower;
|
|
|
|
struct btrfs_backref_edge *edge;
|
|
|
|
struct extent_buffer *eb;
|
|
|
|
struct btrfs_root *root;
|
|
|
|
struct rb_node *rb_node;
|
|
|
|
int level;
|
|
|
|
bool need_check = true;
|
|
|
|
int ret;
|
|
|
|
|
2020-05-15 17:35:55 +00:00
|
|
|
root = btrfs_get_fs_root(fs_info, ref_key->offset, false);
|
2020-03-23 08:08:34 +00:00
|
|
|
if (IS_ERR(root))
|
|
|
|
return PTR_ERR(root);
|
2020-05-15 06:01:40 +00:00
|
|
|
if (!test_bit(BTRFS_ROOT_SHAREABLE, &root->state))
|
2020-03-23 08:08:34 +00:00
|
|
|
cur->cowonly = 1;
|
|
|
|
|
|
|
|
if (btrfs_root_level(&root->root_item) == cur->level) {
|
|
|
|
/* Tree root */
|
|
|
|
ASSERT(btrfs_root_bytenr(&root->root_item) == cur->bytenr);
|
2020-03-16 06:10:01 +00:00
|
|
|
/*
|
|
|
|
* For reloc backref cache, we may ignore reloc root. But for
|
|
|
|
* general purpose backref cache, we can't rely on
|
|
|
|
* btrfs_should_ignore_reloc_root() as it may conflict with
|
|
|
|
* current running relocation and lead to missing root.
|
|
|
|
*
|
|
|
|
* For general purpose backref cache, reloc root detection is
|
|
|
|
* completely relying on direct backref (key->offset is parent
|
|
|
|
* bytenr), thus only do such check for reloc cache.
|
|
|
|
*/
|
|
|
|
if (btrfs_should_ignore_reloc_root(root) && cache->is_reloc) {
|
2020-03-23 08:08:34 +00:00
|
|
|
btrfs_put_root(root);
|
|
|
|
list_add(&cur->list, &cache->useless_node);
|
|
|
|
} else {
|
|
|
|
cur->root = root;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
level = cur->level + 1;
|
|
|
|
|
|
|
|
/* Search the tree to find parent blocks referring to the block */
|
|
|
|
path->search_commit_root = 1;
|
|
|
|
path->skip_locking = 1;
|
|
|
|
path->lowest_level = level;
|
|
|
|
ret = btrfs_search_slot(NULL, root, tree_key, path, 0, 0);
|
|
|
|
path->lowest_level = 0;
|
|
|
|
if (ret < 0) {
|
|
|
|
btrfs_put_root(root);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
if (ret > 0 && path->slots[level] > 0)
|
|
|
|
path->slots[level]--;
|
|
|
|
|
|
|
|
eb = path->nodes[level];
|
|
|
|
if (btrfs_node_blockptr(eb, path->slots[level]) != cur->bytenr) {
|
|
|
|
btrfs_err(fs_info,
|
|
|
|
"couldn't find block (%llu) (level %d) in tree (%llu) with key (%llu %u %llu)",
|
|
|
|
cur->bytenr, level - 1, root->root_key.objectid,
|
|
|
|
tree_key->objectid, tree_key->type, tree_key->offset);
|
|
|
|
btrfs_put_root(root);
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
lower = cur;
|
|
|
|
|
|
|
|
/* Add all nodes and edges in the path */
|
|
|
|
for (; level < BTRFS_MAX_LEVEL; level++) {
|
|
|
|
if (!path->nodes[level]) {
|
|
|
|
ASSERT(btrfs_root_bytenr(&root->root_item) ==
|
|
|
|
lower->bytenr);
|
2020-03-16 06:10:01 +00:00
|
|
|
/* Same as previous should_ignore_reloc_root() call */
|
|
|
|
if (btrfs_should_ignore_reloc_root(root) &&
|
|
|
|
cache->is_reloc) {
|
2020-03-23 08:08:34 +00:00
|
|
|
btrfs_put_root(root);
|
|
|
|
list_add(&lower->list, &cache->useless_node);
|
|
|
|
} else {
|
|
|
|
lower->root = root;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
edge = btrfs_backref_alloc_edge(cache);
|
|
|
|
if (!edge) {
|
|
|
|
btrfs_put_root(root);
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
eb = path->nodes[level];
|
|
|
|
rb_node = rb_simple_search(&cache->rb_root, eb->start);
|
|
|
|
if (!rb_node) {
|
|
|
|
upper = btrfs_backref_alloc_node(cache, eb->start,
|
|
|
|
lower->level + 1);
|
|
|
|
if (!upper) {
|
|
|
|
btrfs_put_root(root);
|
|
|
|
btrfs_backref_free_edge(cache, edge);
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
upper->owner = btrfs_header_owner(eb);
|
2020-05-15 06:01:40 +00:00
|
|
|
if (!test_bit(BTRFS_ROOT_SHAREABLE, &root->state))
|
2020-03-23 08:08:34 +00:00
|
|
|
upper->cowonly = 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we know the block isn't shared we can avoid
|
|
|
|
* checking its backrefs.
|
|
|
|
*/
|
|
|
|
if (btrfs_block_can_be_shared(root, eb))
|
|
|
|
upper->checked = 0;
|
|
|
|
else
|
|
|
|
upper->checked = 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add the block to pending list if we need to check its
|
|
|
|
* backrefs, we only do this once while walking up a
|
|
|
|
* tree as we will catch anything else later on.
|
|
|
|
*/
|
|
|
|
if (!upper->checked && need_check) {
|
|
|
|
need_check = false;
|
|
|
|
list_add_tail(&edge->list[UPPER],
|
|
|
|
&cache->pending_edge);
|
|
|
|
} else {
|
|
|
|
if (upper->checked)
|
|
|
|
need_check = true;
|
|
|
|
INIT_LIST_HEAD(&edge->list[UPPER]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
upper = rb_entry(rb_node, struct btrfs_backref_node,
|
|
|
|
rb_node);
|
|
|
|
ASSERT(upper->checked);
|
|
|
|
INIT_LIST_HEAD(&edge->list[UPPER]);
|
|
|
|
if (!upper->owner)
|
|
|
|
upper->owner = btrfs_header_owner(eb);
|
|
|
|
}
|
|
|
|
btrfs_backref_link_edge(edge, lower, upper, LINK_LOWER);
|
|
|
|
|
|
|
|
if (rb_node) {
|
|
|
|
btrfs_put_root(root);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
lower = upper;
|
|
|
|
upper = NULL;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
btrfs_release_path(path);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add backref node @cur into @cache.
|
|
|
|
*
|
|
|
|
* NOTE: Even if the function returned 0, @cur is not yet cached as its upper
|
|
|
|
* links aren't yet bi-directional. Needs to finish such links.
|
2020-03-23 08:14:08 +00:00
|
|
|
* Use btrfs_backref_finish_upper_links() to finish such linkage.
|
2020-03-23 08:08:34 +00:00
|
|
|
*
|
|
|
|
* @path: Released path for indirect tree backref lookup
|
|
|
|
* @iter: Released backref iter for extent tree search
|
|
|
|
* @node_key: The first key of the tree block
|
|
|
|
*/
|
|
|
|
int btrfs_backref_add_tree_node(struct btrfs_backref_cache *cache,
|
|
|
|
struct btrfs_path *path,
|
|
|
|
struct btrfs_backref_iter *iter,
|
|
|
|
struct btrfs_key *node_key,
|
|
|
|
struct btrfs_backref_node *cur)
|
|
|
|
{
|
|
|
|
struct btrfs_fs_info *fs_info = cache->fs_info;
|
|
|
|
struct btrfs_backref_edge *edge;
|
|
|
|
struct btrfs_backref_node *exist;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = btrfs_backref_iter_start(iter, cur->bytenr);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
/*
|
|
|
|
* We skip the first btrfs_tree_block_info, as we don't use the key
|
|
|
|
* stored in it, but fetch it from the tree block
|
|
|
|
*/
|
|
|
|
if (btrfs_backref_has_tree_block_info(iter)) {
|
|
|
|
ret = btrfs_backref_iter_next(iter);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
/* No extra backref? This means the tree block is corrupted */
|
|
|
|
if (ret > 0) {
|
|
|
|
ret = -EUCLEAN;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WARN_ON(cur->checked);
|
|
|
|
if (!list_empty(&cur->upper)) {
|
|
|
|
/*
|
|
|
|
* The backref was added previously when processing backref of
|
|
|
|
* type BTRFS_TREE_BLOCK_REF_KEY
|
|
|
|
*/
|
|
|
|
ASSERT(list_is_singular(&cur->upper));
|
|
|
|
edge = list_entry(cur->upper.next, struct btrfs_backref_edge,
|
|
|
|
list[LOWER]);
|
|
|
|
ASSERT(list_empty(&edge->list[UPPER]));
|
|
|
|
exist = edge->node[UPPER];
|
|
|
|
/*
|
|
|
|
* Add the upper level block to pending list if we need check
|
|
|
|
* its backrefs
|
|
|
|
*/
|
|
|
|
if (!exist->checked)
|
|
|
|
list_add_tail(&edge->list[UPPER], &cache->pending_edge);
|
|
|
|
} else {
|
|
|
|
exist = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; ret == 0; ret = btrfs_backref_iter_next(iter)) {
|
|
|
|
struct extent_buffer *eb;
|
|
|
|
struct btrfs_key key;
|
|
|
|
int type;
|
|
|
|
|
|
|
|
cond_resched();
|
|
|
|
eb = btrfs_backref_get_eb(iter);
|
|
|
|
|
|
|
|
key.objectid = iter->bytenr;
|
|
|
|
if (btrfs_backref_iter_is_inline_ref(iter)) {
|
|
|
|
struct btrfs_extent_inline_ref *iref;
|
|
|
|
|
|
|
|
/* Update key for inline backref */
|
|
|
|
iref = (struct btrfs_extent_inline_ref *)
|
|
|
|
((unsigned long)iter->cur_ptr);
|
|
|
|
type = btrfs_get_extent_inline_ref_type(eb, iref,
|
|
|
|
BTRFS_REF_TYPE_BLOCK);
|
|
|
|
if (type == BTRFS_REF_TYPE_INVALID) {
|
|
|
|
ret = -EUCLEAN;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
key.type = type;
|
|
|
|
key.offset = btrfs_extent_inline_ref_offset(eb, iref);
|
|
|
|
} else {
|
|
|
|
key.type = iter->cur_key.type;
|
|
|
|
key.offset = iter->cur_key.offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parent node found and matches current inline ref, no need to
|
|
|
|
* rebuild this node for this inline ref
|
|
|
|
*/
|
|
|
|
if (exist &&
|
|
|
|
((key.type == BTRFS_TREE_BLOCK_REF_KEY &&
|
|
|
|
exist->owner == key.offset) ||
|
|
|
|
(key.type == BTRFS_SHARED_BLOCK_REF_KEY &&
|
|
|
|
exist->bytenr == key.offset))) {
|
|
|
|
exist = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* SHARED_BLOCK_REF means key.offset is the parent bytenr */
|
|
|
|
if (key.type == BTRFS_SHARED_BLOCK_REF_KEY) {
|
|
|
|
ret = handle_direct_tree_backref(cache, &key, cur);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
continue;
|
|
|
|
} else if (unlikely(key.type == BTRFS_EXTENT_REF_V0_KEY)) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
btrfs_print_v0_err(fs_info);
|
|
|
|
btrfs_handle_fs_error(fs_info, ret, NULL);
|
|
|
|
goto out;
|
|
|
|
} else if (key.type != BTRFS_TREE_BLOCK_REF_KEY) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* key.type == BTRFS_TREE_BLOCK_REF_KEY, inline ref offset
|
|
|
|
* means the root objectid. We need to search the tree to get
|
|
|
|
* its parent bytenr.
|
|
|
|
*/
|
|
|
|
ret = handle_indirect_tree_backref(cache, path, &key, node_key,
|
|
|
|
cur);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cur->checked = 1;
|
|
|
|
WARN_ON(exist);
|
|
|
|
out:
|
|
|
|
btrfs_backref_iter_release(iter);
|
|
|
|
return ret;
|
|
|
|
}
|
2020-03-23 08:14:08 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Finish the upwards linkage created by btrfs_backref_add_tree_node()
|
|
|
|
*/
|
|
|
|
int btrfs_backref_finish_upper_links(struct btrfs_backref_cache *cache,
|
|
|
|
struct btrfs_backref_node *start)
|
|
|
|
{
|
|
|
|
struct list_head *useless_node = &cache->useless_node;
|
|
|
|
struct btrfs_backref_edge *edge;
|
|
|
|
struct rb_node *rb_node;
|
|
|
|
LIST_HEAD(pending_edge);
|
|
|
|
|
|
|
|
ASSERT(start->checked);
|
|
|
|
|
|
|
|
/* Insert this node to cache if it's not COW-only */
|
|
|
|
if (!start->cowonly) {
|
|
|
|
rb_node = rb_simple_insert(&cache->rb_root, start->bytenr,
|
|
|
|
&start->rb_node);
|
|
|
|
if (rb_node)
|
|
|
|
btrfs_backref_panic(cache->fs_info, start->bytenr,
|
|
|
|
-EEXIST);
|
|
|
|
list_add_tail(&start->lower, &cache->leaves);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use breadth first search to iterate all related edges.
|
|
|
|
*
|
|
|
|
* The starting points are all the edges of this node
|
|
|
|
*/
|
|
|
|
list_for_each_entry(edge, &start->upper, list[LOWER])
|
|
|
|
list_add_tail(&edge->list[UPPER], &pending_edge);
|
|
|
|
|
|
|
|
while (!list_empty(&pending_edge)) {
|
|
|
|
struct btrfs_backref_node *upper;
|
|
|
|
struct btrfs_backref_node *lower;
|
|
|
|
|
|
|
|
edge = list_first_entry(&pending_edge,
|
|
|
|
struct btrfs_backref_edge, list[UPPER]);
|
|
|
|
list_del_init(&edge->list[UPPER]);
|
|
|
|
upper = edge->node[UPPER];
|
|
|
|
lower = edge->node[LOWER];
|
|
|
|
|
|
|
|
/* Parent is detached, no need to keep any edges */
|
|
|
|
if (upper->detached) {
|
|
|
|
list_del(&edge->list[LOWER]);
|
|
|
|
btrfs_backref_free_edge(cache, edge);
|
|
|
|
|
|
|
|
/* Lower node is orphan, queue for cleanup */
|
|
|
|
if (list_empty(&lower->upper))
|
|
|
|
list_add(&lower->list, useless_node);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* All new nodes added in current build_backref_tree() haven't
|
|
|
|
* been linked to the cache rb tree.
|
|
|
|
* So if we have upper->rb_node populated, this means a cache
|
|
|
|
* hit. We only need to link the edge, as @upper and all its
|
|
|
|
* parents have already been linked.
|
|
|
|
*/
|
|
|
|
if (!RB_EMPTY_NODE(&upper->rb_node)) {
|
|
|
|
if (upper->lowest) {
|
|
|
|
list_del_init(&upper->lower);
|
|
|
|
upper->lowest = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_add_tail(&edge->list[UPPER], &upper->lower);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sanity check, we shouldn't have any unchecked nodes */
|
|
|
|
if (!upper->checked) {
|
|
|
|
ASSERT(0);
|
|
|
|
return -EUCLEAN;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sanity check, COW-only node has non-COW-only parent */
|
|
|
|
if (start->cowonly != upper->cowonly) {
|
|
|
|
ASSERT(0);
|
|
|
|
return -EUCLEAN;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only cache non-COW-only (subvolume trees) tree blocks */
|
|
|
|
if (!upper->cowonly) {
|
|
|
|
rb_node = rb_simple_insert(&cache->rb_root, upper->bytenr,
|
|
|
|
&upper->rb_node);
|
|
|
|
if (rb_node) {
|
|
|
|
btrfs_backref_panic(cache->fs_info,
|
|
|
|
upper->bytenr, -EEXIST);
|
|
|
|
return -EUCLEAN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
list_add_tail(&edge->list[UPPER], &upper->lower);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Also queue all the parent edges of this uncached node
|
|
|
|
* to finish the upper linkage
|
|
|
|
*/
|
|
|
|
list_for_each_entry(edge, &upper->upper, list[LOWER])
|
|
|
|
list_add_tail(&edge->list[UPPER], &pending_edge);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2020-03-23 08:57:15 +00:00
|
|
|
|
|
|
|
void btrfs_backref_error_cleanup(struct btrfs_backref_cache *cache,
|
|
|
|
struct btrfs_backref_node *node)
|
|
|
|
{
|
|
|
|
struct btrfs_backref_node *lower;
|
|
|
|
struct btrfs_backref_node *upper;
|
|
|
|
struct btrfs_backref_edge *edge;
|
|
|
|
|
|
|
|
while (!list_empty(&cache->useless_node)) {
|
|
|
|
lower = list_first_entry(&cache->useless_node,
|
|
|
|
struct btrfs_backref_node, list);
|
|
|
|
list_del_init(&lower->list);
|
|
|
|
}
|
|
|
|
while (!list_empty(&cache->pending_edge)) {
|
|
|
|
edge = list_first_entry(&cache->pending_edge,
|
|
|
|
struct btrfs_backref_edge, list[UPPER]);
|
|
|
|
list_del(&edge->list[UPPER]);
|
|
|
|
list_del(&edge->list[LOWER]);
|
|
|
|
lower = edge->node[LOWER];
|
|
|
|
upper = edge->node[UPPER];
|
|
|
|
btrfs_backref_free_edge(cache, edge);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Lower is no longer linked to any upper backref nodes and
|
|
|
|
* isn't in the cache, we can free it ourselves.
|
|
|
|
*/
|
|
|
|
if (list_empty(&lower->upper) &&
|
|
|
|
RB_EMPTY_NODE(&lower->rb_node))
|
|
|
|
list_add(&lower->list, &cache->useless_node);
|
|
|
|
|
|
|
|
if (!RB_EMPTY_NODE(&upper->rb_node))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Add this guy's upper edges to the list to process */
|
|
|
|
list_for_each_entry(edge, &upper->upper, list[LOWER])
|
|
|
|
list_add_tail(&edge->list[UPPER],
|
|
|
|
&cache->pending_edge);
|
|
|
|
if (list_empty(&upper->upper))
|
|
|
|
list_add(&upper->list, &cache->useless_node);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!list_empty(&cache->useless_node)) {
|
|
|
|
lower = list_first_entry(&cache->useless_node,
|
|
|
|
struct btrfs_backref_node, list);
|
|
|
|
list_del_init(&lower->list);
|
|
|
|
if (lower == node)
|
|
|
|
node = NULL;
|
2020-12-16 16:22:11 +00:00
|
|
|
btrfs_backref_drop_node(cache, lower);
|
2020-03-23 08:57:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
btrfs_backref_cleanup_node(cache, node);
|
|
|
|
ASSERT(list_empty(&cache->useless_node) &&
|
|
|
|
list_empty(&cache->pending_edge));
|
|
|
|
}
|