diff --git a/fs/Kconfig b/fs/Kconfig index b95f212be39e31501f342b42bdada3d3b1f4b1df..25e1a90d64912fb59a8b3e4a0c1064614eceaee3 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -346,6 +346,7 @@ endif # NETWORK_FILESYSTEMS source "fs/nls/Kconfig" source "fs/dlm/Kconfig" source "fs/unicode/Kconfig" +source "fs/epfs/Kconfig" config IO_WQ bool diff --git a/fs/Makefile b/fs/Makefile index d71954aaba20e3adf2e640c5f91549605d71af69..3bdbd67b67d527659114fb924d1a430e9459763a 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -137,3 +137,4 @@ obj-$(CONFIG_EFIVAR_FS) += efivarfs/ obj-$(CONFIG_EROFS_FS) += erofs/ obj-$(CONFIG_VBOXSF_FS) += vboxsf/ obj-$(CONFIG_ZONEFS_FS) += zonefs/ +obj-$(CONFIG_EPFS) += epfs/ diff --git a/fs/epfs/Kconfig b/fs/epfs/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..059c3a0cc10d40f8cc99efd7bf39f1b9ea771615 --- /dev/null +++ b/fs/epfs/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +config EPFS + tristate "Enhanced Proxy File System support" + depends on TMPFS + help + Enhanced Proxy File System support. If unsure, say N. + +config EPFS_DEBUG + tristate "Debug message of Enhanced Proxy File System" + depends on EPFS + help + Enhanced Proxy File System debug support. diff --git a/fs/epfs/Makefile b/fs/epfs/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b7375e6f9ee0d168f5ac153bdc944981c322382b --- /dev/null +++ b/fs/epfs/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_EPFS) += epfs.o +epfs-y := main.o super.o dentry.o inode.o file.o dir.o diff --git a/fs/epfs/dentry.c b/fs/epfs/dentry.c new file mode 100644 index 0000000000000000000000000000000000000000..62299eccd4ef5a2674f39bd7e9bae402342c074e --- /dev/null +++ b/fs/epfs/dentry.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fs/epfs/main.c + * + * Copyright (c) 2022 Huawei Technologies Co., Ltd. + * Author: weilongping@huawei.com + * Create: 2022-06-10 + */ +#include "internal.h" + +static int epfs_d_revalidate(struct dentry *dentry, unsigned int flags) +{ + return 1; +} + +static void epfs_d_release(struct dentry *dentry) +{ +} + +const struct dentry_operations epfs_dops = { + .d_revalidate = epfs_d_revalidate, + .d_release = epfs_d_release, +}; diff --git a/fs/epfs/dir.c b/fs/epfs/dir.c new file mode 100644 index 0000000000000000000000000000000000000000..875057a865179c146fb0a6643043d94a3b9cbc15 --- /dev/null +++ b/fs/epfs/dir.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fs/epfs/dir.c + * + * Copyright (c) 2022 Huawei Technologies Co., Ltd. + * Author: weilongping@huawei.com + * Create: 2022-06-10 + */ +#include + +#include "internal.h" + +static int epfs_iterate(struct file *file, struct dir_context *ctx) +{ + return 0; +} + +const struct file_operations epfs_dir_fops = { .iterate = epfs_iterate }; diff --git a/fs/epfs/epfs.h b/fs/epfs/epfs.h new file mode 100644 index 0000000000000000000000000000000000000000..19e66e145d1ab14df2660a5f5738c587b0f20503 --- /dev/null +++ b/fs/epfs/epfs.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fs/epfs/epfs.h + * + * Copyright (c) 2022 Huawei Technologies Co., Ltd. + * Author: weilongping@huawei.com + * Create: 2022-06-10 + */ +#ifndef __FS_EPFS_H__ +#define __FS_EPFS_H__ + +#include +#include +#include + +#define EPFS_MAX_RANGES 127 + +struct __attribute__((__packed__)) epfs_range { + __u64 num; + __u64 reserved; + struct { + __u64 begin; + __u64 end; + } range[0]; +}; + +#define EPFS_IOCTL_MAGIC 0x71 +#define IOC_SET_ORIGIN_FD _IOW(EPFS_IOCTL_MAGIC, 1, __s32) +#define IOC_SET_EPFS_RANGE _IOW(EPFS_IOCTL_MAGIC, 2, struct epfs_range) +#define EPFS_IOCTL_MAXNR 3 + +#define EPFS_TAG "Epfs" + +#define epfs_err(fmt, ...) \ + pr_err("%s:%s:%d: " fmt, EPFS_TAG, __func__, __LINE__, ##__VA_ARGS__) +#define epfs_info(fmt, ...) \ + pr_info("%s:%s:%d: " fmt, EPFS_TAG, __func__, __LINE__, ##__VA_ARGS__) +#define epfs_warn(fmt, ...) \ + pr_warn("%s:%s:%d: " fmt, EPFS_TAG, __func__, __LINE__, ##__VA_ARGS__) +#define epfs_debug(fmt, ...) \ + pr_debug("%s:%s:%d: " fmt, EPFS_TAG, __func__, __LINE__, ##__VA_ARGS__) + +#endif // __FS_EPFS_H__ diff --git a/fs/epfs/file.c b/fs/epfs/file.c new file mode 100644 index 0000000000000000000000000000000000000000..5b236150f8b40ed129fcaf9123825f9b80b4fe0c --- /dev/null +++ b/fs/epfs/file.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fs/epfs/file.c + * + * Copyright (c) 2022 Huawei Technologies Co., Ltd. + * Author: weilongping@huawei.com + * Create: 2022-06-10 + */ +#include +#include +#include +#include +#include + +#include "internal.h" + +long epfs_set_origin_fd(struct file *file, unsigned long arg) +{ + int fd = -1; + struct file *origin_file; + struct inode *inode = file->f_inode; + struct epfs_inode_info *info = epfs_inode_to_private(inode); + int ret = 0; + + if (copy_from_user(&fd, (int *)arg, sizeof(fd))) + return -EFAULT; + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) + epfs_debug("original fd: %d", fd); + origin_file = fget(fd); + if (!origin_file) { + epfs_err("Original file not exist!"); + return -EBADF; + } + + mutex_lock(&info->lock); + if (info->origin_file) { + // origin_file had been set. + ret = -EEXIST; + fput(origin_file); + } else if (file_inode(origin_file) == inode) { + epfs_err("Could not set itself as origin_file!"); + fput(origin_file); + ret = -EINVAL; + } else { + info->origin_file = origin_file; + fsstack_copy_attr_all(inode, file_inode(origin_file)); + fsstack_copy_inode_size(inode, file_inode(origin_file)); + } + mutex_unlock(&info->lock); + return ret; +} + +int check_range(struct epfs_range *range) +{ + __u64 index; + + if (range->range[0].begin >= range->range[0].end) { + epfs_err("Invalid range: [%llu, %llu)", range->range[0].begin, + range->range[0].end); + return -EINVAL; + } + + for (index = 1; index < range->num; index++) { + if ((range->range[index].begin >= range->range[index].end) || + (range->range[index].begin < range->range[index - 1].end)) { + epfs_err("Invalid range: [%llu, %llu), [%llu, %llu)", + range->range[index - 1].begin, + range->range[index - 1].end, + range->range[index].begin, + range->range[index].end); + return -EINVAL; + } + } + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) { + epfs_debug("epfs_range recv %llu ranges:", range->num); + for (index = 0; index < range->num; index++) { + epfs_debug("range:[%llu %llu)", + range->range[index].begin, + range->range[index].end); + } + epfs_debug("\n"); + } + return 0; +} + +long epfs_set_range(struct file *file, unsigned long arg) +{ + struct inode *inode = file->f_inode; + struct inode *origin_inode; + struct epfs_inode_info *info = epfs_inode_to_private(inode); + int ret = 0; + struct epfs_range *range; + struct epfs_range header; + + mutex_lock(&info->lock); + if (!info->origin_file) { + epfs_err("origin file not exist!"); + ret = -EBADF; + goto out_set_range; + } + origin_inode = info->origin_file->f_inode; + if (!in_group_p(origin_inode->i_gid)) { + epfs_err("Only group member can set range: %u", + i_gid_read(origin_inode)); + ret = -EACCES; + goto out_set_range; + } + + if (copy_from_user(&header, (struct epfs_range *)arg, + sizeof(header))) { + ret = -EFAULT; + epfs_err("get header failed!"); + goto out_set_range; + } + + if (header.num > EPFS_MAX_RANGES || header.num == 0) { + ret = -EINVAL; + epfs_err("illegal num: %llu", header.num); + goto out_set_range; + } + + range = kzalloc(sizeof(header) + sizeof(header.range[0]) * header.num, + GFP_KERNEL); + if (!range) { + ret = -ENOMEM; + goto out_set_range; + } + + if (copy_from_user(range, (struct epfs_range *)arg, + sizeof(header) + sizeof(header.range[0]) * header.num)) { + ret = -EFAULT; + epfs_err("Failed to get range! num: %llu", header.num); + kfree(range); + goto out_set_range; + } + + ret = check_range(range); + if (ret) { + kfree(range); + goto out_set_range; + } + + info->range = range; +out_set_range: + mutex_unlock(&info->lock); + return ret; +} + +static long __epfs_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long rc = -ENOTTY; + + if (unlikely(_IOC_TYPE(cmd) != EPFS_IOCTL_MAGIC)) { + epfs_err("Failed to check epfs magic: %u", _IOC_TYPE(cmd)); + return -ENOTTY; + } + if (unlikely(_IOC_NR(cmd) >= EPFS_IOCTL_MAXNR)) { + epfs_err("Failed to check ioctl number: %u", _IOC_NR(cmd)); + return -ENOTTY; + } + if (unlikely(!access_ok((void __user *)arg, _IOC_SIZE(cmd)))) { + epfs_err("Failed to check user address space range!"); + return -EFAULT; + } + + switch (cmd) { + case IOC_SET_ORIGIN_FD: + return epfs_set_origin_fd(file, arg); + case IOC_SET_EPFS_RANGE: + return epfs_set_range(file, arg); + default: + epfs_info("Exit epfs unsupported ioctl, ret: %ld", rc); + return rc; + } +} + +static long epfs_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return __epfs_ioctl(file, cmd, arg); +} + +static long epfs_unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return __epfs_ioctl(file, cmd, arg); +} + +static ssize_t epfs_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct inode *inode = file_inode(file); + struct epfs_inode_info *info = epfs_inode_to_private(inode); + struct file *origin_file; + struct epfs_range *range; + ssize_t ret = 0; + loff_t pos = *ppos; + loff_t file_size; + int current_range_index = 0; + + mutex_lock(&info->lock); + range = info->range; + if (!range) { + ret = -EINVAL; + epfs_err("Invalid inode range!"); + goto out_read; + } + + origin_file = info->origin_file; + + if (!origin_file) { + ret = -ENOENT; + epfs_err("origin file not exist!"); + goto out_read; + } + + // Reduce count when it will read over file size. + file_size = i_size_read(file_inode(origin_file)); + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) + if (count > (file_size - pos)) + epfs_debug( + "count will be truncated to %llu, as file_size=%llu, pos=%llu", + file_size - pos, file_size, pos); + count = count <= (file_size - pos) ? count : (file_size - pos); + + // Skip ranges before pos. + while ((range->range[current_range_index].end <= pos) && + (current_range_index < range->num)) + current_range_index++; + + while (count > 0) { + __u64 current_begin, current_end; + + if (current_range_index >= range->num) { + // read directly when epfs range gone; + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) + epfs_debug( + "read from %llu with len %lu at the end.", + pos, count); + ret = vfs_read(origin_file, buf, count, &pos); + break; + } + current_begin = range->range[current_range_index].begin; + current_end = range->range[current_range_index].end; + if (current_begin <= pos) { + // Clear user memory + unsigned long clear_len = current_end - pos; + + clear_len = clear_len < count ? clear_len : count; + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) + epfs_debug( + "clear user memory from %llu with len %lu", + pos, clear_len); + if (clear_user(buf, clear_len)) { + ret = EFAULT; + break; + } + buf += clear_len; + pos += clear_len; + count -= clear_len; + current_range_index++; + } else { + // Read from pos to (next)current_begin + unsigned long read_len = current_begin - pos; + + read_len = read_len < count ? read_len : count; + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) + epfs_debug( + "read from %llu with len %lu", + pos, read_len); + ret = vfs_read(origin_file, buf, read_len, &pos); + if (ret < 0 || ret < read_len) { + // Could not read enough bytes; + break; + } + buf += ret; + count -= ret; + } + } + + if (ret >= 0) { + ret = pos - *ppos; + *ppos = pos; + } +out_read: + mutex_unlock(&info->lock); + return ret; +} + +const struct file_operations epfs_file_fops = { + .unlocked_ioctl = epfs_unlocked_ioctl, + .compat_ioctl = epfs_compat_ioctl, + .read = epfs_read, + .llseek = generic_file_llseek, +}; diff --git a/fs/epfs/inode.c b/fs/epfs/inode.c new file mode 100644 index 0000000000000000000000000000000000000000..3510fdf7b467357e82087aaedebf190ce9d492d5 --- /dev/null +++ b/fs/epfs/inode.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fs/epfs/inode.c + * + * Copyright (c) 2022 Huawei Technologies Co., Ltd. + * Author: weilongping@huawei.com + * Create: 2022-06-10 + */ +#include +#include +#include + +#include "internal.h" + +#define USER_DATA_RW 1008 +#define USER_DATA_RW_UID KUIDT_INIT(USER_DATA_RW) +#define USER_DATA_RW_GID KGIDT_INIT(USER_DATA_RW) + +struct dentry *epfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + return ERR_PTR(-ENOENT); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +static int epfs_tmpfile(struct user_namespace *, struct inode *dir, + struct dentry *dentry, umode_t mode) +#else +static int epfs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode) +#endif +{ + struct inode *inode = epfs_iget(dir->i_sb, false); + + if (!inode) + return -ENOSPC; + d_tmpfile(dentry, inode); + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) + epfs_debug("epfs: tmpfile %p", inode); + return 0; +} + +const struct inode_operations epfs_dir_iops = { + .tmpfile = epfs_tmpfile, + .lookup = epfs_lookup, +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +static int epfs_getattr(struct user_namespace *mnt_userns, + const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int flags) +#else +static int epfs_getattr(const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int flags) +#endif +{ + struct dentry *dentry = path->dentry; + struct inode *inode = d_inode(dentry); + struct epfs_inode_info *info = epfs_inode_to_private(inode); + struct file *origin_file; + struct kstat origin_stat; + int ret; + + mutex_lock(&info->lock); + origin_file = info->origin_file; + if (!origin_file) { + ret = -ENOENT; + goto out_getattr; + } + ret = vfs_getattr(&(origin_file->f_path), &origin_stat, request_mask, + flags); + if (ret) + goto out_getattr; + fsstack_copy_attr_all(inode, file_inode(origin_file)); + fsstack_copy_inode_size(inode, file_inode(origin_file)); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + generic_fillattr(mnt_userns, d_inode(dentry), stat); +#else + generic_fillattr(d_inode(dentry), stat); +#endif + stat->blocks = origin_stat.blocks; + +out_getattr: + mutex_unlock(&info->lock); + return ret; +} + +const struct inode_operations epfs_file_iops = { + .getattr = epfs_getattr, +}; + +struct inode *epfs_iget(struct super_block *sb, bool is_dir) +{ + struct inode *inode = new_inode(sb); + + if (!inode) { + epfs_err("Failed to allocate new inode"); + return NULL; + } + if (is_dir) { + inode->i_op = &epfs_dir_iops; + inode->i_fop = &epfs_dir_fops; + inode->i_mode = S_IFDIR | 0770; + } else { + inode->i_op = &epfs_file_iops; + inode->i_fop = &epfs_file_fops; + inode->i_mode = S_IFREG; + } + inode->i_uid = USER_DATA_RW_UID; + inode->i_gid = USER_DATA_RW_GID; + return inode; +} diff --git a/fs/epfs/internal.h b/fs/epfs/internal.h new file mode 100644 index 0000000000000000000000000000000000000000..9895ffbc015e59c3dc2fb228790ffe5c1b1d6639 --- /dev/null +++ b/fs/epfs/internal.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fs/epfs/internal.h + * + * Copyright (c) 2022 Huawei Technologies Co., Ltd. + * Author: weilongping@huawei.com + * Create: 2022-06-10 + */ +#ifndef __FS_EPFS_INTERNAL_H__ +#define __FS_EPFS_INTERNAL_H__ + +#include +#include +#include + +#include "epfs.h" + +#define EPFS_SUPER_MAGIC 0x20220607 + +struct epfs_inode_info { + struct inode vfs_inode; + struct file *origin_file; + struct epfs_range *range; + struct mutex lock; +}; + +static inline struct epfs_inode_info *epfs_inode_to_private(struct inode *inode) +{ + return container_of(inode, struct epfs_inode_info, vfs_inode); +} + +struct inode *epfs_iget(struct super_block *sb, bool is_dir); +extern const struct dentry_operations epfs_dops; +extern const struct file_operations epfs_dir_fops; +extern const struct file_operations epfs_file_fops; +extern struct file_system_type epfs_fs_type; +extern struct kmem_cache *epfs_inode_cachep; + +#endif // __FS_EPFS_INTERNAL_H__ diff --git a/fs/epfs/main.c b/fs/epfs/main.c new file mode 100644 index 0000000000000000000000000000000000000000..c91e94f8f15e469495230eaf9b2d9b332847ea19 --- /dev/null +++ b/fs/epfs/main.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fs/epfs/main.c + * + * Copyright (c) 2022 Huawei Technologies Co., Ltd. + * Author: weilongping@huawei.com + * Create: 2022-06-10 + */ +#include +#include +#include + +#include "internal.h" + +struct kmem_cache *epfs_inode_cachep; + +static int __init epfs_init(void) +{ + int ret; + + epfs_inode_cachep = + kmem_cache_create("epfs_inode_cache", + sizeof(struct epfs_inode_info), 0, 0, + NULL); + if (!epfs_inode_cachep) + return -ENOMEM; + ret = register_filesystem(&epfs_fs_type); + if (ret) + kmem_cache_destroy(epfs_inode_cachep); + return ret; +} + +static void __exit epfs_exit(void) +{ + unregister_filesystem(&epfs_fs_type); + kmem_cache_destroy(epfs_inode_cachep); +} + +module_init(epfs_init) +module_exit(epfs_exit) +MODULE_DESCRIPTION("Enhanced Proxy File System for OpenHarmony"); +MODULE_AUTHOR("LongPing Wei weilongping@huawei.com"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS_FS("epfs"); diff --git a/fs/epfs/super.c b/fs/epfs/super.c new file mode 100644 index 0000000000000000000000000000000000000000..4d708f855d1fb14185ce242f1d2a54e7326687e7 --- /dev/null +++ b/fs/epfs/super.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fs/epfs/super.c + * + * Copyright (c) 2022 Huawei Technologies Co., Ltd. + * Author: weilongping@huawei.com + * Create: 2022-06-10 + */ +#include +#include +#include +#include +#include + +#include "internal.h" + +static struct inode *epfs_alloc_inode(struct super_block *sb) +{ + struct epfs_inode_info *info = + kmem_cache_zalloc(epfs_inode_cachep, GFP_KERNEL); + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) + epfs_debug("inode info: %p", info); + inode_init_once(&info->vfs_inode); + mutex_init(&info->lock); + return &info->vfs_inode; +} + +// Free epfs_inode_info +static void epfs_free_inode(struct inode *inode) +{ + if (IS_ENABLED(CONFIG_EPFS_DEBUG)) + epfs_debug("free_inode: %p", inode); + kmem_cache_free(epfs_inode_cachep, + epfs_inode_to_private(inode)); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) +static void i_callback(struct rcu_head *head) +{ + struct inode *inode = container_of(head, struct inode, i_rcu); + + epfs_free_inode(inode); +} +#endif + +// Destroy epfs_range +static void epfs_destroy_inode(struct inode *inode) +{ + struct epfs_inode_info *info = epfs_inode_to_private(inode); + + mutex_lock(&info->lock); + kfree(info->range); + info->range = NULL; + mutex_unlock(&info->lock); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) + call_rcu(&inode->i_rcu, i_callback); +#endif +} + +// Clear vfs_inode +static void epfs_evict_inode(struct inode *inode) +{ + struct epfs_inode_info *info = epfs_inode_to_private(inode); + + clear_inode(inode); + mutex_lock(&info->lock); + if (info->origin_file) { + fput(info->origin_file); + info->origin_file = NULL; + } + mutex_unlock(&info->lock); +} + +static int epfs_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + buf->f_type = EPFS_SUPER_MAGIC; + return 0; +} +struct super_operations epfs_sops = { + .alloc_inode = epfs_alloc_inode, + .destroy_inode = epfs_destroy_inode, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) + .free_inode = epfs_free_inode, +#endif + .evict_inode = epfs_evict_inode, + .statfs = epfs_statfs, +}; + +static int epfs_fill_super(struct super_block *s, void *data, int silent) +{ + struct inode *inode; + + s->s_op = &epfs_sops; + s->s_d_op = &epfs_dops; + s->s_magic = EPFS_SUPER_MAGIC; + inode = epfs_iget(s, true /* dir */); + if (!inode) { + epfs_err("Failed to get root inode!"); + return -ENOMEM; + } + + s->s_root = d_make_root(inode); + if (!s->s_root) { + epfs_err("Failed to make root inode"); + return -ENOMEM; + } + + return 0; +} + +struct dentry *epfs_mount(struct file_system_type *fs_type, int flags, + const char *dev_name, void *raw_data) +{ + return mount_nodev(fs_type, flags, raw_data, epfs_fill_super); +} + +void epfs_kill_sb(struct super_block *sb) +{ + kill_anon_super(sb); +} + +struct file_system_type epfs_fs_type = { + .owner = THIS_MODULE, + .name = "epfs", + .mount = epfs_mount, + .kill_sb = epfs_kill_sb, +};