mirror of
https://github.com/torvalds/linux
synced 2026-07-22 18:20:51 +09:00
- add missing unlocks in some corner cases - whitespace touch ups - s/smp_store_release/rcu_assign_pointer/ [nop, the macro expands to the same thing] - mark file_systems_string as __read_mostly Signed-off-by: Mateusz Guzik <mjguzik@gmail.com> Link: https://patch.msgid.link/20260610143954.34185-1-mjguzik@gmail.com Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
414 lines
9.5 KiB
C
414 lines
9.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/filesystems.c
|
|
*
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*
|
|
* table of configured filesystems
|
|
*/
|
|
|
|
#include <linux/syscalls.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/kmod.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/fs_parser.h>
|
|
#include <linux/rculist.h>
|
|
|
|
/*
|
|
* Read-mostly filesystem drivers list.
|
|
*
|
|
* Readers walk under rcu_read_lock(); writers take file_systems_lock
|
|
* and publish via _rcu hlist primitives. unregister_filesystem()
|
|
* synchronize_rcu()s after unlock so the embedded file_system_type
|
|
* can't go away under a reader. To keep using a filesystem after
|
|
* the RCU section ends, take a module reference via try_module_get().
|
|
*/
|
|
static HLIST_HEAD(file_systems);
|
|
static DEFINE_SPINLOCK(file_systems_lock);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
/*
|
|
* Cache a stringified version of the filesystem list.
|
|
*
|
|
* The fs list gets queried a lot by userspace because of libselinux, including
|
|
* rather surprising programs (would you guess *sed* is on the list?). In order
|
|
* to reduce the overhead we cache the resulting string, which normally hangs
|
|
* around below 512 bytes in size.
|
|
*
|
|
* As the list almost never changes, its creation is not particularly optimized
|
|
* to keep things simple.
|
|
*
|
|
* We sort it out on read in order to not introduce a failure point for fs
|
|
* registration (in principle we may be unable to alloc memory for the list).
|
|
*/
|
|
struct file_systems_string {
|
|
struct rcu_head rcu;
|
|
unsigned long gen;
|
|
size_t len;
|
|
char string[];
|
|
};
|
|
|
|
static unsigned long file_systems_gen;
|
|
static struct file_systems_string __read_mostly __rcu *file_systems_string;
|
|
|
|
static void invalidate_filesystems_string(void);
|
|
#else
|
|
static inline void invalidate_filesystems_string(void) { }
|
|
#endif
|
|
|
|
/* WARNING: This can be used only if we _already_ own a reference */
|
|
struct file_system_type *get_filesystem(struct file_system_type *fs)
|
|
{
|
|
__module_get(fs->owner);
|
|
return fs;
|
|
}
|
|
|
|
void put_filesystem(struct file_system_type *fs)
|
|
{
|
|
module_put(fs->owner);
|
|
}
|
|
|
|
static struct file_system_type *find_filesystem(const char *name, unsigned len)
|
|
{
|
|
struct file_system_type *fs;
|
|
|
|
hlist_for_each_entry_rcu(fs, &file_systems, list,
|
|
lockdep_is_held(&file_systems_lock))
|
|
if (strncmp(fs->name, name, len) == 0 && !fs->name[len])
|
|
return fs;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* register_filesystem - register a new filesystem
|
|
* @fs: the file system structure
|
|
*
|
|
* Adds the file system passed to the list of file systems the kernel
|
|
* is aware of for mount and other syscalls. Returns 0 on success,
|
|
* or a negative errno code on an error.
|
|
*
|
|
* The &struct file_system_type that is passed is linked into the kernel
|
|
* structures and must not be freed until the file system has been
|
|
* unregistered.
|
|
*/
|
|
int register_filesystem(struct file_system_type *fs)
|
|
{
|
|
if (fs->parameters &&
|
|
!fs_validate_description(fs->name, fs->parameters))
|
|
return -EINVAL;
|
|
|
|
BUG_ON(strchr(fs->name, '.'));
|
|
if (!hlist_unhashed_lockless(&fs->list))
|
|
return -EBUSY;
|
|
|
|
guard(spinlock)(&file_systems_lock);
|
|
if (find_filesystem(fs->name, strlen(fs->name)))
|
|
return -EBUSY;
|
|
hlist_add_tail_rcu(&fs->list, &file_systems);
|
|
invalidate_filesystems_string();
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(register_filesystem);
|
|
|
|
/**
|
|
* unregister_filesystem - unregister a file system
|
|
* @fs: filesystem to unregister
|
|
*
|
|
* Remove a file system that was previously successfully registered
|
|
* with the kernel. An error is returned if the file system is not found.
|
|
* Zero is returned on a success.
|
|
*
|
|
* Once this function has returned the &struct file_system_type structure
|
|
* may be freed or reused.
|
|
*/
|
|
int unregister_filesystem(struct file_system_type *fs)
|
|
{
|
|
scoped_guard(spinlock, &file_systems_lock) {
|
|
if (hlist_unhashed(&fs->list))
|
|
return -EINVAL;
|
|
hlist_del_init_rcu(&fs->list);
|
|
invalidate_filesystems_string();
|
|
}
|
|
synchronize_rcu();
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(unregister_filesystem);
|
|
|
|
#ifdef CONFIG_SYSFS_SYSCALL
|
|
static int fs_index(const char __user *__name)
|
|
{
|
|
struct file_system_type *p;
|
|
char *name __free(kfree) = strndup_user(__name, PATH_MAX);
|
|
int index = 0;
|
|
|
|
if (IS_ERR(name))
|
|
return PTR_ERR(name);
|
|
|
|
guard(rcu)();
|
|
hlist_for_each_entry_rcu(p, &file_systems, list) {
|
|
if (strcmp(p->name, name) == 0)
|
|
return index;
|
|
index++;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int fs_name(unsigned int index, char __user *buf)
|
|
{
|
|
struct file_system_type *p, *found = NULL;
|
|
int len, res;
|
|
|
|
scoped_guard(rcu) {
|
|
hlist_for_each_entry_rcu(p, &file_systems, list) {
|
|
if (index--)
|
|
continue;
|
|
if (try_module_get(p->owner))
|
|
found = p;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
return -EINVAL;
|
|
|
|
/* OK, we got the reference, so we can safely block */
|
|
len = strlen(found->name) + 1;
|
|
res = copy_to_user(buf, found->name, len) ? -EFAULT : 0;
|
|
put_filesystem(found);
|
|
return res;
|
|
}
|
|
|
|
static int fs_maxindex(void)
|
|
{
|
|
struct file_system_type *p;
|
|
int index = 0;
|
|
|
|
guard(rcu)();
|
|
hlist_for_each_entry_rcu(p, &file_systems, list)
|
|
index++;
|
|
return index;
|
|
}
|
|
|
|
/*
|
|
* Whee.. Weird sysv syscall.
|
|
*/
|
|
SYSCALL_DEFINE3(sysfs, int, option, unsigned long, arg1, unsigned long, arg2)
|
|
{
|
|
int retval = -EINVAL;
|
|
|
|
switch (option) {
|
|
case 1:
|
|
retval = fs_index((const char __user *) arg1);
|
|
break;
|
|
|
|
case 2:
|
|
retval = fs_name(arg1, (char __user *) arg2);
|
|
break;
|
|
|
|
case 3:
|
|
retval = fs_maxindex();
|
|
break;
|
|
}
|
|
return retval;
|
|
}
|
|
#endif
|
|
|
|
int __init list_bdev_fs_names(char *buf, size_t size)
|
|
{
|
|
struct file_system_type *p;
|
|
size_t len;
|
|
int count = 0;
|
|
|
|
guard(rcu)();
|
|
hlist_for_each_entry_rcu(p, &file_systems, list) {
|
|
if (!(p->fs_flags & FS_REQUIRES_DEV))
|
|
continue;
|
|
len = strlen(p->name) + 1;
|
|
if (len > size) {
|
|
pr_warn("%s: truncating file system list\n", __func__);
|
|
break;
|
|
}
|
|
memcpy(buf, p->name, len);
|
|
buf += len;
|
|
size -= len;
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static void invalidate_filesystems_string(void)
|
|
{
|
|
struct file_systems_string *old;
|
|
|
|
lockdep_assert_held_write(&file_systems_lock);
|
|
file_systems_gen++;
|
|
old = rcu_replace_pointer(file_systems_string, NULL,
|
|
lockdep_is_held(&file_systems_lock));
|
|
if (old)
|
|
kfree_rcu(old, rcu);
|
|
}
|
|
|
|
static __cold noinline int regen_filesystems_string(void)
|
|
{
|
|
struct file_system_type *p;
|
|
struct file_systems_string *old, *new;
|
|
size_t newlen, usedlen;
|
|
unsigned long gen;
|
|
|
|
retry:
|
|
newlen = 0;
|
|
|
|
/* pre-calc space for each fs */
|
|
spin_lock(&file_systems_lock);
|
|
gen = file_systems_gen;
|
|
hlist_for_each_entry_rcu(p, &file_systems, list) {
|
|
if (!(p->fs_flags & FS_REQUIRES_DEV))
|
|
newlen += strlen("nodev");
|
|
newlen += strlen("\t") + strlen(p->name) + strlen("\n");
|
|
}
|
|
spin_unlock(&file_systems_lock);
|
|
|
|
new = kmalloc(offsetof(struct file_systems_string, string) + newlen + 1,
|
|
GFP_KERNEL);
|
|
if (!new)
|
|
return -ENOMEM;
|
|
|
|
new->gen = gen;
|
|
new->len = newlen;
|
|
new->string[newlen] = '\0';
|
|
|
|
spin_lock(&file_systems_lock);
|
|
old = file_systems_string;
|
|
|
|
/*
|
|
* Did someone beat us to it?
|
|
*/
|
|
if (old && old->gen == file_systems_gen) {
|
|
spin_unlock(&file_systems_lock);
|
|
kfree(new);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Did the list change in the meantime?
|
|
*/
|
|
if (gen != file_systems_gen) {
|
|
spin_unlock(&file_systems_lock);
|
|
kfree(new);
|
|
goto retry;
|
|
}
|
|
|
|
/*
|
|
* Populate the string.
|
|
*
|
|
* We know we have just enough space because we calculated the right
|
|
* size the previous time we had the lock and confirmed the list has
|
|
* not changed after reacquiring it.
|
|
*/
|
|
usedlen = 0;
|
|
hlist_for_each_entry_rcu(p, &file_systems, list) {
|
|
usedlen += sprintf(&new->string[usedlen], "%s\t%s\n",
|
|
(p->fs_flags & FS_REQUIRES_DEV) ? "" : "nodev",
|
|
p->name);
|
|
}
|
|
|
|
if (WARN_ON_ONCE(new->len != strlen(new->string))) {
|
|
/*
|
|
* Should never happen of course, keep this in case someone changes string
|
|
* generation above and messes it up.
|
|
*/
|
|
spin_unlock(&file_systems_lock);
|
|
kfree(new);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rcu_assign_pointer(file_systems_string, new);
|
|
spin_unlock(&file_systems_lock);
|
|
if (old)
|
|
kfree_rcu(old, rcu);
|
|
return 0;
|
|
}
|
|
|
|
static __cold noinline int filesystems_proc_show_fallback(struct seq_file *m, void *v)
|
|
{
|
|
struct file_system_type *p;
|
|
|
|
guard(rcu)();
|
|
hlist_for_each_entry_rcu(p, &file_systems, list) {
|
|
seq_printf(m, "%s\t%s\n",
|
|
(p->fs_flags & FS_REQUIRES_DEV) ? "" : "nodev",
|
|
p->name);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int filesystems_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
struct file_systems_string *fss;
|
|
|
|
for (;;) {
|
|
scoped_guard(rcu) {
|
|
fss = rcu_dereference(file_systems_string);
|
|
if (likely(fss)) {
|
|
seq_write(m, fss->string, fss->len);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int err = regen_filesystems_string();
|
|
if (unlikely(err))
|
|
return filesystems_proc_show_fallback(m, v);
|
|
}
|
|
}
|
|
|
|
static int __init proc_filesystems_init(void)
|
|
{
|
|
struct proc_dir_entry *pde;
|
|
|
|
pde = proc_create_single("filesystems", 0, NULL, filesystems_proc_show);
|
|
if (!pde)
|
|
return -ENOMEM;
|
|
proc_make_permanent(pde);
|
|
return 0;
|
|
}
|
|
module_init(proc_filesystems_init);
|
|
#endif
|
|
|
|
static struct file_system_type *__get_fs_type(const char *name, int len)
|
|
{
|
|
struct file_system_type *fs;
|
|
|
|
guard(rcu)();
|
|
fs = find_filesystem(name, len);
|
|
if (fs && !try_module_get(fs->owner))
|
|
fs = NULL;
|
|
return fs;
|
|
}
|
|
|
|
struct file_system_type *get_fs_type(const char *name)
|
|
{
|
|
struct file_system_type *fs;
|
|
const char *dot = strchr(name, '.');
|
|
int len = dot ? dot - name : strlen(name);
|
|
|
|
fs = __get_fs_type(name, len);
|
|
if (!fs && (request_module("fs-%.*s", len, name) == 0)) {
|
|
fs = __get_fs_type(name, len);
|
|
if (!fs)
|
|
pr_warn_once("request_module fs-%.*s succeeded, but still no fs?\n",
|
|
len, name);
|
|
}
|
|
|
|
if (dot && fs && !(fs->fs_flags & FS_HAS_SUBTYPE)) {
|
|
put_filesystem(fs);
|
|
fs = NULL;
|
|
}
|
|
return fs;
|
|
}
|
|
EXPORT_SYMBOL(get_fs_type);
|