Commit ea3271f7 authored by Chris Down's avatar Chris Down Committed by Linus Torvalds

tmpfs: support 64-bit inums per-sb

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.anvilsSigned-off-by: default avatarChris Down <chris@chrisdown.name>
Signed-off-by: default avatarHugh Dickins <hughd@google.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Reviewed-by: default avatarAmir Goldstein <amir73il@gmail.com>
Acked-by: default avatarHugh 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.nameSigned-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent e809d5f0
...@@ -150,6 +150,22 @@ These options do not have any effect on remount. You can change these ...@@ -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. 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' 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 will give you tmpfs instance on /mytmpfs which can allocate 10GB
RAM/SWAP in 10240 inodes and it is only accessible by root. 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. ...@@ -161,3 +177,5 @@ RAM/SWAP in 10240 inodes and it is only accessible by root.
Hugh Dickins, 4 June 2007 Hugh Dickins, 4 June 2007
:Updated: :Updated:
KOSAKI Motohiro, 16 Mar 2010 KOSAKI Motohiro, 16 Mar 2010
:Updated:
Chris Down, 13 July 2020
...@@ -201,6 +201,27 @@ config TMPFS_XATTR ...@@ -201,6 +201,27 @@ config TMPFS_XATTR
If unsure, say N. 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 config HUGETLBFS
bool "HugeTLB file system support" bool "HugeTLB file system support"
depends on X86 || IA64 || SPARC64 || (S390 && 64BIT) || \ depends on X86 || IA64 || SPARC64 || (S390 && 64BIT) || \
......
...@@ -36,6 +36,7 @@ struct shmem_sb_info { ...@@ -36,6 +36,7 @@ struct shmem_sb_info {
unsigned char huge; /* Whether to try for hugepages */ unsigned char huge; /* Whether to try for hugepages */
kuid_t uid; /* Mount uid for root directory */ kuid_t uid; /* Mount uid for root directory */
kgid_t gid; /* Mount gid 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 next_ino; /* The next per-sb inode number to use */
ino_t __percpu *ino_batch; /* The next per-cpu 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 */ struct mempolicy *mpol; /* default memory policy for mappings */
......
...@@ -114,11 +114,13 @@ struct shmem_options { ...@@ -114,11 +114,13 @@ struct shmem_options {
kuid_t uid; kuid_t uid;
kgid_t gid; kgid_t gid;
umode_t mode; umode_t mode;
bool full_inums;
int huge; int huge;
int seen; int seen;
#define SHMEM_SEEN_BLOCKS 1 #define SHMEM_SEEN_BLOCKS 1
#define SHMEM_SEEN_INODES 2 #define SHMEM_SEEN_INODES 2
#define SHMEM_SEEN_HUGE 4 #define SHMEM_SEEN_HUGE 4
#define SHMEM_SEEN_INUMS 8
}; };
#ifdef CONFIG_TMPFS #ifdef CONFIG_TMPFS
...@@ -286,12 +288,17 @@ static int shmem_reserve_inode(struct super_block *sb, ino_t *inop) ...@@ -286,12 +288,17 @@ static int shmem_reserve_inode(struct super_block *sb, ino_t *inop)
ino = sbinfo->next_ino++; ino = sbinfo->next_ino++;
if (unlikely(is_zero_ino(ino))) if (unlikely(is_zero_ino(ino)))
ino = sbinfo->next_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 * Emulate get_next_ino uint wraparound for
* compatibility * 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; *inop = ino;
} }
...@@ -304,6 +311,10 @@ static int shmem_reserve_inode(struct super_block *sb, ino_t *inop) ...@@ -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 * unknown contexts. As such, use a per-cpu batched allocator
* which doesn't require the per-sb stat_lock unless we are at * which doesn't require the per-sb stat_lock unless we are at
* the batch boundary. * 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; ino_t *next_ino;
next_ino = per_cpu_ptr(sbinfo->ino_batch, get_cpu()); next_ino = per_cpu_ptr(sbinfo->ino_batch, get_cpu());
...@@ -3397,6 +3408,8 @@ enum shmem_param { ...@@ -3397,6 +3408,8 @@ enum shmem_param {
Opt_nr_inodes, Opt_nr_inodes,
Opt_size, Opt_size,
Opt_uid, Opt_uid,
Opt_inode32,
Opt_inode64,
}; };
static const struct constant_table shmem_param_enums_huge[] = { static const struct constant_table shmem_param_enums_huge[] = {
...@@ -3416,6 +3429,8 @@ const struct fs_parameter_spec shmem_fs_parameters[] = { ...@@ -3416,6 +3429,8 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
fsparam_string("nr_inodes", Opt_nr_inodes), fsparam_string("nr_inodes", Opt_nr_inodes),
fsparam_string("size", Opt_size), fsparam_string("size", Opt_size),
fsparam_u32 ("uid", Opt_uid), 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) ...@@ -3487,6 +3502,18 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
break; break;
} }
goto unsupported_parameter; 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; return 0;
...@@ -3578,8 +3605,16 @@ static int shmem_reconfigure(struct fs_context *fc) ...@@ -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) if (ctx->seen & SHMEM_SEEN_HUGE)
sbinfo->huge = ctx->huge; sbinfo->huge = ctx->huge;
if (ctx->seen & SHMEM_SEEN_INUMS)
sbinfo->full_inums = ctx->full_inums;
if (ctx->seen & SHMEM_SEEN_BLOCKS) if (ctx->seen & SHMEM_SEEN_BLOCKS)
sbinfo->max_blocks = ctx->blocks; sbinfo->max_blocks = ctx->blocks;
if (ctx->seen & SHMEM_SEEN_INODES) { if (ctx->seen & SHMEM_SEEN_INODES) {
...@@ -3619,6 +3654,29 @@ static int shmem_show_options(struct seq_file *seq, struct dentry *root) ...@@ -3619,6 +3654,29 @@ static int shmem_show_options(struct seq_file *seq, struct dentry *root)
if (!gid_eq(sbinfo->gid, GLOBAL_ROOT_GID)) if (!gid_eq(sbinfo->gid, GLOBAL_ROOT_GID))
seq_printf(seq, ",gid=%u", seq_printf(seq, ",gid=%u",
from_kgid_munged(&init_user_ns, sbinfo->gid)); 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 #ifdef CONFIG_TRANSPARENT_HUGEPAGE
/* Rightly or wrongly, show huge mount option unmasked by shmem_huge */ /* Rightly or wrongly, show huge mount option unmasked by shmem_huge */
if (sbinfo->huge) if (sbinfo->huge)
...@@ -3667,6 +3725,8 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc) ...@@ -3667,6 +3725,8 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
ctx->blocks = shmem_default_max_blocks(); ctx->blocks = shmem_default_max_blocks();
if (!(ctx->seen & SHMEM_SEEN_INODES)) if (!(ctx->seen & SHMEM_SEEN_INODES))
ctx->inodes = shmem_default_max_inodes(); ctx->inodes = shmem_default_max_inodes();
if (!(ctx->seen & SHMEM_SEEN_INUMS))
ctx->full_inums = IS_ENABLED(CONFIG_TMPFS_INODE64);
} else { } else {
sb->s_flags |= SB_NOUSER; sb->s_flags |= SB_NOUSER;
} }
...@@ -3684,6 +3744,7 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc) ...@@ -3684,6 +3744,7 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
} }
sbinfo->uid = ctx->uid; sbinfo->uid = ctx->uid;
sbinfo->gid = ctx->gid; sbinfo->gid = ctx->gid;
sbinfo->full_inums = ctx->full_inums;
sbinfo->mode = ctx->mode; sbinfo->mode = ctx->mode;
sbinfo->huge = ctx->huge; sbinfo->huge = ctx->huge;
sbinfo->mpol = ctx->mpol; sbinfo->mpol = ctx->mpol;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment