btrfs-progs: rescue-zero-log: Modify super block directly
diff mbox series

Message ID 20191026101127.36851-1-wqu@suse.com
State New
Headers show
Series
  • btrfs-progs: rescue-zero-log: Modify super block directly
Related show

Commit Message

Qu Wenruo Oct. 26, 2019, 10:11 a.m. UTC
For log zeroing, we only need to reset log_root and log_root_level to 0.

However current zero-log still goes full open_ctree() which can be
rejected easily by extent tree corruption.

So this patch will change the behavior to modifying super block
directly, avoid any possible rejection from open_ctree()

Reported-by: Christian Pernegger <pernegger@gmail.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 cmds/rescue.c | 48 ++++++++++++++++++++++++++++++++++++------------
 1 file changed, 36 insertions(+), 12 deletions(-)

Comments

David Sterba Nov. 1, 2019, 10:52 a.m. UTC | #1
On Sat, Oct 26, 2019 at 06:11:27PM +0800, Qu Wenruo wrote:
> +	/*
> +	 * Log tree only exists in the primary super block, so SBREAD_DEFAULT
> +	 * is enough.

For read it should be enough to read the default one, but do you mean
that 1st and 2nd copy don't have the log_root values set? They're
written from the same buffer so I'd expect the contents to be the same.

> +	ret = btrfs_read_dev_super(fd, sb, BTRFS_SUPER_INFO_OFFSET,
> +				   SBREAD_DEFAULT);
> +	if (ret < 0) {
> +		errno = -ret;
> +		error("failed to read super block on '%s': %m", devname);
> +		goto close_fd;
>  	}
>  
> -	sb = root->fs_info->super_copy;
>  	printf("Clearing log on %s, previous log_root %llu, level %u\n",
>  			devname,
>  			(unsigned long long)btrfs_super_log_root(sb),
>  			(unsigned)btrfs_super_log_root_level(sb));
> -	trans = btrfs_start_transaction(root, 1);
> -	BUG_ON(IS_ERR(trans));
>  	btrfs_set_super_log_root(sb, 0);
>  	btrfs_set_super_log_root_level(sb, 0);
> -	btrfs_commit_transaction(trans, root);
> -	close_ctree(root);
> +	btrfs_csum_data(btrfs_super_csum_type(sb), (u8 *)sb + BTRFS_CSUM_SIZE,
> +			result, BTRFS_SUPER_INFO_SIZE - BTRFS_CSUM_SIZE);
> +	memcpy(&sb->csum[0], result, BTRFS_CSUM_SIZE);
> +	ret = pwrite64(fd, sb, BTRFS_SUPER_INFO_SIZE, BTRFS_SUPER_INFO_OFFSET);

So this only writes on the one device that's passed to the command.
Previously it would update superblocks on all devices.
Qu Wenruo Nov. 1, 2019, 10:56 a.m. UTC | #2
On 2019/11/1 下午6:52, David Sterba wrote:
> On Sat, Oct 26, 2019 at 06:11:27PM +0800, Qu Wenruo wrote:
>> +	/*
>> +	 * Log tree only exists in the primary super block, so SBREAD_DEFAULT
>> +	 * is enough.
> 
> For read it should be enough to read the default one, but do you mean
> that 1st and 2nd copy don't have the log_root values set? They're
> written from the same buffer so I'd expect the contents to be the same.

Log tree update only happens for primary sb.

The kernel code has this:

btrfs_sync_log()
|- ret = write_all_supers(fs_info, 1)
   |- write_dev_supers(max_mirrors); # max_mirrors == 1
      |- for (i = 0; i < max_mirrors; i++)

> 
>> +	ret = btrfs_read_dev_super(fd, sb, BTRFS_SUPER_INFO_OFFSET,
>> +				   SBREAD_DEFAULT);
>> +	if (ret < 0) {
>> +		errno = -ret;
>> +		error("failed to read super block on '%s': %m", devname);
>> +		goto close_fd;
>>  	}
>>  
>> -	sb = root->fs_info->super_copy;
>>  	printf("Clearing log on %s, previous log_root %llu, level %u\n",
>>  			devname,
>>  			(unsigned long long)btrfs_super_log_root(sb),
>>  			(unsigned)btrfs_super_log_root_level(sb));
>> -	trans = btrfs_start_transaction(root, 1);
>> -	BUG_ON(IS_ERR(trans));
>>  	btrfs_set_super_log_root(sb, 0);
>>  	btrfs_set_super_log_root_level(sb, 0);
>> -	btrfs_commit_transaction(trans, root);
>> -	close_ctree(root);
>> +	btrfs_csum_data(btrfs_super_csum_type(sb), (u8 *)sb + BTRFS_CSUM_SIZE,
>> +			result, BTRFS_SUPER_INFO_SIZE - BTRFS_CSUM_SIZE);
>> +	memcpy(&sb->csum[0], result, BTRFS_CSUM_SIZE);
>> +	ret = pwrite64(fd, sb, BTRFS_SUPER_INFO_SIZE, BTRFS_SUPER_INFO_OFFSET);
> 
> So this only writes on the one device that's passed to the command.
> Previously it would update superblocks on all devices.

Oh, you got me.

That's indeed the case. I guess we need to do the same skip_bg behavior
just like kernel to handle multiple devices.

Thanks,
Qu
David Sterba Nov. 1, 2019, 11:08 a.m. UTC | #3
On Fri, Nov 01, 2019 at 10:56:36AM +0000, Qu WenRuo wrote:
> >> +	btrfs_csum_data(btrfs_super_csum_type(sb), (u8 *)sb + BTRFS_CSUM_SIZE,
> >> +			result, BTRFS_SUPER_INFO_SIZE - BTRFS_CSUM_SIZE);
> >> +	memcpy(&sb->csum[0], result, BTRFS_CSUM_SIZE);
> >> +	ret = pwrite64(fd, sb, BTRFS_SUPER_INFO_SIZE, BTRFS_SUPER_INFO_OFFSET);
> > 
> > So this only writes on the one device that's passed to the command.
> > Previously it would update superblocks on all devices.
> 
> Oh, you got me.
> 
> That's indeed the case. I guess we need to do the same skip_bg behavior
> just like kernel to handle multiple devices.

In progs we can add another mode for open_ctree to open only devices,
then iterate over their superblocks and update it in place.

Patch
diff mbox series

diff --git a/cmds/rescue.c b/cmds/rescue.c
index e8eab6808bc3..3e2dedf04fda 100644
--- a/cmds/rescue.c
+++ b/cmds/rescue.c
@@ -19,6 +19,9 @@ 
 #include "kerncompat.h"
 
 #include <getopt.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
 #include "ctree.h"
 #include "volumes.h"
 #include "transaction.h"
@@ -164,10 +167,11 @@  static const char * const cmd_rescue_zero_log_usage[] = {
 static int cmd_rescue_zero_log(const struct cmd_struct *cmd,
 			       int argc, char **argv)
 {
-	struct btrfs_root *root;
-	struct btrfs_trans_handle *trans;
-	struct btrfs_super_block *sb;
 	char *devname;
+	u8 buf[BTRFS_SUPER_INFO_SIZE];
+	u8 result[BTRFS_CSUM_SIZE];
+	struct btrfs_super_block *sb = (struct btrfs_super_block *)buf;
+	int fd;
 	int ret;
 
 	clean_args_no_options(cmd, argc, argv);
@@ -187,24 +191,44 @@  static int cmd_rescue_zero_log(const struct cmd_struct *cmd,
 		goto out;
 	}
 
-	root = open_ctree(devname, 0, OPEN_CTREE_WRITES | OPEN_CTREE_PARTIAL);
-	if (!root) {
-		error("could not open ctree");
-		return 1;
+	fd = open(devname, O_RDWR);
+	if (fd < 0) {
+		ret = -errno;
+		error("failed to open '%s': %m", devname);
+		goto out;
+	}
+	/*
+	 * Log tree only exists in the primary super block, so SBREAD_DEFAULT
+	 * is enough.
+	 */
+	ret = btrfs_read_dev_super(fd, sb, BTRFS_SUPER_INFO_OFFSET,
+				   SBREAD_DEFAULT);
+	if (ret < 0) {
+		errno = -ret;
+		error("failed to read super block on '%s': %m", devname);
+		goto close_fd;
 	}
 
-	sb = root->fs_info->super_copy;
 	printf("Clearing log on %s, previous log_root %llu, level %u\n",
 			devname,
 			(unsigned long long)btrfs_super_log_root(sb),
 			(unsigned)btrfs_super_log_root_level(sb));
-	trans = btrfs_start_transaction(root, 1);
-	BUG_ON(IS_ERR(trans));
 	btrfs_set_super_log_root(sb, 0);
 	btrfs_set_super_log_root_level(sb, 0);
-	btrfs_commit_transaction(trans, root);
-	close_ctree(root);
+	btrfs_csum_data(btrfs_super_csum_type(sb), (u8 *)sb + BTRFS_CSUM_SIZE,
+			result, BTRFS_SUPER_INFO_SIZE - BTRFS_CSUM_SIZE);
+	memcpy(&sb->csum[0], result, BTRFS_CSUM_SIZE);
+	ret = pwrite64(fd, sb, BTRFS_SUPER_INFO_SIZE, BTRFS_SUPER_INFO_OFFSET);
+	if (ret != BTRFS_SUPER_INFO_SIZE) {
+		ret = -EIO;
+		errno = -ret;
+		error("failed to write super block for '%s': %m", devname);
+	} else {
+		ret = 0;
+	}
 
+close_fd:
+	close(fd);
 out:
 	return !!ret;
 }