The following two patches add code that prevents btrfsck to run on mounted filesystems. The first patch makes check_mounted() work with multidevice filesystems. The second patch finally adds the check to btrfsck. Cheers, Andi -- 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
Andi Drebes
2009-Nov-21 14:38 UTC
[PATCH 1/2] btrfs-progs: multidevice support for check_mounted
Check_mount() should also work with multi device filesystems. This patch adds
checks that allow to detect if a file is a device file used by a mounted single
or multi device btrfs or if it is a regular file used by a loopback device that
is part of a mounted single or multi device btrfs.
The single device checks also work for non-btrfs filesystems. This might be
helpful to prevent users from running btrfs programs (e.g. mkfs.btrfs)
accidentally on a filesystem used somewhere else.
Signed-off-by: Andi Drebes <lists-receive@programmierforen.de>
---
diff --git a/kerncompat.h b/kerncompat.h
index e4c8ce0..46236cd 100644
--- a/kerncompat.h
+++ b/kerncompat.h
@@ -42,7 +42,11 @@
#define GFP_NOFS 0
#define __read_mostly
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#ifndef ULONG_MAX
#define ULONG_MAX (~0UL)
+#endif
+
#define BUG() abort()
#ifdef __CHECKER__
#define __force __attribute__((force))
diff --git a/utils.c b/utils.c
index 2f4c6e1..8114217 100644
--- a/utils.c
+++ b/utils.c
@@ -31,6 +31,10 @@
#include <fcntl.h>
#include <unistd.h>
#include <mntent.h>
+#include <linux/loop.h>
+#include <linux/major.h>
+#include <linux/kdev_t.h>
+#include <limits.h>
#include "kerncompat.h"
#include "radix-tree.h"
#include "ctree.h"
@@ -586,55 +590,224 @@ error:
return ret;
}
+/* checks if a device is a loop device */
+int is_loop_device (const char* device) {
+ struct stat statbuf;
+
+ if(stat(device, &statbuf) < 0)
+ return -errno;
+
+ return (S_ISBLK(statbuf.st_mode) &&
+ MAJOR(statbuf.st_rdev) == LOOP_MAJOR);
+}
+
+
+/* Takes a loop device path (e.g. /dev/loop0) and returns
+ * the associated file (e.g. /images/my_btrfs.img) */
+int resolve_loop_device(const char* loop_dev, char* loop_file, int max_len)
+{
+ int loop_fd;
+ int ret_ioctl;
+ struct loop_info loopinfo;
+
+ if ((loop_fd = open(loop_dev, O_RDONLY)) < 0)
+ return -errno;
+
+ ret_ioctl = ioctl(loop_fd, LOOP_GET_STATUS, &loopinfo);
+ close(loop_fd);
+
+ if (ret_ioctl == 0)
+ strncpy(loop_file, loopinfo.lo_name, max_len);
+ else
+ return -errno;
+
+ return 0;
+}
+
+/* Checks whether a and b are identical or device
+ * files associated with the same block device
+ */
+int is_same_blk_file(const char* a, const char* b)
+{
+ struct stat st_buf_a, st_buf_b;
+ char real_a[PATH_MAX];
+ char real_b[PATH_MAX];
+
+ if(!realpath(a, real_a) ||
+ !realpath(b, real_b))
+ {
+ return -errno;
+ }
+
+ /* Identical path? */
+ if(strcmp(real_a, real_b) == 0)
+ return 1;
+
+ if(stat(a, &st_buf_a) < 0 ||
+ stat(b, &st_buf_b) < 0)
+ {
+ return -errno;
+ }
+
+ /* Same blockdevice? */
+ if(S_ISBLK(st_buf_a.st_mode) &&
+ S_ISBLK(st_buf_b.st_mode) &&
+ st_buf_a.st_rdev == st_buf_b.st_rdev)
+ {
+ return 1;
+ }
+
+ /* Hardlink? */
+ if (st_buf_a.st_dev == st_buf_b.st_dev &&
+ st_buf_a.st_ino == st_buf_b.st_ino)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* checks if a and b are identical or device
+ * files associated with the same block device or
+ * if one file is a loop device that uses the other
+ * file.
+ */
+int is_same_loop_file(const char* a, const char* b)
+{
+ char res_a[PATH_MAX];
+ char res_b[PATH_MAX];
+ const char* final_a;
+ const char* final_b;
+ int ret;
+
+ /* Resolve a if it is a loop device */
+ if((ret = is_loop_device(a)) < 0) {
+ return ret;
+ } else if(ret) {
+ if((ret = resolve_loop_device(a, res_a, sizeof(res_a))) < 0)
+ return ret;
+
+ final_a = res_a;
+ } else {
+ final_a = a;
+ }
+
+ /* Resolve b if it is a loop device */
+ if((ret = is_loop_device(b)) < 0) {
+ return ret;
+ } else if(ret) {
+ if((ret = resolve_loop_device(b, res_b, sizeof(res_b))) < 0)
+ return ret;
+
+ final_b = res_b;
+ } else {
+ final_b = b;
+ }
+
+ return is_same_blk_file(final_a, final_b);
+}
+
+/* Checks if an mntentry represents a pseudo FS */
+int is_pseudo_fs(const struct mntent* mnt)
+{
+ struct stat st_buf;
+
+ if(stat(mnt->mnt_fsname, &st_buf) < 0) {
+ if(errno == ENOENT)
+ return 1;
+ else
+ return -errno;
+ }
+
+ return 0;
+}
+
+/* Checks if a file is used (directly or indirectly via a loop device)
+ * by a device in fs_devices
+ */
+int blk_file_in_dev_list(struct btrfs_fs_devices* fs_devices, const char* file)
+{
+ int ret;
+ struct list_head *head;
+ struct list_head *cur;
+ struct btrfs_device *device;
+
+ head = &fs_devices->devices;
+ list_for_each(cur, head) {
+ device = list_entry(cur, struct btrfs_device, dev_list);
+
+ if((ret = is_same_loop_file(device->name, file)))
+ return ret;
+ }
+
+ return 0;
+}
+
/*
* returns 1 if the device was mounted, < 0 on error or 0 if everything
- * is safe to continue. TODO, this should also scan multi-device filesystems
+ * is safe to continue.
*/
-int check_mounted(char *file)
+int check_mounted(const char* file)
{
- struct mntent *mnt;
- struct stat st_buf;
- dev_t file_dev = 0;
- dev_t file_rdev = 0;
- ino_t file_ino = 0;
+ int ret;
+ int fd;
+ u64 total_devs = 1;
+ int is_btrfs;
+ struct btrfs_fs_devices* fs_devices_mnt = NULL;
FILE *f;
- int ret = 0;
+ struct mntent *mnt;
- if ((f = setmntent ("/proc/mounts", "r")) == NULL)
+ fd = open(file, O_RDONLY);
+ if (fd < 0) {
+ fprintf (stderr, "check_mounted(): Could not open %s\n", file);
return -errno;
+ }
- if (stat(file, &st_buf) < 0) {
- return -errno;
- } else {
- if (S_ISBLK(st_buf.st_mode)) {
- file_rdev = st_buf.st_rdev;
- } else {
- file_dev = st_buf.st_dev;
- file_ino = st_buf.st_ino;
- }
+ /* scan the initial device */
+ ret = btrfs_scan_one_device(fd, file, &fs_devices_mnt,
+ &total_devs, BTRFS_SUPER_INFO_OFFSET);
+ is_btrfs = (ret >= 0);
+ close(fd);
+
+ /* scan other devices */
+ if (is_btrfs && total_devs > 1) {
+ if((ret = btrfs_scan_for_fsid(fs_devices_mnt, total_devs, 1)))
+ return ret;
}
+ /* iterate over the list of currently mountes filesystems */
+ if ((f = setmntent ("/proc/mounts", "r")) == NULL)
+ return -errno;
+
while ((mnt = getmntent (f)) != NULL) {
- if (strcmp(file, mnt->mnt_fsname) == 0)
- break;
+ /* Only check btrfs filesystems */
+ if(is_btrfs && strcmp(mnt->mnt_type, "btrfs") != 0)
+ continue;
- if (stat(mnt->mnt_fsname, &st_buf) == 0) {
- if (S_ISBLK(st_buf.st_mode)) {
- if (file_rdev && (file_rdev == st_buf.st_rdev))
- break;
- } else if (file_dev && ((file_dev == st_buf.st_dev) &&
- (file_ino == st_buf.st_ino))) {
- break;
- }
- }
- }
+ /* ignore pseudo filesystems */
+ if(!is_btrfs && (ret = is_pseudo_fs(mnt)) < 0)
+ goto out_mntloop_err;
+ else if(ret)
+ continue;
- if (mnt) {
- /* found an entry in mnt table */
- ret = 1;
+ /* perform the check */
+ if(is_btrfs)
+ ret = blk_file_in_dev_list(fs_devices_mnt, mnt->mnt_fsname);
+ else
+ ret = is_same_loop_file(file, mnt->mnt_fsname);
+
+ if(ret < 0)
+ goto out_mntloop_err;
+ else if(ret)
+ break;
}
+ /* Did we find an entry in mnt table? */
+ ret = (mnt != NULL);
+
+out_mntloop_err:
endmntent (f);
+
return ret;
}
diff --git a/utils.h b/utils.h
index 7ff542b..9dce5b0 100644
--- a/utils.h
+++ b/utils.h
@@ -36,7 +36,7 @@ int btrfs_scan_for_fsid(struct btrfs_fs_devices *fs_devices,
u64 total_devs,
int run_ioctls);
void btrfs_register_one_device(char *fname);
int btrfs_scan_one_dir(char *dirname, int run_ioctl);
-int check_mounted(char *devicename);
+int check_mounted(const char *devicename);
int btrfs_device_already_in_root(struct btrfs_root *root, int fd,
int super_offset);
char *pretty_sizes(u64 size);
--
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
Andi Drebes
2009-Nov-21 14:39 UTC
[PATCH 2/2] btrfs-progs: prevent btrfsck to run on mounted filesystems
As recently discussed on the list, btrfsck should only be run on unmounted
filesystems. This patch adds a short check for the mount status at the beginning
of btrfsck. If the FS is mounted, the program aborts showing an error message.
Signed-off-by: Andi Drebes <lists-receive@programmierforen.de>
---
diff --git a/btrfsck.c b/btrfsck.c
index 73f1836..6f2a0d0 100644
--- a/btrfsck.c
+++ b/btrfsck.c
@@ -28,6 +28,7 @@
#include "transaction.h"
#include "list.h"
#include "version.h"
+#include "utils.h"
static u64 bytes_used = 0;
static u64 total_csum_bytes = 0;
@@ -2821,6 +2822,15 @@ int main(int ac, char **av)
radix_tree_init();
cache_tree_init(&root_cache);
+
+ if((ret = check_mounted(av[1])) < 0) {
+ fprintf(stderr, "Could not check mount status: %s\n",
strerror(ret));
+ return ret;
+ } else if(ret) {
+ fprintf(stderr, "%s is currently mounted. Aborting.\n", av[1]);
+ return -EBUSY;
+ }
+
root = open_ctree(av[1], 0, 0);
if (root == NULL)
--
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
Karel Zak
2009-Nov-21 20:21 UTC
Re: [PATCH 1/2] btrfs-progs: multidevice support for check_mounted
On Sat, Nov 21, 2009 at 03:38:38PM +0100, Andi Drebes wrote:> + > +/* Checks if an mntentry represents a pseudo FS */ > +int is_pseudo_fs(const struct mntent* mnt) > +{ > + struct stat st_buf; > + > + if(stat(mnt->mnt_fsname, &st_buf) < 0) { > + if(errno == ENOENT) > + return 1; > + else > + return -errno; > + } > + > + return 0; > +}This is bad idea. The mnt_fsname field could be an arbitrary string include valid paths. # grep sysfs /proc/mounts /sys /sys sysfs rw,relatime 0 0 Karel -- Karel Zak <kzak@redhat.com> -- 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
Andi Drebes
2009-Nov-22 13:33 UTC
Re: [PATCH 1/2] btrfs-progs: multidevice support for check_mounted
Hi! Thanks for the reply!> > + > > +/* Checks if an mntentry represents a pseudo FS */ > > +int is_pseudo_fs(const struct mntent* mnt) > > +{ > > + struct stat st_buf; > > + > > + if(stat(mnt->mnt_fsname, &st_buf) < 0) { > > + if(errno == ENOENT) > > + return 1; > > + else > > + return -errno; > > + } > > + > > + return 0; > > +} > > This is bad idea. The mnt_fsname field could be an arbitrary string > include valid paths. > > # grep sysfs /proc/mounts > /sys /sys sysfs rw,relatime 0 0Yes, indeed. Although this doesn''t really cause problems (because if the string is a valid path, we would just do one check too much), the name of the function doesn''t really correspond to what it does. In the new patch below, is_pseudo_fs() is replaced by is_existing_blk_or_reg_file(). We ignore entries associated with an invalid path or paths that don''t point to a regular or block file. However, if a path used in a pseudo-filesystem entry points to the file that is being checked, check_mounted() returns 1. In my eyes, this is extremely unlikely. Here''s the new patch: Signed-off-by: Andi Drebes <lists-receive@programmierforen.de> --- diff --git a/kerncompat.h b/kerncompat.h index e4c8ce0..46236cd 100644 --- a/kerncompat.h +++ b/kerncompat.h @@ -42,7 +42,11 @@ #define GFP_NOFS 0 #define __read_mostly #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#ifndef ULONG_MAX #define ULONG_MAX (~0UL) +#endif + #define BUG() abort() #ifdef __CHECKER__ #define __force __attribute__((force)) diff --git a/utils.c b/utils.c index 2f4c6e1..fd894f3 100644 --- a/utils.c +++ b/utils.c @@ -31,6 +31,10 @@ #include <fcntl.h> #include <unistd.h> #include <mntent.h> +#include <linux/loop.h> +#include <linux/major.h> +#include <linux/kdev_t.h> +#include <limits.h> #include "kerncompat.h" #include "radix-tree.h" #include "ctree.h" @@ -586,55 +590,224 @@ error: return ret; } +/* checks if a device is a loop device */ +int is_loop_device (const char* device) { + struct stat statbuf; + + if(stat(device, &statbuf) < 0) + return -errno; + + return (S_ISBLK(statbuf.st_mode) && + MAJOR(statbuf.st_rdev) == LOOP_MAJOR); +} + + +/* Takes a loop device path (e.g. /dev/loop0) and returns + * the associated file (e.g. /images/my_btrfs.img) */ +int resolve_loop_device(const char* loop_dev, char* loop_file, int max_len) +{ + int loop_fd; + int ret_ioctl; + struct loop_info loopinfo; + + if ((loop_fd = open(loop_dev, O_RDONLY)) < 0) + return -errno; + + ret_ioctl = ioctl(loop_fd, LOOP_GET_STATUS, &loopinfo); + close(loop_fd); + + if (ret_ioctl == 0) + strncpy(loop_file, loopinfo.lo_name, max_len); + else + return -errno; + + return 0; +} + +/* Checks whether a and b are identical or device + * files associated with the same block device + */ +int is_same_blk_file(const char* a, const char* b) +{ + struct stat st_buf_a, st_buf_b; + char real_a[PATH_MAX]; + char real_b[PATH_MAX]; + + if(!realpath(a, real_a) || + !realpath(b, real_b)) + { + return -errno; + } + + /* Identical path? */ + if(strcmp(real_a, real_b) == 0) + return 1; + + if(stat(a, &st_buf_a) < 0 || + stat(b, &st_buf_b) < 0) + { + return -errno; + } + + /* Same blockdevice? */ + if(S_ISBLK(st_buf_a.st_mode) && + S_ISBLK(st_buf_b.st_mode) && + st_buf_a.st_rdev == st_buf_b.st_rdev) + { + return 1; + } + + /* Hardlink? */ + if (st_buf_a.st_dev == st_buf_b.st_dev && + st_buf_a.st_ino == st_buf_b.st_ino) + { + return 1; + } + + return 0; +} + +/* checks if a and b are identical or device + * files associated with the same block device or + * if one file is a loop device that uses the other + * file. + */ +int is_same_loop_file(const char* a, const char* b) +{ + char res_a[PATH_MAX]; + char res_b[PATH_MAX]; + const char* final_a; + const char* final_b; + int ret; + + /* Resolve a if it is a loop device */ + if((ret = is_loop_device(a)) < 0) { + return ret; + } else if(ret) { + if((ret = resolve_loop_device(a, res_a, sizeof(res_a))) < 0) + return ret; + + final_a = res_a; + } else { + final_a = a; + } + + /* Resolve b if it is a loop device */ + if((ret = is_loop_device(b)) < 0) { + return ret; + } else if(ret) { + if((ret = resolve_loop_device(b, res_b, sizeof(res_b))) < 0) + return ret; + + final_b = res_b; + } else { + final_b = b; + } + + return is_same_blk_file(final_a, final_b); +} + +/* Checks if a file exists and is a block or regular file*/ +int is_existing_blk_or_reg_file(const char* filename) +{ + struct stat st_buf; + + if(stat(filename, &st_buf) < 0) { + if(errno == ENOENT) + return 0; + else + return -errno; + } + + return (S_ISBLK(st_buf.st_mode) || S_ISREG(st_buf.st_mode)); +} + +/* Checks if a file is used (directly or indirectly via a loop device) + * by a device in fs_devices + */ +int blk_file_in_dev_list(struct btrfs_fs_devices* fs_devices, const char* file) +{ + int ret; + struct list_head *head; + struct list_head *cur; + struct btrfs_device *device; + + head = &fs_devices->devices; + list_for_each(cur, head) { + device = list_entry(cur, struct btrfs_device, dev_list); + + if((ret = is_same_loop_file(device->name, file))) + return ret; + } + + return 0; +} + /* * returns 1 if the device was mounted, < 0 on error or 0 if everything - * is safe to continue. TODO, this should also scan multi-device filesystems + * is safe to continue. */ -int check_mounted(char *file) +int check_mounted(const char* file) { - struct mntent *mnt; - struct stat st_buf; - dev_t file_dev = 0; - dev_t file_rdev = 0; - ino_t file_ino = 0; + int ret; + int fd; + u64 total_devs = 1; + int is_btrfs; + struct btrfs_fs_devices* fs_devices_mnt = NULL; FILE *f; - int ret = 0; + struct mntent *mnt; - if ((f = setmntent ("/proc/mounts", "r")) == NULL) + fd = open(file, O_RDONLY); + if (fd < 0) { + fprintf (stderr, "check_mounted(): Could not open %s\n", file); return -errno; + } - if (stat(file, &st_buf) < 0) { - return -errno; - } else { - if (S_ISBLK(st_buf.st_mode)) { - file_rdev = st_buf.st_rdev; - } else { - file_dev = st_buf.st_dev; - file_ino = st_buf.st_ino; - } + /* scan the initial device */ + ret = btrfs_scan_one_device(fd, file, &fs_devices_mnt, + &total_devs, BTRFS_SUPER_INFO_OFFSET); + is_btrfs = (ret >= 0); + close(fd); + + /* scan other devices */ + if (is_btrfs && total_devs > 1) { + if((ret = btrfs_scan_for_fsid(fs_devices_mnt, total_devs, 1))) + return ret; } + /* iterate over the list of currently mountes filesystems */ + if ((f = setmntent ("/proc/mounts", "r")) == NULL) + return -errno; + while ((mnt = getmntent (f)) != NULL) { - if (strcmp(file, mnt->mnt_fsname) == 0) - break; + if(is_btrfs) { + if(strcmp(mnt->mnt_type, "btrfs") != 0) + continue; - if (stat(mnt->mnt_fsname, &st_buf) == 0) { - if (S_ISBLK(st_buf.st_mode)) { - if (file_rdev && (file_rdev == st_buf.st_rdev)) - break; - } else if (file_dev && ((file_dev == st_buf.st_dev) && - (file_ino == st_buf.st_ino))) { - break; - } + ret = blk_file_in_dev_list(fs_devices_mnt, mnt->mnt_fsname); + } else { + /* ignore entries in the mount table that are not + associated with a file*/ + if((ret = is_existing_blk_or_reg_file(mnt->mnt_fsname)) < 0) + goto out_mntloop_err; + else if(!ret) + continue; + + ret = is_same_loop_file(file, mnt->mnt_fsname); } - } - if (mnt) { - /* found an entry in mnt table */ - ret = 1; + if(ret < 0) + goto out_mntloop_err; + else if(ret) + break; } + /* Did we find an entry in mnt table? */ + ret = (mnt != NULL); + +out_mntloop_err: endmntent (f); + return ret; } diff --git a/utils.h b/utils.h index 7ff542b..9dce5b0 100644 --- a/utils.h +++ b/utils.h @@ -36,7 +36,7 @@ int btrfs_scan_for_fsid(struct btrfs_fs_devices *fs_devices, u64 total_devs, int run_ioctls); void btrfs_register_one_device(char *fname); int btrfs_scan_one_dir(char *dirname, int run_ioctl); -int check_mounted(char *devicename); +int check_mounted(const char *devicename); int btrfs_device_already_in_root(struct btrfs_root *root, int fd, int super_offset); char *pretty_sizes(u64 size); -- 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
Karel Zak
2009-Nov-23 09:01 UTC
Re: [PATCH 1/2] btrfs-progs: multidevice support for check_mounted
On Sun, Nov 22, 2009 at 02:33:13PM +0100, Andi Drebes wrote:> In the new patch below, is_pseudo_fs() is replaced by > is_existing_blk_or_reg_file(). We ignore entries associated with an > invalid path or paths that don''t point to a regular or block file. > However, if a path used in a pseudo-filesystem entry points to the > file that is being checked, check_mounted() returns 1. In my eyes, > this is extremely unlikely....> +/* Checks if a file exists and is a block or regular file*/ > +int is_existing_blk_or_reg_file(const char* filename) > +{ > + struct stat st_buf; > + > + if(stat(filename, &st_buf) < 0) { > + if(errno == ENOENT) > + return 0; > + else > + return -errno; > + } > + > + return (S_ISBLK(st_buf.st_mode) || S_ISREG(st_buf.st_mode)); > +}Yes, this looks better. Karel -- Karel Zak <kzak@redhat.com> -- 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