diff mbox

[4/4,V2] btrfs-progs: rework get_fs_info to remove side effects

Message ID 513EAC64.7020706@redhat.com (mailing list archive)
State Accepted, archived
Headers show

Commit Message

Eric Sandeen March 12, 2013, 4:17 a.m. UTC
get_fs_info() has been silently switching from a device to a mounted
path as needed; the caller's filehandle was unexpectedly closed &
reopened outside the caller's scope.  Not so great.

The callers do want "fdmnt" to be the filehandle for the mount point
in all cases, though - the various ioctls act on this (not on an fd
for the device).  But switching it in the local scope of get_fs_info
is incorrect; it just so happens that *usually* the fd number is
unchanged.

So - use the new helpers to detect when an argument is a block
device, and open the the mounted path more obviously / explicitly
for ioctl use, storing the filehandle in fdmnt.

Then, in get_fs_info, ignore the fd completely, and use the path on
the argument to determine if the caller wanted to act on just that
device, or on all devices for the filesystem.

Affects those commands which are documented to accept either
a block device or a path:

* btrfs device stats
* btrfs replace start
* btrfs scrub start
* btrfs scrub status

Signed-off-by: Eric Sandeen <sandeen@redhat.com>
---

V2: don't call BTRFS_IOC_FS_INFO in the single device case
after we change path/fd to be for the fs mount point.

In the single device case we manually filled in fi_args;
calling this ioctl after switching fd/path to the mount point
overwrites that setup.



--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

David Sterba March 12, 2013, 4:40 p.m. UTC | #1
On Mon, Mar 11, 2013 at 11:17:40PM -0500, Eric Sandeen wrote:
> * btrfs scrub start
> * btrfs scrub status

I did a quick test here:

$ mkfs.btrfs -d raid10 -m raid10 /dev/sda[5678]
$ mount /dev/sda5 /mnt
$ <fill with files> &
$ btrfs scrub start /dev/sda5
(ok)

$ btrfs scrub status /dev/sda5
(ok)

$ btrfs scrub status /mnt
(ok)

$ btrfs scrub cancel /mnt
^C^C^C^C^C^C^C^C<hang>
(exitted after a few minutes)

Name:   btrfs   State:  D (disk sleep)  Pid:    24214
[<ffffffffa008f625>] btrfs_scrub_cancel+0xc5/0x130 [btrfs]
[<ffffffffa006ab40>] btrfs_ioctl+0x13b0/0x1d90 [btrfs]
[<ffffffff81172ee6>] do_vfs_ioctl+0x96/0x560
[<ffffffff81173407>] sys_ioctl+0x57/0x90
[<ffffffff819698d9>] system_call_fastpath+0x16/0x1b
[<ffffffffffffffff>] 0xffffffffffffffff

(gdb) l *(btrfs_scrub_cancel+0xc5)
0x8f625 is in btrfs_scrub_cancel (fs/btrfs/scrub.c:2983).
2978            }
2979
2980            atomic_inc(&fs_info->scrub_cancel_req);
2981            while (atomic_read(&fs_info->scrubs_running)) {
2982                    mutex_unlock(&fs_info->scrub_lock);
2983                    wait_event(fs_info->scrub_pause_wait,
^^^^
2984                               atomic_read(&fs_info->scrubs_running) == 0);
2985                    mutex_lock(&fs_info->scrub_lock);
2986            }
2987            atomic_dec(&fs_info->scrub_cancel_req);

I don't know yet if this happens without this changes, will check next.

david
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Sterba March 12, 2013, 5:30 p.m. UTC | #2
On Tue, Mar 12, 2013 at 05:40:27PM +0100, David Sterba wrote:
> I don't know yet if this happens without this changes, will check next.

Happens as well, not a bug in your patches.

david
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/cmds-device.c b/cmds-device.c
index 58df6da..41e79d3 100644
--- a/cmds-device.c
+++ b/cmds-device.c
@@ -321,13 +321,14 @@  static int cmd_dev_stats(int argc, char **argv)
 
 	path = argv[optind];
 
-	fdmnt = open_file_or_dir(path);
+	fdmnt = open_path_or_dev_mnt(path);
+
 	if (fdmnt < 0) {
 		fprintf(stderr, "ERROR: can't access '%s'\n", path);
 		return 12;
 	}
 
-	ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
+	ret = get_fs_info(path, &fi_args, &di_args);
 	if (ret) {
 		fprintf(stderr, "ERROR: getting dev info for devstats failed: "
 				"%s\n", strerror(-ret));
diff --git a/cmds-replace.c b/cmds-replace.c
index 10030f6..6397bb5 100644
--- a/cmds-replace.c
+++ b/cmds-replace.c
@@ -168,7 +168,9 @@  static int cmd_start_replace(int argc, char **argv)
 	if (check_argc_exact(argc - optind, 3))
 		usage(cmd_start_replace_usage);
 	path = argv[optind + 2];
-	fdmnt = open_file_or_dir(path);
+
+	fdmnt = open_path_or_dev_mnt(path);
+
 	if (fdmnt < 0) {
 		fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
 			path, strerror(errno));
@@ -215,7 +217,7 @@  static int cmd_start_replace(int argc, char **argv)
 		}
 		start_args.start.srcdevid = (__u64)atoi(srcdev);
 
-		ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
+		ret = get_fs_info(path, &fi_args, &di_args);
 		if (ret) {
 			fprintf(stderr, "ERROR: getting dev info for devstats failed: "
 					"%s\n", strerror(-ret));
diff --git a/cmds-scrub.c b/cmds-scrub.c
index e5fccc7..52264f1 100644
--- a/cmds-scrub.c
+++ b/cmds-scrub.c
@@ -1101,13 +1101,14 @@  static int scrub_start(int argc, char **argv, int resume)
 
 	path = argv[optind];
 
-	fdmnt = open_file_or_dir(path);
+	fdmnt = open_path_or_dev_mnt(path);
+
 	if (fdmnt < 0) {
 		ERR(!do_quiet, "ERROR: can't access '%s'\n", path);
 		return 12;
 	}
 
-	ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
+	ret = get_fs_info(path, &fi_args, &di_args);
 	if (ret) {
 		ERR(!do_quiet, "ERROR: getting dev info for scrub failed: "
 		    "%s\n", strerror(-ret));
@@ -1558,13 +1559,14 @@  static int cmd_scrub_status(int argc, char **argv)
 
 	path = argv[optind];
 
-	fdmnt = open_file_or_dir(path);
+	fdmnt = open_path_or_dev_mnt(path);
+
 	if (fdmnt < 0) {
 		fprintf(stderr, "ERROR: can't access to '%s'\n", path);
 		return 12;
 	}
 
-	ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
+	ret = get_fs_info(path, &fi_args, &di_args);
 	if (ret) {
 		fprintf(stderr, "ERROR: getting dev info for scrub failed: "
 				"%s\n", strerror(-ret));
diff --git a/utils.c b/utils.c
index 4bf457f..c756e23 100644
--- a/utils.c
+++ b/utils.c
@@ -717,7 +717,7 @@  int open_path_or_dev_mnt(const char *path)
 			errno = EINVAL;
 			return -1;
 		}
-		fdmnt = open(mp, O_RDWR);
+		fdmnt = open_file_or_dir(mp);
 	} else
 		fdmnt = open_file_or_dir(path);
 
@@ -1544,9 +1544,20 @@  int get_device_info(int fd, u64 devid,
 	return ret ? -errno : 0;
 }
 
-int get_fs_info(int fd, char *path, struct btrfs_ioctl_fs_info_args *fi_args,
+/*
+ * For a given path, fill in the ioctl fs_ and info_ args.
+ * If the path is a btrfs mountpoint, fill info for all devices.
+ * If the path is a btrfs device, fill in only that device.
+ *
+ * The path provided must be either on a mounted btrfs fs,
+ * or be a mounted btrfs device.
+ *
+ * Returns 0 on success, or a negative errno.
+ */
+int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
 		struct btrfs_ioctl_dev_info_args **di_ret)
 {
+	int fd = -1;
 	int ret = 0;
 	int ndevs = 0;
 	int i = 1;
@@ -1556,33 +1567,56 @@  int get_fs_info(int fd, char *path, struct btrfs_ioctl_fs_info_args *fi_args,
 
 	memset(fi_args, 0, sizeof(*fi_args));
 
-	ret = ioctl(fd, BTRFS_IOC_FS_INFO, fi_args);
-	if (ret && (errno == EINVAL || errno == ENOTTY)) {
-		/* path is not a mounted btrfs. Try if it's a device */
+	if (is_block_device(path)) {
+		/* Ensure it's mounted, then set path to the mountpoint */
+		fd = open(path, O_RDONLY);
+		if (fd < 0) {
+			ret = -errno;
+			fprintf(stderr, "Couldn't open %s: %s\n",
+				path, strerror(errno));
+			goto out;
+		}
 		ret = check_mounted_where(fd, path, mp, sizeof(mp),
 					  &fs_devices_mnt);
-		if (!ret)
-			return -EINVAL;
+		if (!ret) {
+			ret = -EINVAL;
+			goto out;
+		}
 		if (ret < 0)
-			return ret;
+			goto out;
+		path = mp;
+		/* Only fill in this one device */
 		fi_args->num_devices = 1;
 		fi_args->max_id = fs_devices_mnt->latest_devid;
 		i = fs_devices_mnt->latest_devid;
 		memcpy(fi_args->fsid, fs_devices_mnt->fsid, BTRFS_FSID_SIZE);
 		close(fd);
-		fd = open_file_or_dir(mp);
-		if (fd < 0)
-			return -errno;
-	} else if (ret) {
-		return -errno;
+	}
+
+	/* at this point path must not be for a block device */
+	fd = open_file_or_dir(path);
+	if (fd < 0) {
+		ret = -errno;
+		goto out;
+	}
+
+	/* fill in fi_args if not just a single device */
+	if (fi_args->num_devices != 1) {
+		ret = ioctl(fd, BTRFS_IOC_FS_INFO, fi_args);
+		if (ret < 0) {
+			ret = -errno;
+			goto out;
+		}
 	}
 
 	if (!fi_args->num_devices)
-		return 0;
+		goto out;
 
 	di_args = *di_ret = malloc(fi_args->num_devices * sizeof(*di_args));
-	if (!di_args)
-		return -errno;
+	if (!di_args) {
+		ret = -errno;
+		goto out;
+	}
 
 	for (; i <= fi_args->max_id; ++i) {
 		BUG_ON(ndevs >= fi_args->num_devices);
@@ -1590,13 +1624,16 @@  int get_fs_info(int fd, char *path, struct btrfs_ioctl_fs_info_args *fi_args,
 		if (ret == -ENODEV)
 			continue;
 		if (ret)
-			return ret;
+			goto out;
 		ndevs++;
 	}
 
 	BUG_ON(ndevs == 0);
-
-	return 0;
+	ret = 0;
+out:
+	if (fd != -1)
+		close(fd);
+	return ret;
 }
 
 #define isoctal(c)	(((c) & ~7) == '0')
diff --git a/utils.h b/utils.h
index 8e0252b..885b9c5 100644
--- a/utils.h
+++ b/utils.h
@@ -50,7 +50,7 @@  u64 parse_size(char *s);
 int open_file_or_dir(const char *fname);
 int get_device_info(int fd, u64 devid,
 		    struct btrfs_ioctl_dev_info_args *di_args);
-int get_fs_info(int fd, char *path, struct btrfs_ioctl_fs_info_args *fi_args,
+int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
 		struct btrfs_ioctl_dev_info_args **di_ret);
 int get_label(const char *btrfs_dev);
 int set_label(const char *btrfs_dev, const char *label);