diff --git a/fs/sharefs/Kconfig b/fs/sharefs/Kconfig new file mode 100755 index 0000000000000000000000000000000000000000..588192e2682c85994fd3ebf427773a1aa3c4c998 --- /dev/null +++ b/fs/sharefs/Kconfig @@ -0,0 +1,24 @@ +config SHARE_FS + tristate "SHAREFS filesystem support" + help + SHAREFS is an overlay file system.SHAREFS is used for file sharing + between applications. Sharefs manages permissions through different + permissions for reading and writing directories. + +config SHAREFS_SUPPORT_OVERRIDE + bool "Sharefs: support override " + depends on SHARE_FS + default n + help + This is the switch of override feature on sharefs file system. + If the device type is 2in1, it shoule be set y. + +config SHAREFS_SUPPORT_WRITE + bool "Sharefs: support write operations" + depends on SHARE_FS + depends on SHAREFS_SUPPORT_OVERRIDE + default n + help + This is the switch of write operation on sharefs file system. + If the device type is 2in1 and writing files is needed, + it shoule be set y. diff --git a/fs/sharefs/Makefile b/fs/sharefs/Makefile new file mode 100755 index 0000000000000000000000000000000000000000..9b84e26d1cf6cce309297568ecaf32a4cb38bd58 --- /dev/null +++ b/fs/sharefs/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_SHARE_FS) += sharefs.o +ccflags-y += -I$(src) + +sharefs-y := dentry.o file.o inode.o main.o super.o lookup.o authentication.o config.o +ccflags-y += -I$(src) -Werror -Wall +export CONFIG_SHARE_FS := m +KDIR ::= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean \ No newline at end of file diff --git a/fs/sharefs/authentication.c b/fs/sharefs/authentication.c new file mode 100755 index 0000000000000000000000000000000000000000..39997c6324918df75dec0809fcf4b3e4e86401cb --- /dev/null +++ b/fs/sharefs/authentication.c @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fs/sharefs/authentication.c + * + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ +#include "authentication.h" + +static inline __u16 perm_get_next_level(__u16 perm) +{ + __u16 level = (perm & SHAREFS_PERM_MASK) + 1; + + if (level <= SHAREFS_PERM_OTHER) + return level; + else + return SHAREFS_PERM_OTHER; +} + +void fixup_perm_from_level(struct inode *dir, struct dentry *dentry) +{ + struct sharefs_inode_info *hii = SHAREFS_I(dir); + struct inode *dinode = d_inode(dentry); + struct sharefs_inode_info *dinfo = SHAREFS_I(dinode); + const unsigned char* cur_name = dentry->d_name.name; + __u16 level = perm_get_next_level(hii->perm); + __u16 perm = 0; + int bid = 0; + + if (IS_ERR_OR_NULL(dinode)) + return; + dinode->i_uid = dir->i_uid; + dinode->i_gid = dir->i_gid; + switch (level) { + case SHAREFS_PERM_MNT: + bid = get_bundle_uid(SHAREFS_SB(dentry->d_sb), + dentry->d_name.name); + perm = level; + if (bid != 0) { + dinode->i_uid = KUIDT_INIT(bid); + dinode->i_gid = KGIDT_INIT(bid); + } else { + dinode->i_uid = ROOT_UID; + dinode->i_gid = ROOT_GID; + } + dinode->i_mode = (dinode->i_mode & S_IFMT) | SHAREFS_PERM_READONLY_DIR; + break; + case SHAREFS_PERM_DFS: + if (!strcmp(cur_name, SHAREFS_READ_DIR)) { + perm = SHAREFS_DIR_TYPE_READONLY | level; + sharefs_set_read_perm(dinode); + } else if (!strcmp(cur_name, SHAREFS_READWRITE_DIR)) { + perm = SHAREFS_DIR_TYPE_READWRITE | level; + sharefs_set_read_write_perm(dinode); + } + break; + case SHAREFS_PERM_OTHER: + if (is_read_only_auth(hii->perm)) { + perm = SHAREFS_DIR_TYPE_READONLY | SHAREFS_PERM_DFS; + sharefs_set_read_perm(dinode); + } else if (is_read_write_auth(hii->perm)) { + perm = SHAREFS_DIR_TYPE_READWRITE | SHAREFS_PERM_DFS; + sharefs_set_read_write_perm(dinode); + } + break; + default: + sharefs_err("sharedfs perm incorrect got default case, level:%u", level); + break; + } + dinfo->perm = perm; +} + +void sharefs_root_inode_perm_init(struct inode *root_inode) +{ + struct sharefs_inode_info *hii = SHAREFS_I(root_inode); + hii->perm = SHAREFS_PERM_FIX; +} + +#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE +const struct cred *sharefs_override_file_fsids(struct inode *dir, __u16 *_perm) +{ + struct cred *cred = NULL; + cred = prepare_creds(); + if (!cred) + return NULL; + + cred->fsuid = dir->i_uid; + cred->fsgid = dir->i_gid; + return override_creds(cred); +} + +void sharefs_revert_fsids(const struct cred *old_cred) +{ + const struct cred *cur_cred; + cur_cred = current->cred; + revert_creds(old_cred); + put_cred(cur_cred); +} +#endif \ No newline at end of file diff --git a/fs/sharefs/authentication.h b/fs/sharefs/authentication.h new file mode 100755 index 0000000000000000000000000000000000000000..bc107b8fdeb9b56dcfc516b77e42df54b7055946 --- /dev/null +++ b/fs/sharefs/authentication.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fs/sharefs/authentication.h + * + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef AUTHENTICATION_H +#define AUTHENTICATION_H + +#include "sharefs.h" + +#define OID_ROOT 0 + +#define SHAREFS_PERM_MASK 0x000F + +#define SHAREFS_PERM_FIX 0 +#define SHAREFS_PERM_MNT 1 +#define SHAREFS_PERM_DFS 2 +#define SHAREFS_PERM_OTHER 3 + +#define SHAREFS_READ_DIR "r" +#define SHAREFS_READWRITE_DIR "rw" + +#define BASE_USER_RANGE 200000 /* offset for uid ranges for each user */ + + +#define SHAREFS_DIR_TYPE_MASK 0x00F0 +#define SHAREFS_DIR_TYPE_READONLY 0x0010 +#define SHAREFS_DIR_TYPE_READWRITE 0x0020 + +#define SHAREFS_PERM_READONLY_DIR 00550 +#define SHAREFS_PERM_READONLY_FILE 00440 +#define SHAREFS_PERM_READWRITE_DIR 00550 +#define SHAREFS_PERM_READWRITE_FILE 00660 + +extern int get_bid_config(const char *bname); +extern int __init sharefs_init_configfs(void); +extern void sharefs_exit_configfs(void); + +void sharefs_root_inode_perm_init(struct inode *root_inode); +void fixup_perm_from_level(struct inode *dir, struct dentry *dentry); +#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE +const struct cred *sharefs_override_file_fsids(struct inode *dir, + __u16 *_perm); +void sharefs_revert_fsids(const struct cred *old_cred); +#endif + +static inline bool is_read_only_auth(__u16 perm) +{ + return (perm & SHAREFS_DIR_TYPE_MASK) == SHAREFS_DIR_TYPE_READONLY; +} + +static inline bool is_read_write_auth(__u16 perm) +{ + return (perm & SHAREFS_DIR_TYPE_MASK) == SHAREFS_DIR_TYPE_READWRITE; +} + +static inline void sharefs_set_read_perm(struct inode *inode) +{ + if (S_ISDIR(inode->i_mode)) + inode->i_mode = (inode->i_mode & S_IFMT) | SHAREFS_PERM_READONLY_DIR; + else + inode->i_mode = (inode->i_mode & S_IFMT) | SHAREFS_PERM_READONLY_FILE; +} + +static inline void sharefs_set_read_write_perm(struct inode *inode) +{ + if (S_ISDIR(inode->i_mode)) + inode->i_mode = (inode->i_mode & S_IFMT) | SHAREFS_PERM_READWRITE_DIR; + else + inode->i_mode = (inode->i_mode & S_IFMT) | SHAREFS_PERM_READWRITE_FILE; +} + +static inline int get_bundle_uid(struct sharefs_sb_info *sbi, const char *bname) +{ + return sbi->user_id * BASE_USER_RANGE + get_bid_config(bname); +} + +#endif //_AUTHENTICATION_H_ diff --git a/fs/sharefs/config.c b/fs/sharefs/config.c new file mode 100755 index 0000000000000000000000000000000000000000..fe30eeeff0f0c5a7e37b1f931c4c0123c1d6d0dd --- /dev/null +++ b/fs/sharefs/config.c @@ -0,0 +1,372 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fs/sharefs/config.c + * + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include "sharefs.h" + +static struct kmem_cache *sharefs_bid_entry_cachep; + +struct sharefs_bid_entry { + struct hlist_node node; + struct qstr str; + int id; +}; + +struct sharefs_config_bitem { + struct config_item item; + struct qstr str; +}; + +static unsigned int make_hash(const char *name, unsigned int len) +{ + unsigned long hash; + + hash = init_name_hash(0); + while (len--) + hash = partial_name_hash(tolower(*name++), hash); + + return end_name_hash(hash); +} + +static struct qstr make_qstr(const char *name) +{ + struct qstr str; + str.name = name; + str.len = strlen(name); + str.hash = make_hash(str.name, str.len); + + return str; +} + +static struct sharefs_bid_entry *alloc_bid_entry(const char *name, int id) +{ + struct sharefs_bid_entry *bid_entry; + char *bid_entry_name; + + bid_entry = kmem_cache_alloc(sharefs_bid_entry_cachep, GFP_KERNEL); + if (!bid_entry) { + bid_entry = ERR_PTR(-ENOMEM); + goto out; + } + + bid_entry_name = kstrdup(name, GFP_KERNEL); + if (!bid_entry_name) { + kmem_cache_free(sharefs_bid_entry_cachep, bid_entry); + bid_entry = ERR_PTR(-ENOMEM); + goto out; + } + + INIT_HLIST_NODE(&bid_entry->node); + bid_entry->str = make_qstr(bid_entry_name); + bid_entry->id = id; +out: + return bid_entry; +} + +static void free_bid_entry(struct sharefs_bid_entry *bid_entry) +{ + if (bid_entry == NULL) + return; + + kfree(bid_entry->str.name); + kmem_cache_free(sharefs_bid_entry_cachep, bid_entry); +} + +static struct sharefs_config_bitem *alloc_bitem(const char *name) +{ + struct sharefs_config_bitem *bitem; + char *bitem_name; + + bitem = kzalloc(sizeof(*bitem), GFP_KERNEL); + if (!bitem) { + bitem = ERR_PTR(-ENOMEM); + goto out; + } + + bitem_name = kstrdup(name, GFP_KERNEL); + if (!bitem_name) { + kfree(bitem); + bitem = ERR_PTR(-ENOMEM); + goto out; + } + + bitem->str = make_qstr(bitem_name); +out: + return bitem; +} + +static void free_bitem(struct sharefs_config_bitem *bitem) +{ + if (bitem == NULL) + return; + + kfree(bitem->str.name); + kfree(bitem); +} + +#define SHAREFS_BUNDLE_ATTRIBUTE(_attr_) \ + \ +static DEFINE_HASHTABLE(sharefs_##_attr_##_hash_table, 4); \ + \ +static DEFINE_MUTEX(sharefs_##_attr_##_hash_mutex); \ + \ +static int query_##_attr_##_hash_entry(struct qstr *str) \ +{ \ + int id = 0; \ + struct sharefs_bid_entry *bid_entry; \ + struct hlist_node *hash_node; \ + \ + mutex_lock(&sharefs_##_attr_##_hash_mutex); \ + hash_for_each_possible_safe(sharefs_##_attr_##_hash_table, \ + bid_entry, hash_node, node, str->hash) { \ + if (qstr_case_eq(str, &bid_entry->str)) { \ + id = bid_entry->id; \ + break; \ + } \ + } \ + mutex_unlock(&sharefs_##_attr_##_hash_mutex); \ + \ + return id; \ +} \ + \ +static int insert_##_attr_##_hash_entry(struct qstr *str, int id) \ +{ \ + int err = 0; \ + struct sharefs_bid_entry *bid_entry; \ + struct hlist_node *hash_node; \ + \ + sharefs_info("insert name = %s", str->name); \ + \ + mutex_lock(&sharefs_##_attr_##_hash_mutex); \ + hash_for_each_possible_safe(sharefs_##_attr_##_hash_table, \ + bid_entry, hash_node, node, str->hash) { \ + if (qstr_case_eq(str, &bid_entry->str)) { \ + bid_entry->id = id; \ + mutex_unlock(&sharefs_##_attr_##_hash_mutex); \ + goto out; \ + } \ + } \ + mutex_unlock(&sharefs_##_attr_##_hash_mutex); \ + \ + bid_entry = alloc_bid_entry(str->name, id); \ + if (IS_ERR(bid_entry)) { \ + err = PTR_ERR(bid_entry); \ + goto out; \ + } \ + \ + hash_add_rcu(sharefs_##_attr_##_hash_table, &bid_entry->node, \ + bid_entry->str.hash); \ +out: \ + return err; \ +} \ + \ +static void remove_##_attr_##_hash_entry(struct qstr *str) \ +{ \ + struct sharefs_bid_entry *bid_entry; \ + struct hlist_node *hash_node; \ + \ + sharefs_info("remove name = %s", str->name); \ + \ + mutex_lock(&sharefs_##_attr_##_hash_mutex); \ + hash_for_each_possible_safe(sharefs_##_attr_##_hash_table, \ + bid_entry, hash_node, node, str->hash) { \ + if (qstr_case_eq(str, &bid_entry->str)) { \ + hash_del_rcu(&bid_entry->node); \ + free_bid_entry(bid_entry); \ + break; \ + } \ + } \ + mutex_unlock(&sharefs_##_attr_##_hash_mutex); \ +} \ + \ +static void clear_##_attr_##_hash_entry(void) \ +{ \ + int index; \ + struct sharefs_bid_entry *bid_entry; \ + struct hlist_node *hash_node; \ + \ + sharefs_info("clear bid entry"); \ + \ + mutex_lock(&sharefs_##_attr_##_hash_mutex); \ + hash_for_each_safe(sharefs_##_attr_##_hash_table, index, \ + hash_node, bid_entry, node) { \ + hash_del_rcu(&bid_entry->node); \ + kfree(bid_entry->str.name); \ + kmem_cache_free(sharefs_bid_entry_cachep, bid_entry); \ + } \ + mutex_unlock(&sharefs_##_attr_##_hash_mutex); \ +} \ + \ +static int sharefs_##_attr_##_get(const char *bname) \ +{ \ + struct qstr str; \ + \ + str = make_qstr(bname); \ + return query_##_attr_##_hash_entry(&str); \ +} \ + \ +static ssize_t sharefs_##_attr_##_show(struct config_item *item, \ + char *page) \ +{ \ + int id; \ + struct sharefs_config_bitem *bitem; \ + \ + sharefs_info("show bundle id"); \ + \ + bitem = container_of(item, struct sharefs_config_bitem, item); \ + id = query_##_attr_##_hash_entry(&bitem->str); \ + \ + return scnprintf(page, PAGE_SIZE, "%u\n", id); \ +} \ + \ +static ssize_t sharefs_##_attr_##_store(struct config_item *item, \ + const char *page, size_t count) \ +{ \ + int id; \ + int err; \ + size_t size; \ + struct sharefs_config_bitem *bitem; \ + \ + sharefs_info("store bundle id"); \ + \ + bitem = container_of(item, struct sharefs_config_bitem, item); \ + \ + if (kstrtouint(page, 10, &id)) { \ + size = -EINVAL; \ + goto out; \ + } \ + \ + err = insert_##_attr_##_hash_entry(&bitem->str, id); \ + if (err) { \ + size = err; \ + goto out; \ + } \ + \ + size = count; \ +out: \ + return size; \ +} \ + \ +static struct configfs_attribute sharefs_##_attr_##_attr = { \ + .ca_name = __stringify(_attr_), \ + .ca_mode = S_IRUGO | S_IWUGO, \ + .ca_owner = THIS_MODULE, \ + .show = sharefs_##_attr_##_show, \ + .store = sharefs_##_attr_##_store, \ +}; + +SHAREFS_BUNDLE_ATTRIBUTE(appid) + +static struct configfs_attribute *sharefs_battrs[] = { + &sharefs_appid_attr, + NULL, +}; + +static void sharefs_config_bitem_release(struct config_item *item) +{ + struct sharefs_config_bitem *bitem; + + sharefs_info("release bundle item"); + + bitem = container_of(item, struct sharefs_config_bitem, item); + remove_appid_hash_entry(&bitem->str); + remove_appid_hash_entry(&bitem->str); + free_bitem(bitem); +} + +static struct configfs_item_operations sharefs_config_bitem_ops = { + .release = sharefs_config_bitem_release, +}; + +static struct config_item_type sharefs_config_bitem_type = { + .ct_item_ops = &sharefs_config_bitem_ops, + .ct_attrs = sharefs_battrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *sharefs_make_bitem(struct config_group *group, + const char *name) +{ + struct config_item *item; + struct sharefs_config_bitem *bitem; + + bitem = alloc_bitem(name); + if (IS_ERR(bitem)) { + item = ERR_PTR(-ENOMEM); + goto out; + } + + config_item_init_type_name(&bitem->item, name, + &sharefs_config_bitem_type); + item = &bitem->item; +out: + return item; +} + +static struct configfs_group_operations sharefs_group_ops = { + .make_item = sharefs_make_bitem, +}; + +static struct config_item_type sharefs_group_type = { + .ct_group_ops = &sharefs_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem sharefs_subsystem = { + .su_group = { + .cg_item = { + .ci_namebuf = "sharefs", + .ci_type = &sharefs_group_type, + }, + }, +}; + +int get_bid_config(const char *bname) +{ + return sharefs_appid_get(bname); +} + +int __init sharefs_init_configfs(void) +{ + int err; + struct configfs_subsystem *subsys; + + sharefs_info("init configfs"); + + sharefs_bid_entry_cachep = kmem_cache_create("sharefs_bid_entry_cachep", + sizeof(struct sharefs_bid_entry), 0, 0, NULL); + if (!sharefs_bid_entry_cachep) { + sharefs_err("failed to create bid entry cachep"); + err = -ENOMEM; + goto out; + } + + subsys = &sharefs_subsystem; + config_group_init(&subsys->su_group); + mutex_init(&subsys->su_mutex); + + err = configfs_register_subsystem(subsys); + if (err) + sharefs_err("failed to register subsystem"); + +out: + return err; +} + +void sharefs_exit_configfs(void) +{ + sharefs_info("sharefs exit configfs"); + + configfs_unregister_subsystem(&sharefs_subsystem); + clear_appid_hash_entry(); + + kmem_cache_destroy(sharefs_bid_entry_cachep); +} \ No newline at end of file diff --git a/fs/sharefs/dentry.c b/fs/sharefs/dentry.c new file mode 100755 index 0000000000000000000000000000000000000000..dee29cace6b751a5cd29b648bb5024bc53e96714 --- /dev/null +++ b/fs/sharefs/dentry.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fs/sharefs/dentry.c + * + * Copyright (c) 1998-2022 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2022 Stony Brook University + * Copyright (c) 2003-2022 The Research Foundation of SUNY + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include "sharefs.h" + +/* + * returns: 0: tell VFS to invalidate dentry in share directory + */ +static int sharefs_d_revalidate(struct dentry *dentry, unsigned int flags) +{ + return 0; +} + +static void sharefs_d_release(struct dentry *dentry) +{ + /* + * It is possible that the dentry private data is NULL in case we + * ran out of memory while initializing it in + * new_dentry_private_data. So check for NULL before attempting to + * release resources. + */ + if (SHAREFS_D(dentry)) { + /* release and reset the lower paths */ + sharefs_put_reset_lower_path(dentry); + free_dentry_private_data(dentry); + } + return; +} + +const struct dentry_operations sharefs_dops = { + .d_revalidate = sharefs_d_revalidate, + .d_release = sharefs_d_release, +}; diff --git a/fs/sharefs/file.c b/fs/sharefs/file.c new file mode 100755 index 0000000000000000000000000000000000000000..f04963d57a4a6b8896393ade9d80d59cae885ff9 --- /dev/null +++ b/fs/sharefs/file.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fs/sharefs/file.c + * + * Copyright (c) 1998-2022 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2022 Stony Brook University + * Copyright (c) 2003-2022 The Research Foundation of SUNY + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include "sharefs.h" + +static int sharefs_readdir(struct file *file, struct dir_context *ctx) +{ + int err; + struct file *lower_file = NULL; + struct dentry *dentry = file->f_path.dentry; + + lower_file = sharefs_lower_file(file); + err = iterate_dir(lower_file, ctx); + file->f_pos = lower_file->f_pos; + if (err >= 0) /* copy the atime */ + fsstack_copy_attr_atime(d_inode(dentry), + file_inode(lower_file)); + return err; +} + +static int sharefs_open(struct inode *inode, struct file *file) +{ + int err = 0; + struct file *lower_file = NULL; + struct path lower_path; + + /* don't open unhashed/deleted files */ + if (d_unhashed(file->f_path.dentry)) { + err = -ENOENT; + goto out_err; + } + + file->private_data = + kzalloc(sizeof(struct sharefs_file_info), GFP_KERNEL); + if (!SHAREFS_F(file)) { + err = -ENOMEM; + goto out_err; + } + + /* open lower object and link sharefs's file struct to lower's */ + sharefs_get_lower_path(file->f_path.dentry, &lower_path); + lower_file = dentry_open(&lower_path, file->f_flags, current_cred()); + path_put(&lower_path); + if (IS_ERR(lower_file)) { + err = PTR_ERR(lower_file); + lower_file = sharefs_lower_file(file); + if (lower_file) { + sharefs_set_lower_file(file, NULL); + fput(lower_file); /* fput calls dput for lower_dentry */ + } + } else { + sharefs_set_lower_file(file, lower_file); + } + + if (err) { + kfree(SHAREFS_F(file)); + } else { + kuid_t uid = inode->i_uid; + kgid_t gid = inode->i_gid; + mode_t mode = inode->i_mode; + fsstack_copy_attr_all(inode, sharefs_lower_inode(inode)); + inode->i_uid = uid; + inode->i_gid = gid; + inode->i_mode = mode; + } +out_err: + return err; +} + +static int sharefs_flush(struct file *file, fl_owner_t id) +{ + int err = 0; + struct file *lower_file = NULL; + + lower_file = sharefs_lower_file(file); + if (lower_file && lower_file->f_op && lower_file->f_op->flush) { + filemap_write_and_wait(file->f_mapping); + err = lower_file->f_op->flush(lower_file, id); + } + + return err; +} + +/* release all lower object references & free the file info structure */ +static int sharefs_file_release(struct inode *inode, struct file *file) +{ + struct file *lower_file; + + lower_file = sharefs_lower_file(file); + if (lower_file) { + sharefs_set_lower_file(file, NULL); + fput(lower_file); + } + + kfree(SHAREFS_F(file)); + return 0; +} + +static int sharefs_fsync(struct file *file, loff_t start, loff_t end, + int datasync) +{ + int err; + struct file *lower_file; + struct path lower_path; + struct dentry *dentry = file->f_path.dentry; + + err = __generic_file_fsync(file, start, end, datasync); + if (err) + goto out; + lower_file = sharefs_lower_file(file); + sharefs_get_lower_path(dentry, &lower_path); + err = vfs_fsync_range(lower_file, start, end, datasync); + sharefs_put_lower_path(dentry, &lower_path); +out: + return err; +} + +static int sharefs_fasync(int fd, struct file *file, int flag) +{ + int err = 0; + struct file *lower_file = NULL; + + lower_file = sharefs_lower_file(file); + if (lower_file->f_op && lower_file->f_op->fasync) + err = lower_file->f_op->fasync(fd, lower_file, flag); + + return err; +} + +/* + * Sharefs cannot use generic_file_llseek as ->llseek, because it would + * only set the offset of the upper file. So we have to implement our + * own method to set both the upper and lower file offsets + * consistently. + */ +static loff_t sharefs_file_llseek(struct file *file, loff_t offset, int whence) +{ + loff_t err; + struct file *lower_file; + + lower_file = sharefs_lower_file(file); + lower_file->f_pos = file->f_pos; + err = generic_file_llseek(lower_file, offset, whence); + file->f_pos = lower_file->f_pos; + + return err; +} + +/* + * Sharefs read_iter, redirect modified iocb to lower read_iter + */ +ssize_t sharefs_read_iter(struct kiocb *iocb, struct iov_iter *iter) +{ + int err; + struct file *file = iocb->ki_filp; + struct file *lower_file; + + lower_file = sharefs_lower_file(file); + if (!lower_file->f_op->read_iter) { + err = -EINVAL; + goto out; + } + + /* prevent lower_file from being released */ + get_file(lower_file); + iocb->ki_filp = lower_file; + err = lower_file->f_op->read_iter(iocb, iter); + iocb->ki_filp = file; + fput(lower_file); + + /* update upper inode atime as needed */ + if (err >= 0 || err == -EIOCBQUEUED) + fsstack_copy_attr_atime(d_inode(file->f_path.dentry), + file_inode(lower_file)); +out: + return err; +} + +/* + * Sharefs write_iter, redirect modified iocb to lower write_iter + */ +ssize_t sharefs_write_iter(struct kiocb *iocb, struct iov_iter *iter) +{ + int err; + struct file *file = iocb->ki_filp; + struct file *lower_file; + + lower_file = sharefs_lower_file(file); + if (!lower_file->f_op->write_iter) { + err = -EINVAL; + goto out; + } + + get_file(lower_file); /* prevent lower_file from being released */ + iocb->ki_filp = lower_file; + err = lower_file->f_op->write_iter(iocb, iter); + iocb->ki_filp = file; + fput(lower_file); + /* update upper inode times/sizes as needed */ + if (err >= 0 || err == -EIOCBQUEUED) { + fsstack_copy_inode_size(d_inode(file->f_path.dentry), + file_inode(lower_file)); + fsstack_copy_attr_times(d_inode(file->f_path.dentry), + file_inode(lower_file)); + } +out: + return err; +} + +int sharefs_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + int err = 0; + struct file *lower_file; + + lower_file = sharefs_lower_file(file); + if (!lower_file) + return -EINVAL; + + if (!lower_file->f_op->mmap) + return -ENODEV; + + if (WARN_ON(file != vma->vm_file)) + return -EIO; + + vma->vm_file = get_file(lower_file); + err = call_mmap(vma->vm_file, vma); + if (err) + fput(lower_file); + else + fput(file); + + file_accessed(file); + + return err; +} + +const struct file_operations sharefs_main_fops = { + .llseek = sharefs_file_llseek, + .open = sharefs_open, + .flush = sharefs_flush, + .release = sharefs_file_release, + .fsync = sharefs_fsync, + .fasync = sharefs_fasync, + .read_iter = sharefs_read_iter, + .write_iter = sharefs_write_iter, + .mmap = sharefs_file_mmap, + .splice_read = generic_file_splice_read, + .splice_write = iter_file_splice_write, +}; + +/* trimmed directory options */ +const struct file_operations sharefs_dir_fops = { + .llseek = sharefs_file_llseek, + .read = generic_read_dir, + .iterate = sharefs_readdir, + .open = sharefs_open, + .release = sharefs_file_release, + .flush = sharefs_flush, + .fsync = sharefs_fsync, + .fasync = sharefs_fasync, +}; diff --git a/fs/sharefs/inode.c b/fs/sharefs/inode.c new file mode 100755 index 0000000000000000000000000000000000000000..64c1ff15bb2df582da9570a492fee21fc6ad08f3 --- /dev/null +++ b/fs/sharefs/inode.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fs/sharefs/inode.c + * + * Copyright (c) 1998-2022 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2022 Stony Brook University + * Copyright (c) 2003-2022 The Research Foundation of SUNY + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include "sharefs.h" +#ifdef CONFIG_SHAREFS_SUPPORT_WRITE +#include "authentication.h" +#endif + +static int sharefs_getattr(struct user_namespace *mnt_userns, const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + struct path lower_path; + int ret; + + sharefs_get_lower_path(path->dentry, &lower_path); + ret = vfs_getattr(&lower_path, stat, request_mask, flags); + stat->ino = d_inode(path->dentry)->i_ino; + stat->uid = d_inode(path->dentry)->i_uid; + stat->gid = d_inode(path->dentry)->i_gid; + stat->mode = d_inode(path->dentry)->i_mode; + stat->dev = 0; + stat->rdev = 0; + sharefs_put_lower_path(path->dentry, &lower_path); + + return ret; +} + +static ssize_t sharefs_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size) +{ + int err; + struct dentry *lower_dentry; + struct path lower_path; + + sharefs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + if (!(d_inode(lower_dentry)->i_opflags & IOP_XATTR)) { + err = -EOPNOTSUPP; + goto out; + } + err = vfs_listxattr(lower_dentry, buffer, buffer_size); + if (err) + goto out; + fsstack_copy_attr_atime(d_inode(dentry), + d_inode(lower_path.dentry)); +out: + sharefs_put_lower_path(dentry, &lower_path); + return err; +} + +static int sharefs_permission(struct user_namespace *mnt_userns, struct inode *inode, int mask) +{ +#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE + return 0; +#endif + unsigned short mode = inode->i_mode; + kuid_t cur_uid = current_fsuid(); + if (uid_eq(cur_uid, ROOT_UID)) + return 0; + if (uid_eq(cur_uid, inode->i_uid)) { + mode >>= 6; + } else if (in_group_p(inode->i_gid)) { + mode >>= 3; + } + + if ((mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0) + return 0; + + return -EACCES; +} + +#ifdef CONFIG_SHAREFS_SUPPORT_WRITE +static int sharefs_create(struct inode *dir, struct dentry *dentry, + umode_t mode, bool want_excl) +{ + int err; + struct dentry *lower_dentry; + struct dentry *lower_parent_dentry = NULL; + struct path lower_path; + const struct cred *saved_cred = NULL; + __u16 child_perm; + + saved_cred = sharefs_override_file_fsids(dir, &child_perm); + if (!saved_cred) { + err = -ENOMEM; + return err; + } + + sharefs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_parent_dentry = lock_parent(lower_dentry); + err = vfs_create(d_inode(lower_parent_dentry), lower_dentry, mode, + want_excl); + if (err) + goto out; + err = sharefs_interpose(dentry, dir->i_sb, &lower_path); + if (err) + goto out; + fsstack_copy_attr_times(dir, sharefs_lower_inode(dir)); + fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry)); + +out: + unlock_dir(lower_parent_dentry); + sharefs_put_lower_path(dentry, &lower_path); + sharefs_revert_fsids(saved_cred); + return err; +} + +static int sharefs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + int err; + struct dentry *lower_dentry; + struct dentry *lower_parent_dentry = NULL; + struct path lower_path; + const struct cred *saved_cred = NULL; + __u16 child_perm; + + saved_cred = sharefs_override_file_fsids(dir, &child_perm); + if (!saved_cred) { + err = -ENOMEM; + return err; + } + + sharefs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_parent_dentry = lock_parent(lower_dentry); + err = vfs_mkdir(d_inode(lower_parent_dentry), lower_dentry, mode); + if (err) + goto out; + + err = sharefs_interpose(dentry, dir->i_sb, &lower_path); + if (err) + goto out; + + fsstack_copy_attr_times(dir, sharefs_lower_inode(dir)); + fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry)); + /* update number of links on parent directory */ + set_nlink(dir, sharefs_lower_inode(dir)->i_nlink); + +out: + unlock_dir(lower_parent_dentry); + sharefs_put_lower_path(dentry, &lower_path); + sharefs_revert_fsids(saved_cred); + return err; +} + +static int sharefs_unlink(struct inode *dir, struct dentry *dentry) +{ + int err; + struct dentry *lower_dentry = NULL; + struct inode *lower_dir_inode = sharefs_lower_inode(dir); + struct dentry *lower_dir_dentry = NULL; + struct path lower_path; + + sharefs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + dget(lower_dentry); + lower_dir_dentry = lock_parent(lower_dentry); + err = vfs_unlink(lower_dir_inode, lower_dentry, NULL); + if (err) + goto out; + fsstack_copy_attr_times(dir, lower_dir_inode); + fsstack_copy_inode_size(dir, lower_dir_inode); + set_nlink(dentry->d_inode, + sharefs_lower_inode(dentry->d_inode)->i_nlink); + dentry->d_inode->i_ctime = dir->i_ctime; + d_drop(dentry); + +out: + unlock_dir(lower_dir_dentry); + dput(lower_dentry); + sharefs_put_lower_path(dentry, &lower_path); + return err; +} + +static int sharefs_rmdir(struct inode *dir, struct dentry *dentry) +{ + int err; + struct dentry *lower_dentry; + struct dentry *lower_dir_dentry; + struct path lower_path; + + sharefs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_dir_dentry = lock_parent(lower_dentry); + err = vfs_rmdir(lower_dir_dentry->d_inode, lower_dentry); + if (err) + goto out; + + d_drop(dentry); + if (dentry->d_inode) + clear_nlink(dentry->d_inode); + fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode); + fsstack_copy_inode_size(dir, lower_dir_dentry->d_inode); + set_nlink(dir, lower_dir_dentry->d_inode->i_nlink); + +out: + unlock_dir(lower_dir_dentry); + sharefs_put_lower_path(dentry, &lower_path); + return err; +} + +static int sharefs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +{ + int err; + struct dentry *lower_old_dentry = NULL; + struct dentry *lower_new_dentry = NULL; + struct dentry *lower_old_dir_dentry = NULL; + struct dentry *lower_new_dir_dentry = NULL; + struct dentry *trap = NULL; + struct path lower_old_path, lower_new_path; + + if (flags) + return -EINVAL; + + sharefs_get_lower_path(old_dentry, &lower_old_path); + sharefs_get_lower_path(new_dentry, &lower_new_path); + lower_old_dentry = lower_old_path.dentry; + lower_new_dentry = lower_new_path.dentry; + lower_old_dir_dentry = dget_parent(lower_old_dentry); + lower_new_dir_dentry = dget_parent(lower_new_dentry); + trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + /* source should not be ancestor of target */ + if (trap == lower_old_dentry) { + err = -EINVAL; + goto out; + } + /* target should not be ancestor of source */ + if (trap == lower_new_dentry) { + err = -ENOTEMPTY; + goto out; + } + + err = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry, + lower_new_dir_dentry->d_inode, lower_new_dentry, + NULL, 0); + if (err) + goto out; + + fsstack_copy_attr_all(new_dir, lower_new_dir_dentry->d_inode); + fsstack_copy_inode_size(new_dir, lower_new_dir_dentry->d_inode); + if (new_dir != old_dir) { + fsstack_copy_attr_all(old_dir, + lower_old_dir_dentry->d_inode); + fsstack_copy_inode_size(old_dir, + lower_old_dir_dentry->d_inode); + } + +out: + unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + dput(lower_old_dir_dentry); + dput(lower_new_dir_dentry); + sharefs_put_lower_path(old_dentry, &lower_old_path); + sharefs_put_lower_path(new_dentry, &lower_new_path); + return err; +} + +static int sharefs_setattr(struct dentry *dentry, struct iattr *ia) +{ + int err; + struct dentry *lower_dentry; + struct inode *inode; + struct inode *lower_inode; + struct path lower_path; + struct iattr lower_ia; + + inode = dentry->d_inode; + /* + * Check if user has permission to change inode. We don't check if + * this user can change the lower inode: that should happen when + * calling notify_change on the lower inode. + */ + + err = setattr_prepare(dentry, ia); + if (err) + goto out_err; + + sharefs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_inode = sharefs_lower_inode(inode); + + /* prepare our own lower struct iattr (with the lower file) */ + memcpy(&lower_ia, ia, sizeof(lower_ia)); + if (ia->ia_valid & ATTR_FILE) + lower_ia.ia_file = sharefs_lower_file(ia->ia_file); + + /* + * If shrinking, first truncate upper level to cancel writing dirty + * pages beyond the new eof; and also if its' maxbytes is more + * limiting (fail with -EFBIG before making any change to the lower + * level). There is no need to vmtruncate the upper level + * afterwards in the other cases: we fsstack_copy_inode_size from + * the lower level. + */ + if (ia->ia_valid & ATTR_SIZE) { + err = inode_newsize_ok(inode, ia->ia_size); + if (err) + goto out; + truncate_setsize(inode, ia->ia_size); + } + + /* + * mode change is for clearing setuid/setgid bits. Allow lower fs + * to interpret this in its own way. + */ + if (lower_ia.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) + lower_ia.ia_valid &= ~ATTR_MODE; + + /* notify the (possibly copied-up) lower inode */ + /* + * Note: we use lower_dentry->d_inode, because lower_inode may be + * unlinked (no inode->i_sb and i_ino==0. This happens if someone + * tries to open(), unlink(), then ftruncate() a file. + */ + + inode_lock(d_inode(lower_dentry)); + err = notify_change(lower_dentry, &lower_ia, /* note: lower_ia */ + NULL); + inode_unlock(d_inode(lower_dentry)); + + if (err) + goto out; + + /* get attributes from the lower inode */ + fsstack_copy_attr_all(inode, lower_inode); + /* + * Not running fsstack_copy_inode_size(inode, lower_inode), because + * VFS should update our inode size, and notify_change on + * lower_inode should update its size. + */ + +out: + sharefs_put_lower_path(dentry, &lower_path); +out_err: + return err; +} +#endif + +const struct inode_operations sharefs_symlink_iops = { + .permission = sharefs_permission, + .getattr = sharefs_getattr, + .get_link = NULL, + .listxattr = sharefs_listxattr, +}; + +const struct inode_operations sharefs_dir_iops = { + .lookup = sharefs_lookup, + .permission = sharefs_permission, + .getattr = sharefs_getattr, + .listxattr = sharefs_listxattr, +#ifdef CONFIG_SHAREFS_SUPPORT_WRITE + .unlink = sharefs_unlink, + .rmdir = sharefs_rmdir, + .rename = sharefs_rename, + .create = sharefs_create, + .mkdir = sharefs_mkdir, + .setattr = sharefs_setattr, +#endif +}; + +const struct inode_operations sharefs_main_iops = { + .permission = sharefs_permission, + .getattr = sharefs_getattr, + .listxattr = sharefs_listxattr, +#ifdef CONFIG_SHAREFS_SUPPORT_WRITE + .setattr = sharefs_setattr, +#endif +}; diff --git a/fs/sharefs/lookup.c b/fs/sharefs/lookup.c new file mode 100755 index 0000000000000000000000000000000000000000..0b0c30ef46f12aa9e22c49ebe7cee8ca5efd9875 --- /dev/null +++ b/fs/sharefs/lookup.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fs/sharefs/lookup.c + * + * Copyright (c) 1998-2022 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2022 Stony Brook University + * Copyright (c) 2003-2022 The Research Foundation of SUNY + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include "sharefs.h" +#include "authentication.h" + +/* The dentry cache is just so we have properly sized dentries */ +static struct kmem_cache *sharefs_dentry_cachep; + +int sharefs_init_dentry_cache(void) +{ + sharefs_dentry_cachep = + kmem_cache_create("sharefs_dentry", + sizeof(struct sharefs_dentry_info), + 0, SLAB_RECLAIM_ACCOUNT, NULL); + + return sharefs_dentry_cachep ? 0 : -ENOMEM; +} + +void sharefs_destroy_dentry_cache(void) +{ + if (sharefs_dentry_cachep) + kmem_cache_destroy(sharefs_dentry_cachep); +} + +void free_dentry_private_data(struct dentry *dentry) +{ + if (!dentry || !dentry->d_fsdata) + return; + kmem_cache_free(sharefs_dentry_cachep, dentry->d_fsdata); + dentry->d_fsdata = NULL; +} + +/* allocate new dentry private data */ +int new_dentry_private_data(struct dentry *dentry) +{ + struct sharefs_dentry_info *info = SHAREFS_D(dentry); + + /* use zalloc to init dentry_info.lower_path */ + info = kmem_cache_zalloc(sharefs_dentry_cachep, GFP_ATOMIC); + if (!info) + return -ENOMEM; + + spin_lock_init(&info->lock); + dentry->d_fsdata = info; + + return 0; +} + +static int sharefs_inode_test(struct inode *inode, void *candidate_lower_inode) +{ + struct inode *current_lower_inode = sharefs_lower_inode(inode); + if (current_lower_inode == (struct inode *)candidate_lower_inode) + return 1; /* found a match */ + else + return 0; /* no match */ +} + +static int sharefs_inode_set(struct inode *inode, void *lower_inode) +{ + /* we do actual inode initialization in sharefs_iget */ + return 0; +} + +struct inode *sharefs_iget(struct super_block *sb, struct inode *lower_inode) +{ + struct inode *inode; /* the new inode to return */ + + if (!igrab(lower_inode)) + return ERR_PTR(-ESTALE); + inode = iget5_locked(sb, /* our superblock */ + /* + * hashval: we use inode number, but we can + * also use "(unsigned long)lower_inode" + * instead. + */ + lower_inode->i_ino, /* hashval */ + sharefs_inode_test, /* inode comparison function */ + sharefs_inode_set, /* inode init function */ + lower_inode); /* data passed to test+set fxns */ + if (!inode) { + iput(lower_inode); + return ERR_PTR(-ENOMEM); + } + + if (lower_inode->i_nlink == 0) { + iput(lower_inode); + iput(inode); + return ERR_PTR(-ENOENT); + } + + /* if found a cached inode, then just return it (after iput) */ + if (!(inode->i_state & I_NEW)) { + iput(lower_inode); + return inode; + } + + /* initialize new inode */ + inode->i_ino = lower_inode->i_ino; + sharefs_set_lower_inode(inode, lower_inode); + + atomic64_inc(&inode->i_version); + + /* use different set of inode ops for symlinks & directories */ + if (S_ISDIR(lower_inode->i_mode)) + inode->i_op = &sharefs_dir_iops; + else if (S_ISLNK(lower_inode->i_mode)) + inode->i_op = &sharefs_symlink_iops; + else + inode->i_op = &sharefs_main_iops; + + /* use different set of file ops for directories */ + if (S_ISDIR(lower_inode->i_mode)) + inode->i_fop = &sharefs_dir_fops; + else + inode->i_fop = &sharefs_main_fops; + + inode->i_atime.tv_sec = 0; + inode->i_atime.tv_nsec = 0; + inode->i_mtime.tv_sec = 0; + inode->i_mtime.tv_nsec = 0; + inode->i_ctime.tv_sec = 0; + inode->i_ctime.tv_nsec = 0; + + /* properly initialize special inodes */ + if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || + S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) + init_special_inode(inode, lower_inode->i_mode, + lower_inode->i_rdev); + + /* all well, copy inode attributes */ + fsstack_copy_attr_all(inode, lower_inode); + fsstack_copy_inode_size(inode, lower_inode); + + unlock_new_inode(inode); + return inode; +} + +/* + * Helper interpose routine, called directly by ->lookup to handle + * spliced dentries. + */ +static struct dentry *__sharefs_interpose(struct dentry *dentry, + struct super_block *sb, + struct path *lower_path) +{ + struct inode *inode; + struct inode *lower_inode = d_inode(lower_path->dentry); + struct dentry *ret_dentry; + + /* + * We allocate our new inode below by calling sharefs_iget, + * which will initialize some of the new inode's fields + */ + + /* inherit lower inode number for sharefs's inode */ + inode = sharefs_iget(sb, lower_inode); + if (IS_ERR(inode)) { + ret_dentry = ERR_PTR(PTR_ERR(inode)); + goto out; + } + + ret_dentry = d_splice_alias(inode, dentry); + +out: + return ret_dentry; +} + +/* + * Connect a sharefs inode dentry/inode with several lower ones. This is + * the classic stackable file system "vnode interposition" action. + * + * @dentry: sharefs's dentry which interposes on lower one + * @sb: sharefs's super_block + * @lower_path: the lower path (caller does path_get/put) + */ +int sharefs_interpose(struct dentry *dentry, struct super_block *sb, + struct path *lower_path) +{ + struct dentry *ret_dentry; + + ret_dentry = __sharefs_interpose(dentry, sb, lower_path); + return PTR_ERR(ret_dentry); +} + +/* + * Main driver function for sharefs's lookup. + * + * Returns: NULL (ok), ERR_PTR if an error occurred. + * Fills in lower_parent_path with on success. + */ +static struct dentry *__sharefs_lookup(struct dentry *dentry, + unsigned int flags, + struct path *lower_parent_path) +{ + int err = 0; + struct vfsmount *lower_dir_mnt; + struct dentry *lower_dir_dentry = NULL; + struct dentry *lower_dentry; + const char *name; + struct path lower_path; + struct qstr this; + struct dentry *ret_dentry = NULL; + + /* must initialize dentry operations */ + d_set_d_op(dentry, &sharefs_dops); + + if (IS_ROOT(dentry)) + goto out; + + name = dentry->d_name.name; + + /* now start the actual lookup procedure */ + lower_dir_dentry = lower_parent_path->dentry; + lower_dir_mnt = lower_parent_path->mnt; + + /* Use vfs_path_lookup to check if the dentry exists or not */ + err = vfs_path_lookup(lower_dir_dentry, lower_dir_mnt, name, 0, + &lower_path); + /* no error: handle positive dentries */ + if (!err) { + sharefs_set_lower_path(dentry, &lower_path); + ret_dentry = + __sharefs_interpose(dentry, dentry->d_sb, &lower_path); + if (IS_ERR(ret_dentry)) { + err = PTR_ERR(ret_dentry); + /* path_put underlying path on error */ + sharefs_put_reset_lower_path(dentry); + } + goto out; + } + /* + * We don't consider ENOENT an error, and we want to return a + * negative dentry. + */ + if (err && err != -ENOENT) + goto out; + + /* instantiate a new negative dentry */ + this.name = name; + this.len = strlen(name); + this.hash = full_name_hash(lower_dir_dentry, this.name, this.len); + lower_dentry = d_lookup(lower_dir_dentry, &this); + if (lower_dentry) + goto setup_lower; + + lower_dentry = d_alloc(lower_dir_dentry, &this); + if (!lower_dentry) { + err = -ENOMEM; + goto out; + } + + /* + * Calling ->lookup instead of d_add will give the lower fs a chance + * to allocate the d_fsdata field but will still instantiate and hash the + * lower_dentry. Without this, sharefs could not stack on top of itself. + */ + d_inode(lower_dir_dentry)->i_op->lookup(d_inode(lower_dir_dentry), + lower_dentry, flags); + +setup_lower: + lower_path.dentry = lower_dentry; + lower_path.mnt = mntget(lower_dir_mnt); + sharefs_set_lower_path(dentry, &lower_path); + + /* + * If the intent is to create a file, then don't return an error, so + * the VFS will continue the process of making this negative dentry + * into a positive one. + */ + if (err == -ENOENT || (flags & (LOOKUP_CREATE|LOOKUP_RENAME_TARGET))) + err = 0; + +out: + if (err) + return ERR_PTR(err); + return ret_dentry; +} + +struct dentry *sharefs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + int err; + struct dentry *ret, *parent; + struct path lower_parent_path; +#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE + const struct cred *saved_cred = NULL; + __u16 permission; +#endif + + parent = dget_parent(dentry); + sharefs_get_lower_path(parent, &lower_parent_path); +#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE + saved_cred = sharefs_override_file_fsids(dir, &permission); + if (!saved_cred) { + ret = ERR_PTR(-ENOMEM); + goto out_err; + } +#endif + + /* allocate dentry private data. We free it in ->d_release */ + err = new_dentry_private_data(dentry); + if (err) { + ret = ERR_PTR(err); + goto out; + } + ret = __sharefs_lookup(dentry, flags, &lower_parent_path); + if (IS_ERR(ret)) { + sharefs_err("sharefs_lookup error!"); + goto out; + } + + if (ret) + dentry = ret; + if (d_inode(dentry)) + fsstack_copy_attr_times(d_inode(dentry), + sharefs_lower_inode(d_inode(dentry))); + /* update parent directory's atime */ + fsstack_copy_attr_atime(d_inode(parent), + sharefs_lower_inode(d_inode(parent))); + fixup_perm_from_level(d_inode(parent), dentry); +out: +#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE + sharefs_revert_fsids(saved_cred); +out_err: +#endif + sharefs_put_lower_path(parent, &lower_parent_path); + dput(parent); + return ret; +} diff --git a/fs/sharefs/main.c b/fs/sharefs/main.c new file mode 100755 index 0000000000000000000000000000000000000000..10cc7d62630736558f2773785c9d3bffc8cad143 --- /dev/null +++ b/fs/sharefs/main.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fs/sharefs/main.c + * + * Copyright (c) 1998-2022 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2022 Stony Brook University + * Copyright (c) 2003-2022 The Research Foundation of SUNY + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include +#include "sharefs.h" +#include "authentication.h" + + +struct sharefs_mount_priv { + const char *dev_name; + const char *raw_data; +}; + +/* + * There is no need to lock the sharefs_super_info's rwsem as there is no + * way anyone can have a reference to the superblock at this point in time. + */ +static int sharefs_fill_super(struct super_block *sb, void *data, int silent) +{ + + struct sharefs_mount_priv *priv = (struct sharefs_mount_priv *)data; + const char *dev_name = priv->dev_name; + const char *raw_data = priv->raw_data; + + int err = 0; + struct super_block *lower_sb; + struct path lower_path; + struct inode *inode; + + /* parse lower path */ + err = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, + &lower_path); + if (err) { + printk(KERN_ERR "sharefs: error accessing " + "lower directory '%s'\n", dev_name); + goto out; + } + + /* allocate superblock private data */ + sb->s_fs_info = kzalloc(sizeof(struct sharefs_sb_info), GFP_KERNEL); + if (!SHAREFS_SB(sb)) { + printk(KERN_CRIT "sharefs: fill_super: out of memory\n"); + err = -ENOMEM; + goto out_pput; + } + + /* set the lower superblock field of upper superblock */ + lower_sb = lower_path.dentry->d_sb; + atomic_inc(&lower_sb->s_active); + sharefs_set_lower_super(sb, lower_sb); + + /* inherit maxbytes from lower file system */ + sb->s_maxbytes = lower_sb->s_maxbytes; + + /* + * Our c/m/atime granularity is 1 ns because we may stack on file + * systems whose granularity is as good. + */ + sb->s_time_gran = 1; + + sb->s_op = &sharefs_sops; + + /* get a new inode and allocate our root dentry */ + inode = sharefs_iget(sb, d_inode(lower_path.dentry)); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out_pput; + } + sharefs_root_inode_perm_init(inode); + sb->s_root = d_make_root(inode); + if (!sb->s_root) { + err = -ENOMEM; + goto out_pput; + } + d_set_d_op(sb->s_root, &sharefs_dops); + + err = sharefs_parse_options(sb->s_fs_info, raw_data); + if (err) + goto out_pput; + + /* link the upper and lower dentries */ + sb->s_root->d_fsdata = NULL; + err = new_dentry_private_data(sb->s_root); + if (err) + goto out_pput; + + /* if get here: cannot have error */ + + /* set the lower dentries for s_root */ + sharefs_set_lower_path(sb->s_root, &lower_path); + + /* + * No need to call interpose because we already have a positive + * dentry, which was instantiated by d_make_root. Just need to + * d_rehash it. + */ + d_rehash(sb->s_root); + if (!silent) + printk(KERN_INFO + "sharefs: mounted on top of %s type %s\n", + dev_name, lower_sb->s_type->name); + goto out; /* all is well */ + + /* + * path_put is the only resource we need to free if an error occurred + * because returning an error from this function will cause + * generic_shutdown_super to be called, which will call + * sharefs_put_super, and that function will release any other + * resources we took. + */ +out_pput: + path_put(&lower_path); +out: + return err; +} + +struct dentry *sharefs_mount(struct file_system_type *fs_type, int flags, + const char *dev_name, void *raw_data) +{ + struct sharefs_mount_priv priv = { + .dev_name = dev_name, + .raw_data = raw_data, + }; + + /* sharefs needs a valid dev_name to get the lower_sb's metadata */ + if (!dev_name || !*dev_name) + return ERR_PTR(-EINVAL); + + return mount_nodev(fs_type, flags, &priv, + sharefs_fill_super); +} + +static struct file_system_type sharefs_fs_type = { + .owner = THIS_MODULE, + .name = SHAREFS_NAME, + .mount = sharefs_mount, + .kill_sb = generic_shutdown_super, + .fs_flags = 0, +}; + +static int __init init_sharefs_fs(void) +{ + int err; + + pr_info("Registering sharefs"); + + err = sharefs_init_inode_cache(); + if (err) + goto out_err; + err = sharefs_init_dentry_cache(); + if (err) + goto out_err; + err = register_filesystem(&sharefs_fs_type); + if (err) { + sharefs_err("share register failed!"); + goto out_err; + } + + err = sharefs_init_configfs(); + if (err) + goto out_err; + return 0; +out_err: + sharefs_exit_configfs(); + sharefs_destroy_inode_cache(); + sharefs_destroy_dentry_cache(); + sharefs_err("sharefs init failed!"); + return err; +} + +static void __exit exit_sharefs_fs(void) +{ + sharefs_destroy_inode_cache(); + sharefs_destroy_dentry_cache(); + unregister_filesystem(&sharefs_fs_type); + sharefs_exit_configfs(); + pr_info("Completed sharefs module unload\n"); +} + +module_init(init_sharefs_fs); +module_exit(exit_sharefs_fs); + +MODULE_LICENSE("GPL V2"); +MODULE_AUTHOR("Jingjing Mao"); +MODULE_DESCRIPTION("Share File System"); +MODULE_ALIAS_FS("sharefs"); diff --git a/fs/sharefs/sharefs.h b/fs/sharefs/sharefs.h new file mode 100755 index 0000000000000000000000000000000000000000..143f047b235f340d0d8122e34ec3b15b9d5ddf6f --- /dev/null +++ b/fs/sharefs/sharefs.h @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 1998-2022 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2022 Stony Brook University + * Copyright (c) 2003-2022 The Research Foundation of SUNY + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _SHAREFS_H_ +#define _SHAREFS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* the file system name */ +#define SHAREFS_NAME "sharefs" + +/* sharefs root inode number */ +#define SHAREFS_ROOT_INO 1 +#define OID_ROOT 0 +#define ROOT_UID KUIDT_INIT(OID_ROOT) +#define ROOT_GID KGIDT_INIT(OID_ROOT) +#define SHAREFS_SUPER_MAGIC 0x20230212 + +/* useful for tracking code reachability */ +#define UDBG printk(KERN_DEFAULT "DBG:%s:%s:%d\n", __FILE__, __func__, __LINE__) + +/* file private data */ +struct sharefs_file_info { + struct file *lower_file; + const struct vm_operations_struct *lower_vm_ops; +}; + +/* sharefs inode data in memory */ +struct sharefs_inode_info { + struct inode *lower_inode; + struct inode vfs_inode; + __u16 perm; +}; + +/* sharefs dentry data in memory */ +struct sharefs_dentry_info { + spinlock_t lock; /* protects lower_path */ + struct path lower_path; +}; + +/* sharefs super-block data in memory */ +struct sharefs_sb_info { + struct super_block *lower_sb; + /* multi user */ + unsigned int user_id; +}; + +/* operations vectors defined in specific files */ +extern const struct file_operations sharefs_main_fops; +extern const struct file_operations sharefs_dir_fops; +extern const struct inode_operations sharefs_main_iops; +extern const struct inode_operations sharefs_dir_iops; +extern const struct inode_operations sharefs_symlink_iops; +extern const struct super_operations sharefs_sops; +extern const struct dentry_operations sharefs_dops; + +extern int sharefs_init_inode_cache(void); +extern void sharefs_destroy_inode_cache(void); +extern int sharefs_init_dentry_cache(void); +extern void sharefs_destroy_dentry_cache(void); +extern int new_dentry_private_data(struct dentry *dentry); +extern void free_dentry_private_data(struct dentry *dentry); +extern struct dentry *sharefs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags); +extern struct inode *sharefs_iget(struct super_block *sb, + struct inode *lower_inode); +extern int sharefs_interpose(struct dentry *dentry, struct super_block *sb, + struct path *lower_path); +extern int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt, + const char *name, unsigned int flags, + struct path *path); +extern int sharefs_parse_options(struct sharefs_sb_info *sbi, + const char *data); + +/* + * inode to private data + * + * Since we use containers and the struct inode is _inside_ the + * sharefs_inode_info structure, SHAREFS_I will always (given a non-NULL + * inode pointer), return a valid non-NULL pointer. + */ +static inline struct sharefs_inode_info *SHAREFS_I(const struct inode *inode) +{ + return container_of(inode, struct sharefs_inode_info, vfs_inode); +} + +/* dentry to private data */ +#define SHAREFS_D(dent) ((struct sharefs_dentry_info *)(dent)->d_fsdata) + +/* superblock to private data */ +#define SHAREFS_SB(super) ((struct sharefs_sb_info *)(super)->s_fs_info) + +/* file to private Data */ +#define SHAREFS_F(file) ((struct sharefs_file_info *)((file)->private_data)) + +/* file to lower file */ +static inline struct file *sharefs_lower_file(const struct file *f) +{ + return SHAREFS_F(f)->lower_file; +} + +static inline void sharefs_set_lower_file(struct file *f, struct file *val) +{ + SHAREFS_F(f)->lower_file = val; +} + +/* inode to lower inode. */ +static inline struct inode *sharefs_lower_inode(const struct inode *i) +{ + return SHAREFS_I(i)->lower_inode; +} + +static inline void sharefs_set_lower_inode(struct inode *i, struct inode *val) +{ + SHAREFS_I(i)->lower_inode = val; +} + +/* superblock to lower superblock */ +static inline struct super_block *sharefs_lower_super( + const struct super_block *sb) +{ + return SHAREFS_SB(sb)->lower_sb; +} + +static inline void sharefs_set_lower_super(struct super_block *sb, + struct super_block *val) +{ + SHAREFS_SB(sb)->lower_sb = val; +} + +/* path based (dentry/mnt) macros */ +static inline void pathcpy(struct path *dst, const struct path *src) +{ + dst->dentry = src->dentry; + dst->mnt = src->mnt; +} +/* Returns struct path. Caller must path_put it. */ +static inline void sharefs_get_lower_path(const struct dentry *dent, + struct path *lower_path) +{ + spin_lock(&SHAREFS_D(dent)->lock); + pathcpy(lower_path, &SHAREFS_D(dent)->lower_path); + path_get(lower_path); + spin_unlock(&SHAREFS_D(dent)->lock); + return; +} +static inline void sharefs_put_lower_path(const struct dentry *dent, + struct path *lower_path) +{ + path_put(lower_path); + return; +} +static inline void sharefs_set_lower_path(const struct dentry *dent, + struct path *lower_path) +{ + spin_lock(&SHAREFS_D(dent)->lock); + pathcpy(&SHAREFS_D(dent)->lower_path, lower_path); + spin_unlock(&SHAREFS_D(dent)->lock); + return; +} +static inline void sharefs_reset_lower_path(const struct dentry *dent) +{ + spin_lock(&SHAREFS_D(dent)->lock); + SHAREFS_D(dent)->lower_path.dentry = NULL; + SHAREFS_D(dent)->lower_path.mnt = NULL; + spin_unlock(&SHAREFS_D(dent)->lock); + return; +} +static inline void sharefs_put_reset_lower_path(const struct dentry *dent) +{ + struct path lower_path; + spin_lock(&SHAREFS_D(dent)->lock); + pathcpy(&lower_path, &SHAREFS_D(dent)->lower_path); + SHAREFS_D(dent)->lower_path.dentry = NULL; + SHAREFS_D(dent)->lower_path.mnt = NULL; + spin_unlock(&SHAREFS_D(dent)->lock); + path_put(&lower_path); + return; +} + +/* locking helpers */ +static inline struct dentry *lock_parent(struct dentry *dentry) +{ + struct dentry *dir = dget_parent(dentry); + inode_lock_nested(d_inode(dir), I_MUTEX_PARENT); + return dir; +} + +static inline void unlock_dir(struct dentry *dir) +{ + inode_unlock(d_inode(dir)); + dput(dir); +} + +static inline bool str_n_case_eq(const char *s1, const char *s2, size_t len) +{ + return !strncasecmp(s1, s2, len); +} + +static inline bool qstr_case_eq(const struct qstr *q1, const struct qstr *q2) +{ + return q1->len == q2->len && str_n_case_eq(q1->name, q2->name, q2->len); +} +/***************************************************************************** + * log print helpers + *****************************************************************************/ +__printf(4, 5) void __sharefs_log(const char *level, const bool ratelimited, + const char *function, const char *fmt, ...); +#define sharefs_err(fmt, ...) \ + __sharefs_log(KERN_ERR, false, __func__, fmt, ##__VA_ARGS__) +#define sharefs_warning(fmt, ...) \ + __sharefs_log(KERN_WARNING, false, __func__, fmt, ##__VA_ARGS__) +#define sharefs_info(fmt, ...) \ + __sharefs_log(KERN_INFO, false, __func__, fmt, ##__VA_ARGS__) +#define sharefs_err_ratelimited(fmt, ...) \ + __sharefs_log(KERN_ERR, true, __func__, fmt, ##__VA_ARGS__) +#define sharefs_warning_ratelimited(fmt, ...) \ + __sharefs_log(KERN_WARNING, true, __func__, fmt, ##__VA_ARGS__) +#define sharefs_info_ratelimited(fmt, ...) \ + __sharefs_log(KERN_INFO, true, __func__, fmt, ##__VA_ARGS__) + +#endif /* not _SHAREFS_H_ */ diff --git a/fs/sharefs/super.c b/fs/sharefs/super.c new file mode 100755 index 0000000000000000000000000000000000000000..bbe65944647fd611c2aea433b7228f03eeab8e89 --- /dev/null +++ b/fs/sharefs/super.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 1998-2022 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2022 Stony Brook University + * Copyright (c) 2003-2022 The Research Foundation of SUNY + */ +#include +#include +#include +#include +#include "sharefs.h" + +enum { + OPT_USER_ID, + OPT_ERR, +}; + +static match_table_t sharefs_tokens = { + { OPT_USER_ID, "user_id=%s" }, + { OPT_ERR, NULL } +}; + +int sharefs_parse_options(struct sharefs_sb_info *sbi, const char *data) +{ + char *p = NULL; + char *name = NULL; + char *options = NULL; + char *options_src = NULL; + substring_t args[MAX_OPT_ARGS]; + unsigned int user_id = 0; + int err = 0; + + options = kstrdup(data, GFP_KERNEL); + if (data && !options) { + err = -ENOMEM; + goto out; + } + options_src = options; + + while ((p = strsep(&options_src, ",")) != NULL) { + int token; + + if (!*p) + continue; + args[0].to = args[0].from = NULL; + token = match_token(p, sharefs_tokens, args); + + switch (token) { + case OPT_USER_ID: + name = match_strdup(&args[0]); + if (name) { + err = kstrtouint(name, 10, &user_id); + kfree(name); + name = NULL; + if (err) + goto out; + sbi->user_id = user_id; + } + break; + default: + err = -EINVAL; + goto out; + } + } +out: + kfree(options); + + return err; +} + +/* + * The inode cache is used with alloc_inode for both our inode info and the + * vfs inode. + */ +static struct kmem_cache *sharefs_inode_cachep; + +/* final actions when unmounting a file system */ +static void sharefs_put_super(struct super_block *sb) +{ + struct sharefs_sb_info *spd; + struct super_block *s; + + spd = SHAREFS_SB(sb); + if (!spd) + return; + + /* decrement lower super references */ + s = sharefs_lower_super(sb); + sharefs_set_lower_super(sb, NULL); + atomic_dec(&s->s_active); + + kfree(spd); + sb->s_fs_info = NULL; +} + +static int sharefs_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + int err; + struct path lower_path; + + sharefs_get_lower_path(dentry, &lower_path); + err = vfs_statfs(&lower_path, buf); + sharefs_put_lower_path(dentry, &lower_path); + + /* set return buf to our f/s to avoid confusing user-level utils */ + buf->f_type = SHAREFS_SUPER_MAGIC; + + return err; +} + +/* + * Called by iput() when the inode reference count reached zero + * and the inode is not hashed anywhere. Used to clear anything + * that needs to be, before the inode is completely destroyed and put + * on the inode free list. + */ +static void sharefs_evict_inode(struct inode *inode) +{ + struct inode *lower_inode; + + truncate_inode_pages(&inode->i_data, 0); + clear_inode(inode); + /* + * Decrement a reference to a lower_inode, which was incremented + * by our read_inode when it was created initially. + */ + lower_inode = sharefs_lower_inode(inode); + sharefs_set_lower_inode(inode, NULL); + iput(lower_inode); +} + +void __sharefs_log(const char *level, const bool ratelimited, + const char *function, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + if (ratelimited) + printk_ratelimited("%s sharefs: %s() %pV\n", level, + function, &vaf); + else + printk("%s sharefs: %s() %pV\n", level, function, &vaf); + va_end(args); +} + +static struct inode *sharefs_alloc_inode(struct super_block *sb) +{ + struct sharefs_inode_info *i; + + i = kmem_cache_alloc(sharefs_inode_cachep, GFP_KERNEL); + if (!i) + return NULL; + + /* memset everything up to the inode to 0 */ + memset(i, 0, offsetof(struct sharefs_inode_info, vfs_inode)); + + atomic64_set(&i->vfs_inode.i_version, 1); + return &i->vfs_inode; +} + +static void sharefs_destroy_inode(struct inode *inode) +{ + kmem_cache_free(sharefs_inode_cachep, SHAREFS_I(inode)); +} + +/* sharefs inode cache constructor */ +static void init_once(void *obj) +{ + struct sharefs_inode_info *i = obj; + + inode_init_once(&i->vfs_inode); +} + +int sharefs_init_inode_cache(void) +{ + int err = 0; + + sharefs_inode_cachep = + kmem_cache_create("sharefs_inode_cache", + sizeof(struct sharefs_inode_info), 0, + SLAB_RECLAIM_ACCOUNT, init_once); + if (!sharefs_inode_cachep) + err = -ENOMEM; + return err; +} + +/* sharefs inode cache destructor */ +void sharefs_destroy_inode_cache(void) +{ + if (sharefs_inode_cachep) + kmem_cache_destroy(sharefs_inode_cachep); +} + +const struct super_operations sharefs_sops = { + .put_super = sharefs_put_super, + .statfs = sharefs_statfs, + .evict_inode = sharefs_evict_inode, + .alloc_inode = sharefs_alloc_inode, + .destroy_inode = sharefs_destroy_inode, +};