mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 09:16:33 +00:00
vfs-6.13.tmpfs
-----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCZzcZIgAKCRCRxhvAZXjc oge4AQDxhsKW+v/jKHydzqzwG3Ks7DIxrUg/mcGfdtBwjiWgvwEA8t0QAAfKECAK B0+bNKJ8XJRUtZ10Jgm3dzURbEhBWgU= =4Lui -----END PGP SIGNATURE----- Merge tag 'vfs-6.13.tmpfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs Pull tmpfs case folding updates from Christian Brauner: "This adds case-insensitive support for tmpfs. The work contained in here adds support for case-insensitive file names lookups in tmpfs. The main difference from other casefold filesystems is that tmpfs has no information on disk, just on RAM, so we can't use mkfs to create a case-insensitive tmpfs. For this implementation, there's a mount option for casefolding. The rest of the patchset follows a similar approach as ext4 and f2fs. The use case for this feature is similar to the use case for ext4, to better support compatibility layers (like Wine), particularly in combination with sandboxing/container tools (like Flatpak). Those containerization tools can share a subset of the host filesystem with an application. In the container, the root directory and any parent directories required for a shared directory are on tmpfs, with the shared directories bind-mounted into the container's view of the filesystem. If the host filesystem is using case-insensitive directories, then the application can do lookups inside those directories in a case-insensitive way, without this needing to be implemented in user-space. However, if the host is only sharing a subset of a case-insensitive directory with the application, then the parent directories of the mount point will be part of the container's root tmpfs. When the application tries to do case-insensitive lookups of those parent directories on a case-sensitive tmpfs, the lookup will fail" * tag 'vfs-6.13.tmpfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: tmpfs: Initialize sysfs during tmpfs init tmpfs: Fix type for sysfs' casefold attribute libfs: Fix kernel-doc warning in generic_ci_validate_strict_name docs: tmpfs: Add casefold options tmpfs: Expose filesystem features via sysfs tmpfs: Add flag FS_CASEFOLD_FL support for tmpfs dirs tmpfs: Add casefold lookup support libfs: Export generic_ci_ dentry functions unicode: Recreate utf8_parse_version() unicode: Export latest available UTF-8 version number ext4: Use generic_ci_validate_strict_name helper libfs: Create the helper function generic_ci_validate_strict_name()
This commit is contained in:
commit
7956186e75
@ -241,6 +241,28 @@ 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.
|
||||
|
||||
tmpfs has the following mounting options for case-insensitive lookup support:
|
||||
|
||||
================= ==============================================================
|
||||
casefold Enable casefold support at this mount point using the given
|
||||
argument as the encoding standard. Currently only UTF-8
|
||||
encodings are supported. If no argument is used, it will load
|
||||
the latest UTF-8 encoding available.
|
||||
strict_encoding Enable strict encoding at this mount point (disabled by
|
||||
default). In this mode, the filesystem refuses to create file
|
||||
and directory with names containing invalid UTF-8 characters.
|
||||
================= ==============================================================
|
||||
|
||||
This option doesn't render the entire filesystem case-insensitive. One needs to
|
||||
still set the casefold flag per directory, by flipping the +F attribute in an
|
||||
empty directory. Nevertheless, new directories will inherit the attribute. The
|
||||
mountpoint itself cannot be made case-insensitive.
|
||||
|
||||
Example::
|
||||
|
||||
$ mount -t tmpfs -o casefold=utf8-12.1.0,strict_encoding fs_name /mytmpfs
|
||||
$ mount -t tmpfs -o casefold fs_name /mytmpfs
|
||||
|
||||
|
||||
:Author:
|
||||
Christoph Rohland <cr@sap.com>, 1.12.01
|
||||
@ -250,3 +272,5 @@ RAM/SWAP in 10240 inodes and it is only accessible by root.
|
||||
KOSAKI Motohiro, 16 Mar 2010
|
||||
:Updated:
|
||||
Chris Down, 13 July 2020
|
||||
:Updated:
|
||||
André Almeida, 23 Aug 2024
|
||||
|
@ -2395,11 +2395,8 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
|
||||
if (fscrypt_is_nokey_name(dentry))
|
||||
return -ENOKEY;
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
if (sb_has_strict_encoding(sb) && IS_CASEFOLDED(dir) &&
|
||||
utf8_validate(sb->s_encoding, &dentry->d_name))
|
||||
if (!generic_ci_validate_strict_name(dir, &dentry->d_name))
|
||||
return -EINVAL;
|
||||
#endif
|
||||
|
||||
retval = ext4_fname_setup_filename(dir, &dentry->d_name, 0, &fname);
|
||||
if (retval)
|
||||
|
12
fs/libfs.c
12
fs/libfs.c
@ -77,6 +77,10 @@ struct dentry *simple_lookup(struct inode *dir, struct dentry *dentry, unsigned
|
||||
return ERR_PTR(-ENAMETOOLONG);
|
||||
if (!dentry->d_sb->s_d_op)
|
||||
d_set_d_op(dentry, &simple_dentry_operations);
|
||||
|
||||
if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir))
|
||||
return NULL;
|
||||
|
||||
d_add(dentry, NULL);
|
||||
return NULL;
|
||||
}
|
||||
@ -1791,8 +1795,8 @@ bool is_empty_dir_inode(struct inode *inode)
|
||||
*
|
||||
* Return: 0 if names match, 1 if mismatch, or -ERRNO
|
||||
*/
|
||||
static int generic_ci_d_compare(const struct dentry *dentry, unsigned int len,
|
||||
const char *str, const struct qstr *name)
|
||||
int generic_ci_d_compare(const struct dentry *dentry, unsigned int len,
|
||||
const char *str, const struct qstr *name)
|
||||
{
|
||||
const struct dentry *parent;
|
||||
const struct inode *dir;
|
||||
@ -1835,6 +1839,7 @@ static int generic_ci_d_compare(const struct dentry *dentry, unsigned int len,
|
||||
|
||||
return utf8_strncasecmp(dentry->d_sb->s_encoding, name, &qstr);
|
||||
}
|
||||
EXPORT_SYMBOL(generic_ci_d_compare);
|
||||
|
||||
/**
|
||||
* generic_ci_d_hash - generic d_hash implementation for casefolding filesystems
|
||||
@ -1843,7 +1848,7 @@ static int generic_ci_d_compare(const struct dentry *dentry, unsigned int len,
|
||||
*
|
||||
* Return: 0 if hash was successful or unchanged, and -EINVAL on error
|
||||
*/
|
||||
static int generic_ci_d_hash(const struct dentry *dentry, struct qstr *str)
|
||||
int generic_ci_d_hash(const struct dentry *dentry, struct qstr *str)
|
||||
{
|
||||
const struct inode *dir = READ_ONCE(dentry->d_inode);
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
@ -1858,6 +1863,7 @@ static int generic_ci_d_hash(const struct dentry *dentry, struct qstr *str)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(generic_ci_d_hash);
|
||||
|
||||
static const struct dentry_operations generic_ci_dentry_ops = {
|
||||
.d_hash = generic_ci_d_hash,
|
||||
|
@ -214,3 +214,29 @@ void utf8_unload(struct unicode_map *um)
|
||||
}
|
||||
EXPORT_SYMBOL(utf8_unload);
|
||||
|
||||
/**
|
||||
* utf8_parse_version - Parse a UTF-8 version number from a string
|
||||
*
|
||||
* @version: input string
|
||||
*
|
||||
* Returns the parsed version on success, negative code on error
|
||||
*/
|
||||
int utf8_parse_version(char *version)
|
||||
{
|
||||
substring_t args[3];
|
||||
unsigned int maj, min, rev;
|
||||
static const struct match_token token[] = {
|
||||
{1, "%d.%d.%d"},
|
||||
{0, NULL}
|
||||
};
|
||||
|
||||
if (match_token(version, token, args) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (match_int(&args[0], &maj) || match_int(&args[1], &min) ||
|
||||
match_int(&args[2], &rev))
|
||||
return -EINVAL;
|
||||
|
||||
return UNICODE_AGE(maj, min, rev);
|
||||
}
|
||||
EXPORT_SYMBOL(utf8_parse_version);
|
||||
|
@ -17,9 +17,6 @@
|
||||
static unsigned int failed_tests;
|
||||
static unsigned int total_tests;
|
||||
|
||||
/* Tests will be based on this version. */
|
||||
#define UTF8_LATEST UNICODE_AGE(12, 1, 0)
|
||||
|
||||
#define _test(cond, func, line, fmt, ...) do { \
|
||||
total_tests++; \
|
||||
if (!cond) { \
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include <linux/maple_tree.h>
|
||||
#include <linux/rw_hint.h>
|
||||
#include <linux/file_ref.h>
|
||||
#include <linux/unicode.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
#include <uapi/linux/fs.h>
|
||||
@ -3479,6 +3480,54 @@ extern int generic_ci_match(const struct inode *parent,
|
||||
const struct qstr *folded_name,
|
||||
const u8 *de_name, u32 de_name_len);
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
int generic_ci_d_hash(const struct dentry *dentry, struct qstr *str);
|
||||
int generic_ci_d_compare(const struct dentry *dentry, unsigned int len,
|
||||
const char *str, const struct qstr *name);
|
||||
|
||||
/**
|
||||
* generic_ci_validate_strict_name - Check if a given name is suitable
|
||||
* for a directory
|
||||
*
|
||||
* This functions checks if the proposed filename is valid for the
|
||||
* parent directory. That means that only valid UTF-8 filenames will be
|
||||
* accepted for casefold directories from filesystems created with the
|
||||
* strict encoding flag. That also means that any name will be
|
||||
* accepted for directories that doesn't have casefold enabled, or
|
||||
* aren't being strict with the encoding.
|
||||
*
|
||||
* @dir: inode of the directory where the new file will be created
|
||||
* @name: name of the new file
|
||||
*
|
||||
* Return:
|
||||
* * True: if the filename is suitable for this directory. It can be
|
||||
* true if a given name is not suitable for a strict encoding
|
||||
* directory, but the directory being used isn't strict
|
||||
* * False if the filename isn't suitable for this directory. This only
|
||||
* happens when a directory is casefolded and the filesystem is strict
|
||||
* about its encoding.
|
||||
*/
|
||||
static inline bool generic_ci_validate_strict_name(struct inode *dir, struct qstr *name)
|
||||
{
|
||||
if (!IS_CASEFOLDED(dir) || !sb_has_strict_encoding(dir->i_sb))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* A casefold dir must have a encoding set, unless the filesystem
|
||||
* is corrupted
|
||||
*/
|
||||
if (WARN_ON_ONCE(!dir->i_sb->s_encoding))
|
||||
return true;
|
||||
|
||||
return !utf8_validate(dir->i_sb->s_encoding, name);
|
||||
}
|
||||
#else
|
||||
static inline bool generic_ci_validate_strict_name(struct inode *dir, struct qstr *name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline bool sb_has_encoding(const struct super_block *sb)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
|
@ -42,10 +42,10 @@ struct shmem_inode_info {
|
||||
struct inode vfs_inode;
|
||||
};
|
||||
|
||||
#define SHMEM_FL_USER_VISIBLE FS_FL_USER_VISIBLE
|
||||
#define SHMEM_FL_USER_VISIBLE (FS_FL_USER_VISIBLE | FS_CASEFOLD_FL)
|
||||
#define SHMEM_FL_USER_MODIFIABLE \
|
||||
(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL | FS_NOATIME_FL)
|
||||
#define SHMEM_FL_INHERITED (FS_NODUMP_FL | FS_NOATIME_FL)
|
||||
(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL | FS_NOATIME_FL | FS_CASEFOLD_FL)
|
||||
#define SHMEM_FL_INHERITED (FS_NODUMP_FL | FS_NOATIME_FL | FS_CASEFOLD_FL)
|
||||
|
||||
struct shmem_quota_limits {
|
||||
qsize_t usrquota_bhardlimit; /* Default user quota block hard limit */
|
||||
|
@ -16,6 +16,8 @@ struct utf8data_table;
|
||||
((unsigned int)(MIN) << UNICODE_MIN_SHIFT) | \
|
||||
((unsigned int)(REV)))
|
||||
|
||||
#define UTF8_LATEST UNICODE_AGE(12, 1, 0)
|
||||
|
||||
static inline u8 unicode_major(unsigned int age)
|
||||
{
|
||||
return (age >> UNICODE_MAJ_SHIFT) & 0xff;
|
||||
@ -76,4 +78,6 @@ int utf8_casefold_hash(const struct unicode_map *um, const void *salt,
|
||||
struct unicode_map *utf8_load(unsigned int version);
|
||||
void utf8_unload(struct unicode_map *um);
|
||||
|
||||
int utf8_parse_version(char *version);
|
||||
|
||||
#endif /* _LINUX_UNICODE_H */
|
||||
|
265
mm/shmem.c
265
mm/shmem.c
@ -40,6 +40,7 @@
|
||||
#include <linux/fs_parser.h>
|
||||
#include <linux/swapfile.h>
|
||||
#include <linux/iversion.h>
|
||||
#include <linux/unicode.h>
|
||||
#include "swap.h"
|
||||
|
||||
static struct vfsmount *shm_mnt __ro_after_init;
|
||||
@ -123,6 +124,10 @@ struct shmem_options {
|
||||
bool noswap;
|
||||
unsigned short quota_types;
|
||||
struct shmem_quota_limits qlimits;
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
struct unicode_map *encoding;
|
||||
bool strict_encoding;
|
||||
#endif
|
||||
#define SHMEM_SEEN_BLOCKS 1
|
||||
#define SHMEM_SEEN_INODES 2
|
||||
#define SHMEM_SEEN_HUGE 4
|
||||
@ -2749,13 +2754,62 @@ static int shmem_file_open(struct inode *inode, struct file *file)
|
||||
#ifdef CONFIG_TMPFS_XATTR
|
||||
static int shmem_initxattrs(struct inode *, const struct xattr *, void *);
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
/*
|
||||
* shmem_inode_casefold_flags - Deal with casefold file attribute flag
|
||||
*
|
||||
* The casefold file attribute needs some special checks. I can just be added to
|
||||
* an empty dir, and can't be removed from a non-empty dir.
|
||||
*/
|
||||
static int shmem_inode_casefold_flags(struct inode *inode, unsigned int fsflags,
|
||||
struct dentry *dentry, unsigned int *i_flags)
|
||||
{
|
||||
unsigned int old = inode->i_flags;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
|
||||
if (fsflags & FS_CASEFOLD_FL) {
|
||||
if (!(old & S_CASEFOLD)) {
|
||||
if (!sb->s_encoding)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (!S_ISDIR(inode->i_mode))
|
||||
return -ENOTDIR;
|
||||
|
||||
if (dentry && !simple_empty(dentry))
|
||||
return -ENOTEMPTY;
|
||||
}
|
||||
|
||||
*i_flags = *i_flags | S_CASEFOLD;
|
||||
} else if (old & S_CASEFOLD) {
|
||||
if (dentry && !simple_empty(dentry))
|
||||
return -ENOTEMPTY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int shmem_inode_casefold_flags(struct inode *inode, unsigned int fsflags,
|
||||
struct dentry *dentry, unsigned int *i_flags)
|
||||
{
|
||||
if (fsflags & FS_CASEFOLD_FL)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* chattr's fsflags are unrelated to extended attributes,
|
||||
* but tmpfs has chosen to enable them under the same config option.
|
||||
*/
|
||||
static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
|
||||
static int shmem_set_inode_flags(struct inode *inode, unsigned int fsflags, struct dentry *dentry)
|
||||
{
|
||||
unsigned int i_flags = 0;
|
||||
int ret;
|
||||
|
||||
ret = shmem_inode_casefold_flags(inode, fsflags, dentry, &i_flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (fsflags & FS_NOATIME_FL)
|
||||
i_flags |= S_NOATIME;
|
||||
@ -2766,10 +2820,12 @@ static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
|
||||
/*
|
||||
* But FS_NODUMP_FL does not require any action in i_flags.
|
||||
*/
|
||||
inode_set_flags(inode, i_flags, S_NOATIME | S_APPEND | S_IMMUTABLE);
|
||||
inode_set_flags(inode, i_flags, S_NOATIME | S_APPEND | S_IMMUTABLE | S_CASEFOLD);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
|
||||
static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags, struct dentry *dentry)
|
||||
{
|
||||
}
|
||||
#define shmem_initxattrs NULL
|
||||
@ -2816,7 +2872,7 @@ static struct inode *__shmem_get_inode(struct mnt_idmap *idmap,
|
||||
info->fsflags = (dir == NULL) ? 0 :
|
||||
SHMEM_I(dir)->fsflags & SHMEM_FL_INHERITED;
|
||||
if (info->fsflags)
|
||||
shmem_set_inode_flags(inode, info->fsflags);
|
||||
shmem_set_inode_flags(inode, info->fsflags, NULL);
|
||||
INIT_LIST_HEAD(&info->shrinklist);
|
||||
INIT_LIST_HEAD(&info->swaplist);
|
||||
simple_xattrs_init(&info->xattrs);
|
||||
@ -3562,6 +3618,9 @@ shmem_mknod(struct mnt_idmap *idmap, struct inode *dir,
|
||||
struct inode *inode;
|
||||
int error;
|
||||
|
||||
if (!generic_ci_validate_strict_name(dir, &dentry->d_name))
|
||||
return -EINVAL;
|
||||
|
||||
inode = shmem_get_inode(idmap, dir->i_sb, dir, mode, dev, VM_NORESERVE);
|
||||
if (IS_ERR(inode))
|
||||
return PTR_ERR(inode);
|
||||
@ -3581,7 +3640,12 @@ shmem_mknod(struct mnt_idmap *idmap, struct inode *dir,
|
||||
dir->i_size += BOGO_DIRENT_SIZE;
|
||||
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
|
||||
inode_inc_iversion(dir);
|
||||
d_instantiate(dentry, inode);
|
||||
|
||||
if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir))
|
||||
d_add(dentry, inode);
|
||||
else
|
||||
d_instantiate(dentry, inode);
|
||||
|
||||
dget(dentry); /* Extra count - pin the dentry in core */
|
||||
return error;
|
||||
|
||||
@ -3672,7 +3736,10 @@ static int shmem_link(struct dentry *old_dentry, struct inode *dir,
|
||||
inc_nlink(inode);
|
||||
ihold(inode); /* New dentry reference */
|
||||
dget(dentry); /* Extra pinning count for the created dentry */
|
||||
d_instantiate(dentry, inode);
|
||||
if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir))
|
||||
d_add(dentry, inode);
|
||||
else
|
||||
d_instantiate(dentry, inode);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
@ -3692,6 +3759,14 @@ static int shmem_unlink(struct inode *dir, struct dentry *dentry)
|
||||
inode_inc_iversion(dir);
|
||||
drop_nlink(inode);
|
||||
dput(dentry); /* Undo the count from "create" - does all the work */
|
||||
|
||||
/*
|
||||
* For now, VFS can't deal with case-insensitive negative dentries, so
|
||||
* we invalidate them
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir))
|
||||
d_invalidate(dentry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -3836,7 +3911,10 @@ static int shmem_symlink(struct mnt_idmap *idmap, struct inode *dir,
|
||||
dir->i_size += BOGO_DIRENT_SIZE;
|
||||
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
|
||||
inode_inc_iversion(dir);
|
||||
d_instantiate(dentry, inode);
|
||||
if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir))
|
||||
d_add(dentry, inode);
|
||||
else
|
||||
d_instantiate(dentry, inode);
|
||||
dget(dentry);
|
||||
return 0;
|
||||
|
||||
@ -3901,16 +3979,23 @@ static int shmem_fileattr_set(struct mnt_idmap *idmap,
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct shmem_inode_info *info = SHMEM_I(inode);
|
||||
int ret, flags;
|
||||
|
||||
if (fileattr_has_fsx(fa))
|
||||
return -EOPNOTSUPP;
|
||||
if (fa->flags & ~SHMEM_FL_USER_MODIFIABLE)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
info->fsflags = (info->fsflags & ~SHMEM_FL_USER_MODIFIABLE) |
|
||||
flags = (info->fsflags & ~SHMEM_FL_USER_MODIFIABLE) |
|
||||
(fa->flags & SHMEM_FL_USER_MODIFIABLE);
|
||||
|
||||
shmem_set_inode_flags(inode, info->fsflags);
|
||||
ret = shmem_set_inode_flags(inode, flags, dentry);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
info->fsflags = flags;
|
||||
|
||||
inode_set_ctime_current(inode);
|
||||
inode_inc_iversion(inode);
|
||||
return 0;
|
||||
@ -4189,6 +4274,9 @@ enum shmem_param {
|
||||
Opt_usrquota_inode_hardlimit,
|
||||
Opt_grpquota_block_hardlimit,
|
||||
Opt_grpquota_inode_hardlimit,
|
||||
Opt_casefold_version,
|
||||
Opt_casefold,
|
||||
Opt_strict_encoding,
|
||||
};
|
||||
|
||||
static const struct constant_table shmem_param_enums_huge[] = {
|
||||
@ -4220,9 +4308,54 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
|
||||
fsparam_string("grpquota_block_hardlimit", Opt_grpquota_block_hardlimit),
|
||||
fsparam_string("grpquota_inode_hardlimit", Opt_grpquota_inode_hardlimit),
|
||||
#endif
|
||||
fsparam_string("casefold", Opt_casefold_version),
|
||||
fsparam_flag ("casefold", Opt_casefold),
|
||||
fsparam_flag ("strict_encoding", Opt_strict_encoding),
|
||||
{}
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
static int shmem_parse_opt_casefold(struct fs_context *fc, struct fs_parameter *param,
|
||||
bool latest_version)
|
||||
{
|
||||
struct shmem_options *ctx = fc->fs_private;
|
||||
unsigned int version = UTF8_LATEST;
|
||||
struct unicode_map *encoding;
|
||||
char *version_str = param->string + 5;
|
||||
|
||||
if (!latest_version) {
|
||||
if (strncmp(param->string, "utf8-", 5))
|
||||
return invalfc(fc, "Only UTF-8 encodings are supported "
|
||||
"in the format: utf8-<version number>");
|
||||
|
||||
version = utf8_parse_version(version_str);
|
||||
if (version < 0)
|
||||
return invalfc(fc, "Invalid UTF-8 version: %s", version_str);
|
||||
}
|
||||
|
||||
encoding = utf8_load(version);
|
||||
|
||||
if (IS_ERR(encoding)) {
|
||||
return invalfc(fc, "Failed loading UTF-8 version: utf8-%u.%u.%u\n",
|
||||
unicode_major(version), unicode_minor(version),
|
||||
unicode_rev(version));
|
||||
}
|
||||
|
||||
pr_info("tmpfs: Using encoding : utf8-%u.%u.%u\n",
|
||||
unicode_major(version), unicode_minor(version), unicode_rev(version));
|
||||
|
||||
ctx->encoding = encoding;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int shmem_parse_opt_casefold(struct fs_context *fc, struct fs_parameter *param,
|
||||
bool latest_version)
|
||||
{
|
||||
return invalfc(fc, "tmpfs: Kernel not built with CONFIG_UNICODE\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
|
||||
{
|
||||
struct shmem_options *ctx = fc->fs_private;
|
||||
@ -4381,6 +4514,17 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
|
||||
"Group quota inode hardlimit too large.");
|
||||
ctx->qlimits.grpquota_ihardlimit = size;
|
||||
break;
|
||||
case Opt_casefold_version:
|
||||
return shmem_parse_opt_casefold(fc, param, false);
|
||||
case Opt_casefold:
|
||||
return shmem_parse_opt_casefold(fc, param, true);
|
||||
case Opt_strict_encoding:
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
ctx->strict_encoding = true;
|
||||
break;
|
||||
#else
|
||||
return invalfc(fc, "tmpfs: Kernel not built with CONFIG_UNICODE\n");
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
|
||||
@ -4610,6 +4754,11 @@ static void shmem_put_super(struct super_block *sb)
|
||||
{
|
||||
struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
if (sb->s_encoding)
|
||||
utf8_unload(sb->s_encoding);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TMPFS_QUOTA
|
||||
shmem_disable_quotas(sb);
|
||||
#endif
|
||||
@ -4620,6 +4769,14 @@ static void shmem_put_super(struct super_block *sb)
|
||||
sb->s_fs_info = NULL;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_TMPFS)
|
||||
static const struct dentry_operations shmem_ci_dentry_ops = {
|
||||
.d_hash = generic_ci_d_hash,
|
||||
.d_compare = generic_ci_d_compare,
|
||||
.d_delete = always_delete_dentry,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
{
|
||||
struct shmem_options *ctx = fc->fs_private;
|
||||
@ -4654,9 +4811,25 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
}
|
||||
sb->s_export_op = &shmem_export_ops;
|
||||
sb->s_flags |= SB_NOSEC | SB_I_VERSION;
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
if (!ctx->encoding && ctx->strict_encoding) {
|
||||
pr_err("tmpfs: strict_encoding option without encoding is forbidden\n");
|
||||
error = -EINVAL;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (ctx->encoding) {
|
||||
sb->s_encoding = ctx->encoding;
|
||||
sb->s_d_op = &shmem_ci_dentry_ops;
|
||||
if (ctx->strict_encoding)
|
||||
sb->s_encoding_flags = SB_ENC_STRICT_MODE_FL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
sb->s_flags |= SB_NOUSER;
|
||||
#endif
|
||||
#endif /* CONFIG_TMPFS */
|
||||
sbinfo->max_blocks = ctx->blocks;
|
||||
sbinfo->max_inodes = ctx->inodes;
|
||||
sbinfo->free_ispace = sbinfo->max_inodes * BOGO_INODE_SIZE;
|
||||
@ -4930,6 +5103,10 @@ int shmem_init_fs_context(struct fs_context *fc)
|
||||
ctx->uid = current_fsuid();
|
||||
ctx->gid = current_fsgid();
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
ctx->encoding = NULL;
|
||||
#endif
|
||||
|
||||
fc->fs_private = ctx;
|
||||
fc->ops = &shmem_fs_context_ops;
|
||||
return 0;
|
||||
@ -4946,6 +5123,66 @@ static struct file_system_type shmem_fs_type = {
|
||||
.fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME,
|
||||
};
|
||||
|
||||
#if defined(CONFIG_SYSFS) && defined(CONFIG_TMPFS)
|
||||
|
||||
#define __INIT_KOBJ_ATTR(_name, _mode, _show, _store) \
|
||||
{ \
|
||||
.attr = { .name = __stringify(_name), .mode = _mode }, \
|
||||
.show = _show, \
|
||||
.store = _store, \
|
||||
}
|
||||
|
||||
#define TMPFS_ATTR_W(_name, _store) \
|
||||
static struct kobj_attribute tmpfs_attr_##_name = \
|
||||
__INIT_KOBJ_ATTR(_name, 0200, NULL, _store)
|
||||
|
||||
#define TMPFS_ATTR_RW(_name, _show, _store) \
|
||||
static struct kobj_attribute tmpfs_attr_##_name = \
|
||||
__INIT_KOBJ_ATTR(_name, 0644, _show, _store)
|
||||
|
||||
#define TMPFS_ATTR_RO(_name, _show) \
|
||||
static struct kobj_attribute tmpfs_attr_##_name = \
|
||||
__INIT_KOBJ_ATTR(_name, 0444, _show, NULL)
|
||||
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
static ssize_t casefold_show(struct kobject *kobj, struct kobj_attribute *a,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "supported\n");
|
||||
}
|
||||
TMPFS_ATTR_RO(casefold, casefold_show);
|
||||
#endif
|
||||
|
||||
static struct attribute *tmpfs_attributes[] = {
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
&tmpfs_attr_casefold.attr,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group tmpfs_attribute_group = {
|
||||
.attrs = tmpfs_attributes,
|
||||
.name = "features"
|
||||
};
|
||||
|
||||
static struct kobject *tmpfs_kobj;
|
||||
|
||||
static int __init tmpfs_sysfs_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
tmpfs_kobj = kobject_create_and_add("tmpfs", fs_kobj);
|
||||
if (!tmpfs_kobj)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = sysfs_create_group(tmpfs_kobj, &tmpfs_attribute_group);
|
||||
if (ret)
|
||||
kobject_put(tmpfs_kobj);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_SYSFS && CONFIG_TMPFS */
|
||||
|
||||
void __init shmem_init(void)
|
||||
{
|
||||
int error;
|
||||
@ -4969,6 +5206,14 @@ void __init shmem_init(void)
|
||||
goto out1;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SYSFS) && defined(CONFIG_TMPFS)
|
||||
error = tmpfs_sysfs_init();
|
||||
if (error) {
|
||||
pr_err("Could not init tmpfs sysfs\n");
|
||||
goto out1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
||||
if (has_transparent_hugepage() && shmem_huge > SHMEM_HUGE_DENY)
|
||||
SHMEM_SB(shm_mnt->mnt_sb)->huge = shmem_huge;
|
||||
|
Loading…
Reference in New Issue
Block a user