42
42
#include "common/extent-tree-utils.h"
43
43
#include "common/root-tree-utils.h"
44
44
#include "common/path-utils.h"
45
+ #include "common/rbtree-utils.h"
45
46
#include "mkfs/rootdir.h"
46
47
47
48
static u32 fs_block_size ;
@@ -74,6 +75,52 @@ struct inode_entry {
74
75
struct list_head list ;
75
76
};
76
77
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
+
77
124
/*
78
125
* The path towards the rootdir.
79
126
*
@@ -93,9 +140,6 @@ static struct rootdir_path current_path = {
93
140
.level = 0 ,
94
141
};
95
142
96
- /* Track if a hardlink was found and a warning was printed. */
97
- static bool g_hardlink_warning ;
98
- static u64 g_hardlink_count ;
99
143
static struct btrfs_trans_handle * g_trans = NULL ;
100
144
static struct list_head * g_subvols ;
101
145
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,
134
178
return 0 ;
135
179
}
136
180
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
+
137
257
static void stat_to_inode_item (struct btrfs_inode_item * dst , const struct stat * st )
138
258
{
139
259
/*
@@ -502,29 +622,10 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
502
622
struct btrfs_inode_item inode_item = { 0 };
503
623
struct inode_entry * parent ;
504
624
struct rootdir_subvol * rds ;
625
+ const bool have_hard_links = (!S_ISDIR (st -> st_mode ) && st -> st_nlink > 1 );
505
626
u64 ino ;
506
627
int ret ;
507
628
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
-
528
629
/* The rootdir itself. */
529
630
if (unlikely (ftwbuf -> level == 0 )) {
530
631
u64 root_ino ;
@@ -624,6 +725,37 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
624
725
parent = rootdir_path_last (& current_path );
625
726
root = parent -> root ;
626
727
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
+
627
759
ret = btrfs_find_free_objectid (g_trans , root ,
628
760
BTRFS_FIRST_FREE_OBJECTID , & ino );
629
761
if (ret < 0 ) {
@@ -639,7 +771,6 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
639
771
error ("failed to insert inode item %llu for '%s': %m" , ino , full_path );
640
772
return ret ;
641
773
}
642
-
643
774
ret = btrfs_add_link (g_trans , root , ino , parent -> ino ,
644
775
full_path + ftwbuf -> base ,
645
776
strlen (full_path ) - ftwbuf -> base ,
@@ -650,6 +781,22 @@ static int ftw_add_inode(const char *full_path, const struct stat *st,
650
781
error ("failed to add link for inode %llu ('%s'): %m" , ino , full_path );
651
782
return ret ;
652
783
}
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
+
653
800
/*
654
801
* btrfs_add_link() has increased the nlink to 1 in the metadata.
655
802
* 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
759
906
}
760
907
761
908
g_trans = trans ;
762
- g_hardlink_warning = false;
763
- g_hardlink_count = 0 ;
764
909
g_subvols = subvols ;
765
910
INIT_LIST_HEAD (& current_path .inode_list );
766
911
@@ -770,10 +915,6 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir
770
915
return ret ;
771
916
}
772
917
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
-
777
918
while (current_path .level > 0 )
778
919
rootdir_path_pop (& current_path );
779
920
@@ -785,6 +926,7 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir
785
926
}
786
927
}
787
928
929
+ rb_free_nodes (& hardlink_root , free_one_hardlink );
788
930
return 0 ;
789
931
}
790
932
0 commit comments