Skip to content

Commit ef11574

Browse files
adam900710kdave
authored andcommitted
btrfs-progs: mkfs: add hard link support for --rootdir
The new hard link detection and creation support is done by maintaining an rb tree with the following members: - st_ino, st_dev This is to record the stat() report from the host fs. With this two, we can detect if it's really a hard link (st_dev determines one filesystem/subvolume, and st_ino determines the inode number inside the fs). - root This is btrfs root pointer. This a special requirement for the recent introduced "--subvol" option. As we can have the following corner case: rootdir/ |- foobar_hardlink1 |- foobar_hardlink2 |- subv/ <- To be a subvolume inside btrfs |- foobar_hardlink3 In above case, on the host fs, `subv/` directory is just a regular directory, but in the new btrfs it will be a subvolume. In that case, `foobar_hardlink3` cannot be created as a hard link, but a new inode. - st_nlink and found_nlink Records the original reported number of links, and the nlinks we created inside btrfs. This is recorded in case we created all hard links and can remove the entry early. - btrfs_ino This is the inode number inside btrfs. And since we can handle hard links safely, remove all the related warnings, and add a new note for `--subvol` option, warning about the case where we need to split hard links due to subvolume boundary. Pull-request: #873 Signed-off-by: Qu Wenruo <[email protected]>
1 parent e55ee92 commit ef11574

File tree

2 files changed

+185
-30
lines changed

2 files changed

+185
-30
lines changed

Documentation/mkfs.btrfs.rst

+13
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ OPTIONS
165165
* *default*: create as default subvolume (this can only be specified once)
166166
* *ro*: create as readonly subvolume
167167

168+
If there are hard links inside *rootdir* and *subdir* will split the
169+
subvolumes, like the following case::
170+
171+
rootdir/
172+
|- hardlink1
173+
|- hardlink2
174+
|- subdir/ <- will be a subvolume
175+
|- hardlink3
176+
177+
In that case we cannot create `hardlink3` as hardlinks of
178+
`hardlink1` and `hardlink2` because hardlink3 will be inside a new
179+
subvolume.
180+
168181
--shrink
169182
Shrink the filesystem to its minimal size, only works with *--rootdir* option.
170183

mkfs/rootdir.c

+172-30
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "common/extent-tree-utils.h"
4343
#include "common/root-tree-utils.h"
4444
#include "common/path-utils.h"
45+
#include "common/rbtree-utils.h"
4546
#include "mkfs/rootdir.h"
4647

4748
static u32 fs_block_size;
@@ -74,6 +75,52 @@ struct inode_entry {
7475
struct list_head list;
7576
};
7677

78+
/*
79+
* Record all the hard links we found for a specific file inside
80+
* rootdir.
81+
*
82+
* The search is based on (root, st_dev, st_ino).
83+
* The reason for @root as a search index is, for hard links separated by
84+
* subvolume boundaries:
85+
*
86+
* rootdir/
87+
* |- foobar_hardlink1
88+
* |- foobar_hardlink2
89+
* |- subv/ <- Will be created as a subvolume
90+
* |- foobar_hardlink3.
91+
*
92+
* Since all the 3 hard links are inside the same rootdir and the same
93+
* filesystem, on the host fs they are all hard links to the same inode.
94+
*
95+
* But for the btrfs we are building, only hardlink1 and hardlink2 can be
96+
* created as hardlinks. Since we cannot create hardlink across subvolume.
97+
* So we need @root as a search index to handle such case.
98+
*/
99+
struct hardlink_entry {
100+
struct rb_node node;
101+
/*
102+
* The following three members are reported from the stat() of the
103+
* host filesystem.
104+
*
105+
* For st_nlink we cannot trust it unconditionally, as
106+
* some hard links may be out of rootdir.
107+
* If @found_nlink reached @st_nlink, we know we have created all
108+
* the hard links and can remove the entry.
109+
*/
110+
dev_t st_dev;
111+
ino_t st_ino;
112+
nlink_t st_nlink;
113+
114+
/* The following two are inside the new btrfs. */
115+
struct btrfs_root *root;
116+
u64 btrfs_ino;
117+
118+
/* How many hard links we have created. */
119+
nlink_t found_nlink;
120+
};
121+
122+
static struct rb_root hardlink_root = RB_ROOT;
123+
77124
/*
78125
* The path towards the rootdir.
79126
*
@@ -93,9 +140,6 @@ static struct rootdir_path current_path = {
93140
.level = 0,
94141
};
95142

96-
/* Track if a hardlink was found and a warning was printed. */
97-
static bool g_hardlink_warning;
98-
static u64 g_hardlink_count;
99143
static struct btrfs_trans_handle *g_trans = NULL;
100144
static struct list_head *g_subvols;
101145
static u64 next_subvol_id = BTRFS_FIRST_FREE_OBJECTID;
@@ -134,6 +178,82 @@ static int rootdir_path_push(struct rootdir_path *path, struct btrfs_root *root,
134178
return 0;
135179
}
136180

181+
static int hardlink_compare_nodes(const struct rb_node *node1,
182+
const struct rb_node *node2)
183+
{
184+
const struct hardlink_entry *entry1;
185+
const struct hardlink_entry *entry2;
186+
187+
entry1 = rb_entry(node1, struct hardlink_entry, node);
188+
entry2 = rb_entry(node2, struct hardlink_entry, node);
189+
UASSERT(entry1->root);
190+
UASSERT(entry2->root);
191+
192+
if (entry1->st_dev < entry2->st_dev)
193+
return -1;
194+
if (entry1->st_dev > entry2->st_dev)
195+
return 1;
196+
if (entry1->st_ino < entry2->st_ino)
197+
return -1;
198+
if (entry1->st_ino > entry2->st_ino)
199+
return 1;
200+
if (entry1->root < entry2->root)
201+
return -1;
202+
if (entry1->root > entry2->root)
203+
return 1;
204+
return 0;
205+
}
206+
207+
static struct hardlink_entry *find_hard_link(struct btrfs_root *root,
208+
const struct stat *st)
209+
{
210+
struct rb_node *node;
211+
const struct hardlink_entry tmp = {
212+
.st_dev = st->st_dev,
213+
.st_ino = st->st_ino,
214+
.root = root,
215+
};
216+
217+
node = rb_search(&hardlink_root, &tmp,
218+
(rb_compare_keys)hardlink_compare_nodes, NULL);
219+
if (node)
220+
return rb_entry(node, struct hardlink_entry, node);
221+
return NULL;
222+
}
223+
224+
static int add_hard_link(struct btrfs_root *root, u64 btrfs_ino,
225+
const struct stat *st)
226+
{
227+
struct hardlink_entry *new;
228+
int ret;
229+
230+
UASSERT(st->st_nlink > 1);
231+
232+
new = calloc(1, sizeof(*new));
233+
if (!new)
234+
return -ENOMEM;
235+
236+
new->root = root;
237+
new->btrfs_ino = btrfs_ino;
238+
new->found_nlink = 1;
239+
new->st_dev = st->st_dev;
240+
new->st_ino = st->st_ino;
241+
new->st_nlink = st->st_nlink;
242+
ret = rb_insert(&hardlink_root, &new->node, hardlink_compare_nodes);
243+
if (ret) {
244+
free(new);
245+
return -EEXIST;
246+
}
247+
return 0;
248+
}
249+
250+
static void free_one_hardlink(struct rb_node *node)
251+
{
252+
struct hardlink_entry *entry = rb_entry(node, struct hardlink_entry, node);
253+
254+
free(entry);
255+
}
256+
137257
static void stat_to_inode_item(struct btrfs_inode_item *dst, const struct stat *st)
138258
{
139259
/*
@@ -502,29 +622,10 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
502622
struct btrfs_inode_item inode_item = { 0 };
503623
struct inode_entry *parent;
504624
struct rootdir_subvol *rds;
625+
const bool have_hard_links = (!S_ISDIR(st->st_mode) && st->st_nlink > 1);
505626
u64 ino;
506627
int ret;
507628

508-
/*
509-
* Hard link needs extra detection code, not supported for now, but
510-
* it's not to break anything but splitting the hard links into new
511-
* inodes. And we do not even know if the hard links are inside the
512-
* rootdir.
513-
*
514-
* So here we only need to do extra warning.
515-
*
516-
* On most filesystems st_nlink of a directory is the number of
517-
* subdirs, including "." and "..", so skip directory inodes.
518-
*/
519-
if (unlikely(!S_ISDIR(st->st_mode) && st->st_nlink > 1)) {
520-
if (!g_hardlink_warning) {
521-
warning("'%s' has extra hardlinks, they will be converted into new inodes",
522-
full_path);
523-
g_hardlink_warning = true;
524-
}
525-
g_hardlink_count++;
526-
}
527-
528629
/* The rootdir itself. */
529630
if (unlikely(ftwbuf->level == 0)) {
530631
u64 root_ino;
@@ -624,6 +725,37 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
624725
parent = rootdir_path_last(&current_path);
625726
root = parent->root;
626727

728+
/* For non-directory inode, check if there is already any hard link. */
729+
if (have_hard_links) {
730+
struct hardlink_entry *found;
731+
732+
found = find_hard_link(root, st);
733+
/*
734+
* Can only add the hard link if it doesn't cross subvolume
735+
* boundary.
736+
*/
737+
if (found && found->root == root) {
738+
ret = btrfs_add_link(g_trans, root, found->btrfs_ino,
739+
parent->ino, full_path + ftwbuf->base,
740+
strlen(full_path) - ftwbuf->base,
741+
ftype_to_btrfs_type(st->st_mode),
742+
NULL, 1, 0);
743+
if (ret < 0) {
744+
errno = -ret;
745+
error(
746+
"failed to add link for hard link ('%s'): %m", full_path);
747+
return ret;
748+
}
749+
found->found_nlink++;
750+
/* We found all hard links for it. Can remove the entry. */
751+
if (found->found_nlink >= found->st_nlink) {
752+
rb_erase(&found->node, &hardlink_root);
753+
free(found);
754+
}
755+
return 0;
756+
}
757+
}
758+
627759
ret = btrfs_find_free_objectid(g_trans, root,
628760
BTRFS_FIRST_FREE_OBJECTID, &ino);
629761
if (ret < 0) {
@@ -639,7 +771,6 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
639771
error("failed to insert inode item %llu for '%s': %m", ino, full_path);
640772
return ret;
641773
}
642-
643774
ret = btrfs_add_link(g_trans, root, ino, parent->ino,
644775
full_path + ftwbuf->base,
645776
strlen(full_path) - ftwbuf->base,
@@ -650,6 +781,22 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
650781
error("failed to add link for inode %llu ('%s'): %m", ino, full_path);
651782
return ret;
652783
}
784+
785+
/*
786+
* Found a possible hard link, add it into the hard link rb tree for
787+
* future detection.
788+
*/
789+
if (have_hard_links) {
790+
ret = add_hard_link(root, ino, st);
791+
if (ret < 0) {
792+
errno = -ret;
793+
error("failed to add hard link record for '%s': %m",
794+
full_path);
795+
return ret;
796+
}
797+
ret = 0;
798+
}
799+
653800
/*
654801
* btrfs_add_link() has increased the nlink to 1 in the metadata.
655802
* Also update the value in case we need to update the inode item
@@ -759,8 +906,6 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir
759906
}
760907

761908
g_trans = trans;
762-
g_hardlink_warning = false;
763-
g_hardlink_count = 0;
764909
g_subvols = subvols;
765910
INIT_LIST_HEAD(&current_path.inode_list);
766911

@@ -770,10 +915,6 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir
770915
return ret;
771916
}
772917

773-
if (g_hardlink_warning)
774-
warning("%llu hardlinks were detected in %s, all converted to new inodes",
775-
g_hardlink_count, source_dir);
776-
777918
while (current_path.level > 0)
778919
rootdir_path_pop(&current_path);
779920

@@ -785,6 +926,7 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir
785926
}
786927
}
787928

929+
rb_free_nodes(&hardlink_root, free_one_hardlink);
788930
return 0;
789931
}
790932

0 commit comments

Comments
 (0)