tmpfs: support 64-bit inums per-sb
authorChris Down <chris@chrisdown.name>
Fri, 7 Aug 2020 06:20:25 +0000 (23:20 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 7 Aug 2020 18:33:24 +0000 (11:33 -0700)
The default is still set to inode32 for backwards compatibility, but
system administrators can opt in to the new 64-bit inode numbers by
either:

1. Passing inode64 on the command line when mounting, or
2. Configuring the kernel with CONFIG_TMPFS_INODE64=y

The inode64 and inode32 names are used based on existing precedent from
XFS.

[hughd@google.com: Kconfig fixes]
Link: http://lkml.kernel.org/r/alpine.LSU.2.11.2008011928010.13320@eggly.anvils
Signed-off-by: Chris Down <chris@chrisdown.name>
Signed-off-by: Hugh Dickins <hughd@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Acked-by: Hugh Dickins <hughd@google.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Jeff Layton <jlayton@kernel.org>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Tejun Heo <tj@kernel.org>
Link: http://lkml.kernel.org/r/8b23758d0c66b5e2263e08baf9c4b6a7565cbd8f.1594661218.git.chris@chrisdown.name
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Documentation/filesystems/tmpfs.rst
fs/Kconfig
include/linux/shmem_fs.h
mm/shmem.c

index 4e95929..c44f8b1 100644 (file)
@@ -150,6 +150,22 @@ These options do not have any effect on remount. You can change these
 parameters with chmod(1), chown(1) and chgrp(1) on a mounted filesystem.
 
 
+tmpfs has a mount option to select whether it will wrap at 32- or 64-bit inode
+numbers:
+
+=======   ========================
+inode64   Use 64-bit inode numbers
+inode32   Use 32-bit inode numbers
+=======   ========================
+
+On a 32-bit kernel, inode32 is implicit, and inode64 is refused at mount time.
+On a 64-bit kernel, CONFIG_TMPFS_INODE64 sets the default.  inode64 avoids the
+possibility of multiple files with the same inode number on a single device;
+but risks glibc failing with EOVERFLOW once 33-bit inode numbers are reached -
+if a long-lived tmpfs is accessed by 32-bit applications so ancient that
+opening a file larger than 2GiB fails with EINVAL.
+
+
 So 'mount -t tmpfs -o size=10G,nr_inodes=10k,mode=700 tmpfs /mytmpfs'
 will give you tmpfs instance on /mytmpfs which can allocate 10GB
 RAM/SWAP in 10240 inodes and it is only accessible by root.
@@ -161,3 +177,5 @@ RAM/SWAP in 10240 inodes and it is only accessible by root.
    Hugh Dickins, 4 June 2007
 :Updated:
    KOSAKI Motohiro, 16 Mar 2010
+:Updated:
+   Chris Down, 13 July 2020
index a88aa3a..aa4c122 100644 (file)
@@ -201,6 +201,27 @@ config TMPFS_XATTR
 
          If unsure, say N.
 
+config TMPFS_INODE64
+       bool "Use 64-bit ino_t by default in tmpfs"
+       depends on TMPFS && 64BIT
+       default n
+       help
+         tmpfs has historically used only inode numbers as wide as an unsigned
+         int. In some cases this can cause wraparound, potentially resulting
+         in multiple files with the same inode number on a single device. This
+         option makes tmpfs use the full width of ino_t by default, without
+         needing to specify the inode64 option when mounting.
+
+         But if a long-lived tmpfs is to be accessed by 32-bit applications so
+         ancient that opening a file larger than 2GiB fails with EINVAL, then
+         the INODE64 config option and inode64 mount option risk operations
+         failing with EOVERFLOW once 33-bit inode numbers are reached.
+
+         To override this configured default, use the inode32 or inode64
+         option when mounting.
+
+         If unsure, say N.
+
 config HUGETLBFS
        bool "HugeTLB file system support"
        depends on X86 || IA64 || SPARC64 || (S390 && 64BIT) || \
index eb62869..a5a5d1d 100644 (file)
@@ -36,6 +36,7 @@ struct shmem_sb_info {
        unsigned char huge;         /* Whether to try for hugepages */
        kuid_t uid;                 /* Mount uid for root directory */
        kgid_t gid;                 /* Mount gid for root directory */
+       bool full_inums;            /* If i_ino should be uint or ino_t */
        ino_t next_ino;             /* The next per-sb inode number to use */
        ino_t __percpu *ino_batch;  /* The next per-cpu inode number to use */
        struct mempolicy *mpol;     /* default memory policy for mappings */
index 585a82d..c5c2818 100644 (file)
@@ -114,11 +114,13 @@ struct shmem_options {
        kuid_t uid;
        kgid_t gid;
        umode_t mode;
+       bool full_inums;
        int huge;
        int seen;
 #define SHMEM_SEEN_BLOCKS 1
 #define SHMEM_SEEN_INODES 2
 #define SHMEM_SEEN_HUGE 4
+#define SHMEM_SEEN_INUMS 8
 };
 
 #ifdef CONFIG_TMPFS
@@ -286,12 +288,17 @@ static int shmem_reserve_inode(struct super_block *sb, ino_t *inop)
                        ino = sbinfo->next_ino++;
                        if (unlikely(is_zero_ino(ino)))
                                ino = sbinfo->next_ino++;
-                       if (unlikely(ino > UINT_MAX)) {
+                       if (unlikely(!sbinfo->full_inums &&
+                                    ino > UINT_MAX)) {
                                /*
                                 * Emulate get_next_ino uint wraparound for
                                 * compatibility
                                 */
-                               ino = 1;
+                               if (IS_ENABLED(CONFIG_64BIT))
+                                       pr_warn("%s: inode number overflow on device %d, consider using inode64 mount option\n",
+                                               __func__, MINOR(sb->s_dev));
+                               sbinfo->next_ino = 1;
+                               ino = sbinfo->next_ino++;
                        }
                        *inop = ino;
                }
@@ -304,6 +311,10 @@ static int shmem_reserve_inode(struct super_block *sb, ino_t *inop)
                 * unknown contexts. As such, use a per-cpu batched allocator
                 * which doesn't require the per-sb stat_lock unless we are at
                 * the batch boundary.
+                *
+                * We don't need to worry about inode{32,64} since SB_KERNMOUNT
+                * shmem mounts are not exposed to userspace, so we don't need
+                * to worry about things like glibc compatibility.
                 */
                ino_t *next_ino;
                next_ino = per_cpu_ptr(sbinfo->ino_batch, get_cpu());
@@ -3397,6 +3408,8 @@ enum shmem_param {
        Opt_nr_inodes,
        Opt_size,
        Opt_uid,
+       Opt_inode32,
+       Opt_inode64,
 };
 
 static const struct constant_table shmem_param_enums_huge[] = {
@@ -3416,6 +3429,8 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
        fsparam_string("nr_inodes",     Opt_nr_inodes),
        fsparam_string("size",          Opt_size),
        fsparam_u32   ("uid",           Opt_uid),
+       fsparam_flag  ("inode32",       Opt_inode32),
+       fsparam_flag  ("inode64",       Opt_inode64),
        {}
 };
 
@@ -3487,6 +3502,18 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
                        break;
                }
                goto unsupported_parameter;
+       case Opt_inode32:
+               ctx->full_inums = false;
+               ctx->seen |= SHMEM_SEEN_INUMS;
+               break;
+       case Opt_inode64:
+               if (sizeof(ino_t) < 8) {
+                       return invalfc(fc,
+                                      "Cannot use inode64 with <64bit inums in kernel\n");
+               }
+               ctx->full_inums = true;
+               ctx->seen |= SHMEM_SEEN_INUMS;
+               break;
        }
        return 0;
 
@@ -3578,8 +3605,16 @@ static int shmem_reconfigure(struct fs_context *fc)
                }
        }
 
+       if ((ctx->seen & SHMEM_SEEN_INUMS) && !ctx->full_inums &&
+           sbinfo->next_ino > UINT_MAX) {
+               err = "Current inum too high to switch to 32-bit inums";
+               goto out;
+       }
+
        if (ctx->seen & SHMEM_SEEN_HUGE)
                sbinfo->huge = ctx->huge;
+       if (ctx->seen & SHMEM_SEEN_INUMS)
+               sbinfo->full_inums = ctx->full_inums;
        if (ctx->seen & SHMEM_SEEN_BLOCKS)
                sbinfo->max_blocks  = ctx->blocks;
        if (ctx->seen & SHMEM_SEEN_INODES) {
@@ -3619,6 +3654,29 @@ static int shmem_show_options(struct seq_file *seq, struct dentry *root)
        if (!gid_eq(sbinfo->gid, GLOBAL_ROOT_GID))
                seq_printf(seq, ",gid=%u",
                                from_kgid_munged(&init_user_ns, sbinfo->gid));
+
+       /*
+        * Showing inode{64,32} might be useful even if it's the system default,
+        * since then people don't have to resort to checking both here and
+        * /proc/config.gz to confirm 64-bit inums were successfully applied
+        * (which may not even exist if IKCONFIG_PROC isn't enabled).
+        *
+        * We hide it when inode64 isn't the default and we are using 32-bit
+        * inodes, since that probably just means the feature isn't even under
+        * consideration.
+        *
+        * As such:
+        *
+        *                     +-----------------+-----------------+
+        *                     | TMPFS_INODE64=y | TMPFS_INODE64=n |
+        *  +------------------+-----------------+-----------------+
+        *  | full_inums=true  | show            | show            |
+        *  | full_inums=false | show            | hide            |
+        *  +------------------+-----------------+-----------------+
+        *
+        */
+       if (IS_ENABLED(CONFIG_TMPFS_INODE64) || sbinfo->full_inums)
+               seq_printf(seq, ",inode%d", (sbinfo->full_inums ? 64 : 32));
 #ifdef CONFIG_TRANSPARENT_HUGEPAGE
        /* Rightly or wrongly, show huge mount option unmasked by shmem_huge */
        if (sbinfo->huge)
@@ -3667,6 +3725,8 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
                        ctx->blocks = shmem_default_max_blocks();
                if (!(ctx->seen & SHMEM_SEEN_INODES))
                        ctx->inodes = shmem_default_max_inodes();
+               if (!(ctx->seen & SHMEM_SEEN_INUMS))
+                       ctx->full_inums = IS_ENABLED(CONFIG_TMPFS_INODE64);
        } else {
                sb->s_flags |= SB_NOUSER;
        }
@@ -3684,6 +3744,7 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
        }
        sbinfo->uid = ctx->uid;
        sbinfo->gid = ctx->gid;
+       sbinfo->full_inums = ctx->full_inums;
        sbinfo->mode = ctx->mode;
        sbinfo->huge = ctx->huge;
        sbinfo->mpol = ctx->mpol;