From patchwork Thu Aug 15 04:38:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qu Wenruo X-Patchwork-Id: 13764392 Received: from smtp-out1.suse.de (smtp-out1.suse.de [195.135.223.130]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EF45617C64 for ; Thu, 15 Aug 2024 04:38:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.130 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723696732; cv=none; b=dDfCu6R0/sUDP1/vSjImSliTPKGoxJ+rzwg3cYvFKW2sZRdKQj8d0VM5/OT3V5DHGlLEYElBKN67zgYo7hf+bPi/SlPSsxQZj6FpLpjZiCOFyJY4YNN8Ho4old+/OJpQwqxoAFu38357JktsSEmSsU1Ky+Oa5THapU1JGv8ArIs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723696732; c=relaxed/simple; bh=GrasIIYSRSS/WO2o6qF3RE2xeoCEugD+1/Dp6xI2bFw=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=EFAoiy4i4uq7CK4omJcwxLFdftuWpHR/lD8H5xF+TV6cPQ/yow1KvgTddQst4BPnEOtK8ZLPCMDIDP7t+7HM0YdDgRL1W/zVDF4fZ8W/m5DlfmOH+JxqkvE0GkI4UhWR0Fl0xl8/PFYuT+cZhy37Ai6NG4CSUJMUpOAqQy69LC8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=XR8PGokx; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=XR8PGokx; arc=none smtp.client-ip=195.135.223.130 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="XR8PGokx"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="XR8PGokx" Received: from imap1.dmz-prg2.suse.org (unknown [10.150.64.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id DA7562224B for ; Thu, 15 Aug 2024 04:38:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1723696726; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=HtIhL2qEGTeJx36wbGO7gz1bN3SnD51fU6575+jMEs8=; b=XR8PGokxw7vx3kKqPTnZGuOgQPqzB4SeK+aYZci5nR4IkVe4U7rv2b+vGOyyRUgzRhwUGi TKcc4CoOPV67nvTfJk1/6hhgh57XxHW3X6INY8y+A9p6ENeUuWrElLKao/jFjXMrqC9X/j c/j2NEtD+bqw6BzgPrts6o2q5nbdWR8= Authentication-Results: smtp-out1.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1723696726; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=HtIhL2qEGTeJx36wbGO7gz1bN3SnD51fU6575+jMEs8=; b=XR8PGokxw7vx3kKqPTnZGuOgQPqzB4SeK+aYZci5nR4IkVe4U7rv2b+vGOyyRUgzRhwUGi TKcc4CoOPV67nvTfJk1/6hhgh57XxHW3X6INY8y+A9p6ENeUuWrElLKao/jFjXMrqC9X/j c/j2NEtD+bqw6BzgPrts6o2q5nbdWR8= Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id 0B2D313983 for ; Thu, 15 Aug 2024 04:38:45 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id yD35LVWGvWaoPgAAD6G6ig (envelope-from ) for ; Thu, 15 Aug 2024 04:38:45 +0000 From: Qu Wenruo To: linux-btrfs@vger.kernel.org Subject: [PATCH 1/2] btrfs-progs: mkfs/rootdir: add hard link support Date: Thu, 15 Aug 2024 14:08:17 +0930 Message-ID: <694d6d9d743336910c4f6512d5eb69d2f35e332b.1723696468.git.wqu@suse.com> X-Mailer: git-send-email 2.46.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-btrfs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Spam-Flag: NO X-Spam-Score: -2.80 X-Spam-Level: X-Spamd-Result: default: False [-2.80 / 50.00]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; FUZZY_BLOCKED(0.00)[rspamd.com]; RCVD_VIA_SMTP_AUTH(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; ARC_NA(0.00)[]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; DBL_BLOCKED_OPENRESOLVER(0.00)[imap1.dmz-prg2.suse.org:helo,suse.com:email,suse.com:mid]; FROM_EQ_ENVFROM(0.00)[]; FROM_HAS_DN(0.00)[]; MIME_TRACE(0.00)[0:+]; RCVD_COUNT_TWO(0.00)[2]; TO_MATCH_ENVRCPT_ALL(0.00)[]; TO_DN_NONE(0.00)[]; PREVIOUSLY_DELIVERED(0.00)[linux-btrfs@vger.kernel.org]; RCVD_TLS_ALL(0.00)[] 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` can not 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. Signed-off-by: Qu Wenruo --- Documentation/mkfs.btrfs.rst | 13 +++ mkfs/rootdir.c | 200 +++++++++++++++++++++++++++++------ 2 files changed, 182 insertions(+), 31 deletions(-) diff --git a/Documentation/mkfs.btrfs.rst b/Documentation/mkfs.btrfs.rst index 0e9e84adffc8..a7acd6b0019c 100644 --- a/Documentation/mkfs.btrfs.rst +++ b/Documentation/mkfs.btrfs.rst @@ -160,6 +160,19 @@ OPTIONS directory. The option *--rootdir* must also be specified, and *subdir* must be an existing subdirectory within it. This option can be specified multiple times. + If there are hard links inside *rootdir* and *subdir* will split the + subvolumes, like the following case:: + + rootdir/ + |- hardlink1 + |- hardlink2 + |- subdir/ <- will be a subvolume + |- hardlink3 + + In that case we can not create `hardlink3` as a hardlinks of + `hardlink1` and `hardlink2`. + Because hardlink3 will be inside a new subvolume. + --shrink Shrink the filesystem to its minimal size, only works with *--rootdir* option. diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c index 7976d93ea560..3da3c8247d08 100644 --- a/mkfs/rootdir.c +++ b/mkfs/rootdir.c @@ -42,6 +42,7 @@ #include "common/extent-tree-utils.h" #include "common/root-tree-utils.h" #include "common/path-utils.h" +#include "common/rbtree-utils.h" #include "mkfs/rootdir.h" static u32 fs_block_size; @@ -74,6 +75,50 @@ struct inode_entry { struct list_head list; }; +/* + * Record all the hard links we found for a specific file inside + * rootdir. + * + * The search is based on (root, st_dev, st_ino). + * The reason for @root as a search index is, for hard links separated by + * subvolume boundaries: + * + * rootdir/ + * |- foobar_hardlink1 + * |- foobar_hardlink2 + * |- subv/ <- Will be created as a subvolume + * |- foobar_hardlink3. + * + * Since all the 3 hard links are inside the same rootdir and the same + * filesystem, on the host fs they are all hard links to the same inode. + * + * But for created btrfs, only hardlink1 and hardlink2 can be created as + * hardlinks. Since we can not create hardlink across subvolume. + */ +struct hardlink_entry { + struct rb_node node; + /* + * The following two are reported from the stat() of the + * host filesystem. + * + * For st_nlink we can not trust it unconditionally, as + * some hard links may be out of rootdir. + * If @found_nlink reached @st_nlink, we know we have created all + * the hard links and can remove the entry. + */ + dev_t st_dev; + ino_t st_ino; + nlink_t st_nlink; + + /* The following two are inside the new btrfs. */ + struct btrfs_root *root; + u64 btrfs_ino; + /* How many hard links we have created. */ + nlink_t found_nlink; +}; + +static struct rb_root hardlink_root = RB_ROOT; + /* * The path towards the rootdir. * @@ -93,9 +138,6 @@ static struct rootdir_path current_path = { .level = 0, }; -/* Track if a hardlink was found and a warning was printed. */ -static bool g_hardlink_warning; -static u64 g_hardlink_count; static struct btrfs_trans_handle *g_trans = NULL; static struct list_head *g_subvols; static u64 next_subvol_id = BTRFS_FIRST_FREE_OBJECTID; @@ -133,6 +175,82 @@ static int rootdir_path_push(struct rootdir_path *path, struct btrfs_root *root, return 0; } +static int hardlink_compare_nodes(const struct rb_node *node1, + const struct rb_node *node2) +{ + const struct hardlink_entry *entry1; + const struct hardlink_entry *entry2; + + entry1 = rb_entry(node1, struct hardlink_entry, node); + entry2 = rb_entry(node2, struct hardlink_entry, node); + UASSERT(entry1->root); + UASSERT(entry2->root); + + if (entry1->st_dev < entry2->st_dev) + return -1; + if (entry1->st_dev > entry2->st_dev) + return 1; + if (entry1->st_ino < entry2->st_ino) + return -1; + if (entry1->st_ino > entry2->st_ino) + return 1; + if (entry1->root < entry2->root) + return -1; + if (entry1->root > entry2->root) + return 1; + return 0; +} + +static struct hardlink_entry *find_hard_link(struct btrfs_root *root, + const struct stat *st) +{ + struct rb_node *node; + struct hardlink_entry tmp; + + tmp.st_dev = st->st_dev; + tmp.st_ino = st->st_ino; + tmp.root = root; + + node = rb_search(&hardlink_root, &tmp, + (rb_compare_keys)hardlink_compare_nodes, NULL); + if (node) + return rb_entry(node, struct hardlink_entry, node); + return NULL; +} + +static int add_hard_link(struct btrfs_root *root, u64 btrfs_ino, + const struct stat *st) +{ + struct hardlink_entry *new; + int ret; + + UASSERT(st->st_nlink > 1); + + new = calloc(1, sizeof(*new)); + if (!new) + return -ENOMEM; + + new->root = root; + new->btrfs_ino = btrfs_ino; + new->found_nlink = 1; + new->st_dev = st->st_dev; + new->st_ino = st->st_ino; + new->st_nlink = st->st_nlink; + ret = rb_insert(&hardlink_root, &new->node, hardlink_compare_nodes); + if (ret) { + free(new); + return -EEXIST; + } + return 0; +} + +static void free_one_hardlink(struct rb_node *node) +{ + struct hardlink_entry *entry = rb_entry(node, struct hardlink_entry, node); + + free(entry); +} + static void stat_to_inode_item(struct btrfs_inode_item *dst, const struct stat *st) { /* @@ -498,29 +616,10 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, struct btrfs_inode_item inode_item = { 0 }; struct inode_entry *parent; struct rootdir_subvol *rds; + const bool have_hard_links = (!S_ISDIR(st->st_mode) && st->st_nlink > 1); u64 ino; int ret; - /* - * Hard link needs extra detection code, not supported for now, but - * it's not to break anything but splitting the hard links into new - * inodes. And we do not even know if the hard links are inside the - * rootdir. - * - * So here we only need to do extra warning. - * - * On most filesystems st_nlink of a directory is the number of - * subdirs, including "." and "..", so skip directory inodes. - */ - if (unlikely(!S_ISDIR(st->st_mode) && st->st_nlink > 1)) { - if (!g_hardlink_warning) { - warning("'%s' has extra hardlinks, they will be converted into new inodes", - full_path); - g_hardlink_warning = true; - } - g_hardlink_count++; - } - /* The rootdir itself. */ if (unlikely(ftwbuf->level == 0)) { u64 root_ino; @@ -620,6 +719,36 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, parent = rootdir_path_last(¤t_path); root = parent->root; + /* For non-directory inode, check if there is already any hard link. */ + if (have_hard_links) { + struct hardlink_entry *found; + found = find_hard_link(root, st); + /* + * Can only add the hard link if it doesn't cross subvolume + * boundary. + */ + if (found && found->root == root) { + ret = btrfs_add_link(g_trans, root, found->btrfs_ino, + parent->ino, full_path + ftwbuf->base, + strlen(full_path) - ftwbuf->base, + ftype_to_btrfs_type(st->st_mode), + NULL, 1, 0); + if (ret < 0) { + errno = -ret; + error( + "failed to add link for hard link ('%s'): %m", full_path); + return ret; + } + found->found_nlink++; + /* We found all hard links for it. Can remove the entry. */ + if (found->found_nlink >= found->st_nlink) { + rb_erase(&found->node, &hardlink_root); + free(found); + } + return 0; + } + } + ret = btrfs_find_free_objectid(g_trans, root, BTRFS_FIRST_FREE_OBJECTID, &ino); if (ret < 0) { @@ -635,7 +764,6 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, error("failed to insert inode item %llu for '%s': %m", ino, full_path); return ret; } - ret = btrfs_add_link(g_trans, root, ino, parent->ino, full_path + ftwbuf->base, strlen(full_path) - ftwbuf->base, @@ -646,6 +774,22 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, error("failed to add link for inode %llu ('%s'): %m", ino, full_path); return ret; } + + /* + * Found a possible hard link, add it into the hard link rb tree for + * future detection. + */ + if (have_hard_links) { + ret = add_hard_link(root, ino, st); + if (ret < 0) { + errno = -ret; + error("failed to add hard link record for '%s': %m", + full_path); + return ret; + } + ret = 0; + } + /* * btrfs_add_link() has increased the nlink to 1 in the metadata. * Also update the value in case we need to update the inode item @@ -715,8 +859,6 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir g_trans = trans; g_subvols = subvols; - g_hardlink_warning = false; - g_hardlink_count = 0; INIT_LIST_HEAD(¤t_path.inode_list); ret = nftw(source_dir, ftw_add_inode, 32, FTW_PHYS); @@ -725,13 +867,9 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir return ret; } - if (g_hardlink_warning) - warning("%llu hardlinks were detected in %s, all converted to new inodes", - g_hardlink_count, source_dir); - while (current_path.level > 0) rootdir_path_pop(¤t_path); - + rb_free_nodes(&hardlink_root, free_one_hardlink); return 0; } From patchwork Thu Aug 15 04:38:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qu Wenruo X-Patchwork-Id: 13764393 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E64FA39AE3 for ; Thu, 15 Aug 2024 04:38:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723696734; cv=none; b=VZsLTB9SPAHrAodleDplYI6pKImB5FJJ/qMpd2/g8hduAyh/mTy+3xzp468iB2vMuqZuEsI4fPLr0KyfiB7R/zY+WR/wLwtPK6Y4RJlLE5u5+3sxINbwJYz7eUxPjoSUpzTA/lSmcowvKm5l2AFVBYcD/ecJ3pMquhrqArh0foo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723696734; c=relaxed/simple; bh=gOcEdDdhCtMlf7bYeDfggDHJCySmcceqdIN1c8+f30Y=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QwYeIMhA98QXw+rP9kMFcim7N26ROvW3jLjYWkWl3fDTDU554sEwROx+pb2uIN9c5jp6dCgh3QW8f0UOxnOm49imfoJJdX4ZpPuC8ee3EAJWLQrdB8wUt1qgWJUT66gNbR/PtjX3WlnGMU2sG7MLxsynAACht47EThsEVnLgRSM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=G5j/G7EM; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b=dRjExN6f; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="G5j/G7EM"; dkim=pass (1024-bit key) header.d=suse.com header.i=@suse.com header.b="dRjExN6f" Received: from imap1.dmz-prg2.suse.org (unknown [10.150.64.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 442511FFAA for ; Thu, 15 Aug 2024 04:38:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1723696730; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=JSTrqZsyQhVGfpm87DlK6QCELa9pvck6rvkTTvDCGbY=; b=G5j/G7EMpkMmll4H5Fk9ayoZSR07xiJiapJ7I9VIl0Jg97PGZjy9QZksSIGmeXUHXhPooF vpRJx4vl+vLZ92dHfNlyKwpvZ74F+NeeGikt5TatvLQm02Zy0Egnb/Z3A/hnr6UbUH3Suu +qdJlCH4XygzrX9/eFzEil1v+XEKZsY= Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1723696728; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=JSTrqZsyQhVGfpm87DlK6QCELa9pvck6rvkTTvDCGbY=; b=dRjExN6fMWh3W2NP5JOTPEhiuHtlWLk5hrhPUHXAm+grjSCsrJlU82YR9pMicPU0bDM4Ci 7WhhV0tGRKxeQNSFHsCYHziv/oYgLCdqIweZ323j4fJ9mFDL4FmRIMQBUNWmo+LhH8s2Qb Uw9hPmAMYimvlzCx5io1Bir2ugLWvLw= Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id 685C013983 for ; Thu, 15 Aug 2024 04:38:47 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id CDcNCVeGvWaoPgAAD6G6ig (envelope-from ) for ; Thu, 15 Aug 2024 04:38:47 +0000 From: Qu Wenruo To: linux-btrfs@vger.kernel.org Subject: [PATCH 2/2] btrfs-progs: mkfs-tests: add hardlink related tests for --subvol Date: Thu, 15 Aug 2024 14:08:18 +0930 Message-ID: X-Mailer: git-send-email 2.46.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-btrfs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Spam-Score: -2.80 X-Spam-Flag: NO X-Spamd-Result: default: False [-2.80 / 50.00]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; FUZZY_BLOCKED(0.00)[rspamd.com]; RCVD_VIA_SMTP_AUTH(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; ARC_NA(0.00)[]; DKIM_SIGNED(0.00)[suse.com:s=susede1]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.com:mid,suse.com:email,imap1.dmz-prg2.suse.org:helo]; FROM_EQ_ENVFROM(0.00)[]; FROM_HAS_DN(0.00)[]; MIME_TRACE(0.00)[0:+]; RCVD_COUNT_TWO(0.00)[2]; TO_MATCH_ENVRCPT_ALL(0.00)[]; TO_DN_NONE(0.00)[]; PREVIOUSLY_DELIVERED(0.00)[linux-btrfs@vger.kernel.org]; RCVD_TLS_ALL(0.00)[] X-Spam-Level: This introduces two new cases: - 3 hardlinks without any subvolume This should results 3 hard links inside the btrfs. - 3 hardlinks, but a subvolume will split 2 of them Then the 2 inside the same subvolume should still report 2 nlinks, but the lone one inside the new subvolume can only report 1 nlink. Signed-off-by: Qu Wenruo --- mkfs/rootdir.c | 8 +-- tests/mkfs-tests/036-rootdir-subvol/test.sh | 78 +++++++++++++++++---- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c index 3da3c8247d08..0cb524b8040a 100644 --- a/mkfs/rootdir.c +++ b/mkfs/rootdir.c @@ -719,7 +719,7 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, parent = rootdir_path_last(¤t_path); root = parent->root; - /* For non-directory inode, check if there is already any hard link. */ + /* Check if there is already a hard link record for this. */ if (have_hard_links) { struct hardlink_entry *found; found = find_hard_link(root, st); @@ -764,6 +764,7 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, error("failed to insert inode item %llu for '%s': %m", ino, full_path); return ret; } + ret = btrfs_add_link(g_trans, root, ino, parent->ino, full_path + ftwbuf->base, strlen(full_path) - ftwbuf->base, @@ -775,10 +776,7 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, return ret; } - /* - * Found a possible hard link, add it into the hard link rb tree for - * future detection. - */ + /* Record this new hard link. */ if (have_hard_links) { ret = add_hard_link(root, ino, st); if (ret < 0) { diff --git a/tests/mkfs-tests/036-rootdir-subvol/test.sh b/tests/mkfs-tests/036-rootdir-subvol/test.sh index 63ba928f348a..088004b1c227 100755 --- a/tests/mkfs-tests/036-rootdir-subvol/test.sh +++ b/tests/mkfs-tests/036-rootdir-subvol/test.sh @@ -11,23 +11,75 @@ prepare_test_dev tmp=$(_mktemp_dir mkfs-rootdir) -run_check touch "$tmp/foo" -run_check mkdir "$tmp/dir" -run_check mkdir "$tmp/dir/subvol" -run_check touch "$tmp/dir/subvol/bar" +basic() +{ + run_check touch "$tmp/foo" + run_check mkdir "$tmp/dir" + run_check mkdir "$tmp/dir/subvol" + run_check touch "$tmp/dir/subvol/bar" -run_check_mkfs_test_dev --rootdir "$tmp" --subvol dir/subvol -run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV" + run_check_mkfs_test_dev --rootdir "$tmp" --subvol dir/subvol + run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV" -run_check_mount_test_dev -run_check_stdout $SUDO_HELPER "$TOP/btrfs" subvolume list "$TEST_MNT" | \ + run_check_mount_test_dev + run_check_stdout $SUDO_HELPER "$TOP/btrfs" subvolume list "$TEST_MNT" | \ cut -d\ -f9 > "$tmp/output" -run_check_umount_test_dev + run_check_umount_test_dev -result=$(cat "$tmp/output") + result=$(cat "$tmp/output") -if [ "$result" != "dir/subvol" ]; then - _fail "dir/subvol not in subvolume list" -fi + if [ "$result" != "dir/subvol" ]; then + _fail "dir/subvol not in subvolume list" + fi + rm -rf -- "$tmp/foo" "$tmp/dir" +} +basic_hardlinks() +{ + run_check touch "$tmp/hl1" + run_check ln "$tmp/hl1" "$tmp/hl2" + run_check mkdir "$tmp/dir" + run_check ln "$tmp/hl1" "$tmp/dir/hl3" + + run_check_mkfs_test_dev --rootdir "$tmp" + run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV" + + run_check_mount_test_dev + nr_hardlink=$(run_check_stdout $SUDO_HELPER stat -c "%h" "$TEST_MNT/hl1") + + if [ $nr_hardlink -ne 3 ]; then + _fail "hard link number incorrect, has ${nr_hardlink} expect 3" + fi + run_check_umount_test_dev + rm -rf -- "$tmp/hl1" "$tmp/hl2" "$tmp/dir" +} + +split_by_subvolume_hardlinks() +{ + run_check touch "$tmp/hl1" + run_check ln "$tmp/hl1" "$tmp/hl2" + run_check mkdir "$tmp/subv" + run_check ln "$tmp/hl1" "$tmp/subv/hl3" + + run_check_mkfs_test_dev --rootdir "$tmp" --subvol subv + run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV" + + run_check_mount_test_dev + nr_hardlink=$(run_check_stdout $SUDO_HELPER stat -c "%h" "$TEST_MNT/hl1") + + if [ $nr_hardlink -ne 2 ]; then + _fail "hard link number incorrect for hl1, has ${nr_hardlink} expect 2" + fi + + nr_hardlink=$(run_check_stdout $SUDO_HELPER stat -c "%h" "$TEST_MNT/subv/hl3") + if [ $nr_hardlink -ne 1 ]; then + _fail "hard link number incorrect for subv/hl3, has ${nr_hardlink} expect 1" + fi + run_check_umount_test_dev + rm -rf -- "$tmp/hl1" "$tmp/hl2" "$tmp/dir" +} + +basic +basic_hardlinks +split_by_subvolume_hardlinks rm -rf -- "$tmp"