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
4748static 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 ;
99143static struct btrfs_trans_handle * g_trans = NULL ;
100144static struct list_head * g_subvols ;
101145static 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+
137257static 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