#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>
static HLIST_HEAD(file_systems);
static DEFINE_SPINLOCK(file_systems_lock);
#ifdef CONFIG_PROC_FS
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
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;
}
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);
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;
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;
}
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;
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;
if (old && old->gen == file_systems_gen) {
spin_unlock(&file_systems_lock);
kfree(new);
return 0;
}
if (gen != file_systems_gen) {
spin_unlock(&file_systems_lock);
kfree(new);
goto retry;
}
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))) {
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);