OSDN Git Service

btrfs: more graceful errors/warnings on 32bit systems when reaching limits
authorQu Wenruo <wqu@suse.com>
Thu, 25 Feb 2021 01:18:14 +0000 (09:18 +0800)
committerDavid Sterba <dsterba@suse.com>
Tue, 20 Apr 2021 17:56:50 +0000 (19:56 +0200)
Btrfs uses internally mapped u64 address space for all its metadata.
Due to the page cache limit on 32bit systems, btrfs can't access
metadata at or beyond (ULONG_MAX + 1) << PAGE_SHIFT. See
how MAX_LFS_FILESIZE and page::index are defined.  This is 16T for 4K
page size while 256T for 64K page size.

Users can have a filesystem which doesn't have metadata beyond the
boundary at mount time, but later balance can cause it to create
metadata beyond the boundary.

And modification to MM layer is unrealistic just for such minor use
case. We can't do more than to prevent mounting such filesystem or warn
early when the numbers are still within the limits.

To address such problem, this patch will introduce the following checks:

- Mount time rejection
  This will reject any fs which has metadata chunk at or beyond the
  boundary.

- Mount time early warning
  If there is any metadata chunk beyond 5/8th of the boundary, we do an
  early warning and hope the end user will see it.

- Runtime extent buffer rejection
  If we're going to allocate an extent buffer at or beyond the boundary,
  reject such request with EOVERFLOW.
  This is definitely going to cause problems like transaction abort, but
  we have no better ways.

- Runtime extent buffer early warning
  If an extent buffer beyond 5/8th of the max file size is allocated, do
  an early warning.

Above error/warning message will only be printed once for each fs to
reduce dmesg flood.

If the mount is rejected, the filesystem will be mountable only on a
64bit host.

Link: https://lore.kernel.org/linux-btrfs/1783f16d-7a28-80e6-4c32-fdf19b705ed0@gmx.com/
Reported-by: Erik Jensen <erikjensen@rkjnsn.net>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/ctree.h
fs/btrfs/extent_io.c
fs/btrfs/super.c
fs/btrfs/volumes.c

index 2c858d5..45899ab 100644 (file)
@@ -585,6 +585,12 @@ enum {
 
        /* Indicate whether there are any tree modification log users */
        BTRFS_FS_TREE_MOD_LOG_USERS,
+
+#if BITS_PER_LONG == 32
+       /* Indicate if we have error/warn message printed on 32bit systems */
+       BTRFS_FS_32BIT_ERROR,
+       BTRFS_FS_32BIT_WARN,
+#endif
 };
 
 /*
@@ -3412,6 +3418,19 @@ static inline void assertfail(const char *expr, const char* file, int line) { }
 #define ASSERT(expr)   (void)(expr)
 #endif
 
+#if BITS_PER_LONG == 32
+#define BTRFS_32BIT_MAX_FILE_SIZE (((u64)ULONG_MAX + 1) << PAGE_SHIFT)
+/*
+ * The warning threshold is 5/8th of the MAX_LFS_FILESIZE that limits the logical
+ * addresses of extents.
+ *
+ * For 4K page size it's about 10T, for 64K it's 160T.
+ */
+#define BTRFS_32BIT_EARLY_WARN_THRESHOLD (BTRFS_32BIT_MAX_FILE_SIZE * 5 / 8)
+void btrfs_warn_32bit_limit(struct btrfs_fs_info *fs_info);
+void btrfs_err_32bit_limit(struct btrfs_fs_info *fs_info);
+#endif
+
 /*
  * Get the correct offset inside the page of extent buffer.
  *
index b1599d8..f2d1bb2 100644 (file)
@@ -5821,6 +5821,17 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info,
                return ERR_PTR(-EINVAL);
        }
 
+#if BITS_PER_LONG == 32
+       if (start >= MAX_LFS_FILESIZE) {
+               btrfs_err_rl(fs_info,
+               "extent buffer %llu is beyond 32bit page cache limit", start);
+               btrfs_err_32bit_limit(fs_info);
+               return ERR_PTR(-EOVERFLOW);
+       }
+       if (start >= BTRFS_32BIT_EARLY_WARN_THRESHOLD)
+               btrfs_warn_32bit_limit(fs_info);
+#endif
+
        if (fs_info->sectorsize < PAGE_SIZE &&
            offset_in_page(start) + len > PAGE_SIZE) {
                btrfs_err(fs_info,
index f7a4ad8..4a396c1 100644 (file)
@@ -252,6 +252,32 @@ void __cold btrfs_printk(const struct btrfs_fs_info *fs_info, const char *fmt, .
 }
 #endif
 
+#if BITS_PER_LONG == 32
+void __cold btrfs_warn_32bit_limit(struct btrfs_fs_info *fs_info)
+{
+       if (!test_and_set_bit(BTRFS_FS_32BIT_WARN, &fs_info->flags)) {
+               btrfs_warn(fs_info, "reaching 32bit limit for logical addresses");
+               btrfs_warn(fs_info,
+"due to page cache limit on 32bit systems, btrfs can't access metadata at or beyond %lluT",
+                          BTRFS_32BIT_MAX_FILE_SIZE >> 40);
+               btrfs_warn(fs_info,
+                          "please consider upgrading to 64bit kernel/hardware");
+       }
+}
+
+void __cold btrfs_err_32bit_limit(struct btrfs_fs_info *fs_info)
+{
+       if (!test_and_set_bit(BTRFS_FS_32BIT_ERROR, &fs_info->flags)) {
+               btrfs_err(fs_info, "reached 32bit limit for logical addresses");
+               btrfs_err(fs_info,
+"due to page cache limit on 32bit systems, metadata beyond %lluT can't be accessed",
+                         BTRFS_32BIT_MAX_FILE_SIZE >> 40);
+               btrfs_err(fs_info,
+                          "please consider upgrading to 64bit kernel/hardware");
+       }
+}
+#endif
+
 /*
  * We only mark the transaction aborted and then set the file system read-only.
  * This will prevent new transactions from starting or trying to join this
index 6d9b236..517b404 100644 (file)
@@ -6789,6 +6789,46 @@ static u64 calc_stripe_length(u64 type, u64 chunk_len, int num_stripes)
        return div_u64(chunk_len, data_stripes);
 }
 
+#if BITS_PER_LONG == 32
+/*
+ * Due to page cache limit, metadata beyond BTRFS_32BIT_MAX_FILE_SIZE
+ * can't be accessed on 32bit systems.
+ *
+ * This function do mount time check to reject the fs if it already has
+ * metadata chunk beyond that limit.
+ */
+static int check_32bit_meta_chunk(struct btrfs_fs_info *fs_info,
+                                 u64 logical, u64 length, u64 type)
+{
+       if (!(type & BTRFS_BLOCK_GROUP_METADATA))
+               return 0;
+
+       if (logical + length < MAX_LFS_FILESIZE)
+               return 0;
+
+       btrfs_err_32bit_limit(fs_info);
+       return -EOVERFLOW;
+}
+
+/*
+ * This is to give early warning for any metadata chunk reaching
+ * BTRFS_32BIT_EARLY_WARN_THRESHOLD.
+ * Although we can still access the metadata, it's not going to be possible
+ * once the limit is reached.
+ */
+static void warn_32bit_meta_chunk(struct btrfs_fs_info *fs_info,
+                                 u64 logical, u64 length, u64 type)
+{
+       if (!(type & BTRFS_BLOCK_GROUP_METADATA))
+               return;
+
+       if (logical + length < BTRFS_32BIT_EARLY_WARN_THRESHOLD)
+               return;
+
+       btrfs_warn_32bit_limit(fs_info);
+}
+#endif
+
 static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf,
                          struct btrfs_chunk *chunk)
 {
@@ -6799,6 +6839,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf,
        u64 logical;
        u64 length;
        u64 devid;
+       u64 type;
        u8 uuid[BTRFS_UUID_SIZE];
        int num_stripes;
        int ret;
@@ -6806,8 +6847,16 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf,
 
        logical = key->offset;
        length = btrfs_chunk_length(leaf, chunk);
+       type = btrfs_chunk_type(leaf, chunk);
        num_stripes = btrfs_chunk_num_stripes(leaf, chunk);
 
+#if BITS_PER_LONG == 32
+       ret = check_32bit_meta_chunk(fs_info, logical, length, type);
+       if (ret < 0)
+               return ret;
+       warn_32bit_meta_chunk(fs_info, logical, length, type);
+#endif
+
        /*
         * Only need to verify chunk item if we're reading from sys chunk array,
         * as chunk item in tree block is already verified by tree-checker.
@@ -6851,10 +6900,10 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf,
        map->io_width = btrfs_chunk_io_width(leaf, chunk);
        map->io_align = btrfs_chunk_io_align(leaf, chunk);
        map->stripe_len = btrfs_chunk_stripe_len(leaf, chunk);
-       map->type = btrfs_chunk_type(leaf, chunk);
+       map->type = type;
        map->sub_stripes = btrfs_chunk_sub_stripes(leaf, chunk);
        map->verified_stripes = 0;
-       em->orig_block_len = calc_stripe_length(map->type, em->len,
+       em->orig_block_len = calc_stripe_length(type, em->len,
                                                map->num_stripes);
        for (i = 0; i < num_stripes; i++) {
                map->stripes[i].physical =