Viewing: wbcfs.c
// SPDX-License-Identifier: GPL-2.0
/*
* lustre/osd-wbcfs/osd_wbcfs.c
*
* Author: Yingjin Qian <qian@ddn.com>
*/
#define DEBUG_SUBSYSTEM S_OSD
#include <linux/namei.h>
#include <linux/file.h>
#include <linux/uidgid.h>
#include <linux/types.h>
#include <lustre_compat/linux/fs.h>
#include <linux/dirent.h>
#include <linux/xattr.h>
#include <linux/swap.h>
#include <linux/statfs.h>
#include <linux/fs_context.h>
#include <lustre_compat/linux/uio.h>
#include <lustre_compat.h>
#include "wbcfs.h"
#ifndef HAVE_USER_NAMESPACE_ARG
#define inode_init_owner(ns, inode, dir, mode) \
inode_init_owner(inode, dir, mode)
#define memfs_mknod(ns, dir, dch, mode, rd) memfs_mknod(dir, dch, mode, rd)
#define memfs_create_nd(ns, dir, de, mode, ex) \
memfs_create_nd(dir, de, mode, ex)
#endif /* HAVE_USER_NAMESPCE_ARG */
/*
* In-memory xattr entry.
* Borrowed from osd-ldiskfs @osd_xattr_entry and @simple_xattrs in Linux
* kernel. This part of codes in-memory XATTRs should put into libcfs module.
* The first part of @mxe_buf is XATTR name, and is '\0' terminated.
* The left part is for value, binary mode.
*/
struct mem_xattr_entry {
struct list_head mxe_list;
size_t mxe_len;
size_t mxe_namelen;
bool mxe_exist;
struct rcu_head mxe_rcu;
char mxe_buf[];
};
static int mem_xattr_get(struct mem_xattrs *xattrs, const char *name,
void *buf, size_t len)
{
struct mem_xattr_entry *mxe = NULL;
struct mem_xattr_entry *tmp;
size_t namelen = strlen(name);
int rc;
ENTRY;
rcu_read_lock();
list_for_each_entry_rcu(tmp, &xattrs->mex_xattr_list, mxe_list) {
if (namelen == tmp->mxe_namelen &&
strncmp(name, tmp->mxe_buf, namelen) == 0) {
mxe = tmp;
break;
}
}
if (mxe == NULL)
GOTO(out, rc = -ENODATA);
if (!mxe->mxe_exist)
GOTO(out, rc = -ENODATA);
/* Value length */
rc = mxe->mxe_len - sizeof(*mxe) - mxe->mxe_namelen - 1;
LASSERT(rc > 0);
if (buf == NULL)
GOTO(out, rc);
if (len < rc)
GOTO(out, rc = -ERANGE);
memcpy(buf, &mxe->mxe_buf[namelen + 1], rc);
out:
rcu_read_unlock();
RETURN(rc);
}
static void mem_xattr_free(struct rcu_head *head)
{
struct mem_xattr_entry *mxe;
mxe = container_of(head, struct mem_xattr_entry, mxe_rcu);
OBD_FREE(mxe, mxe->mxe_len);
}
static int mem_xattr_add(struct mem_xattrs *xattrs, const char *name,
const char *buf, int buflen)
{
struct mem_xattr_entry *mxe;
struct mem_xattr_entry *old = NULL;
struct mem_xattr_entry *tmp;
size_t namelen = strlen(name);
size_t len = sizeof(*mxe) + namelen + 1 + buflen;
ENTRY;
OBD_ALLOC(mxe, len);
if (mxe == NULL)
RETURN(-ENOMEM);
INIT_LIST_HEAD(&mxe->mxe_list);
mxe->mxe_len = len;
mxe->mxe_namelen = namelen;
memcpy(mxe->mxe_buf, name, namelen);
if (buflen > 0) {
LASSERT(buf != NULL);
memcpy(mxe->mxe_buf + namelen + 1, buf, buflen);
mxe->mxe_exist = true;
} else {
mxe->mxe_exist = false;
}
/* This should be rarely called, just remove old and add new */
spin_lock(&xattrs->mex_lock);
list_for_each_entry(tmp, &xattrs->mex_xattr_list, mxe_list) {
if (namelen == tmp->mxe_namelen &&
strncmp(name, tmp->mxe_buf, namelen) == 0) {
old = tmp;
break;
}
}
if (old != NULL) {
list_replace_rcu(&old->mxe_list, &mxe->mxe_list);
call_rcu(&old->mxe_rcu, mem_xattr_free);
} else {
list_add_tail_rcu(&mxe->mxe_list, &xattrs->mex_xattr_list);
}
spin_unlock(&xattrs->mex_lock);
RETURN(0);
}
static void mem_xattr_del(struct mem_xattrs *xattrs, const char *name)
{
struct mem_xattr_entry *mxe;
size_t namelen = strlen(name);
spin_lock(&xattrs->mex_lock);
list_for_each_entry(mxe, &xattrs->mex_xattr_list, mxe_list) {
if (namelen == mxe->mxe_namelen &&
strncmp(name, mxe->mxe_buf, namelen) == 0) {
list_del_rcu(&mxe->mxe_list);
call_rcu(&mxe->mxe_rcu, mem_xattr_free);
break;
}
}
spin_unlock(&xattrs->mex_lock);
}
static inline void mem_xattrs_init(struct mem_xattrs *xattrs)
{
INIT_LIST_HEAD(&xattrs->mex_xattr_list);
spin_lock_init(&xattrs->mex_lock);
}
static void mem_xattrs_fini(struct mem_xattrs *xattrs)
{
struct mem_xattr_entry *mxe, *next;
list_for_each_entry_safe(mxe, next, &xattrs->mex_xattr_list, mxe_list) {
list_del(&mxe->mxe_list);
OBD_FREE(mxe, mxe->mxe_len);
}
}
int memfs_xattr_get(struct inode *inode, void *buf, size_t len,
const char *name)
{
return mem_xattr_get(&MEMFS_I(inode)->mei_xattrs, name, buf, len);
}
int memfs_xattr_set(struct inode *inode, void *buf, size_t len,
const char *name, int flags)
{
return mem_xattr_add(&MEMFS_I(inode)->mei_xattrs, name, buf, len);
}
void memfs_xattr_del(struct inode *inode, const char *name)
{
mem_xattr_del(&MEMFS_I(inode)->mei_xattrs, name);
}
static const struct super_operations memfs_ops;
static const struct address_space_operations memfs_aops;
static const struct file_operations memfs_file_operations;
static const struct inode_operations memfs_inode_operations;
static const struct file_operations memfs_dir_operations;
static const struct inode_operations memfs_dir_inode_operations;
static struct file_system_type memfs_fstype;
static inline struct memfs_sb_info *MEMFS_SB(struct super_block *sb)
{
return sb->s_fs_info;
}
static int memfs_reserve_inode(struct super_block *sb)
{
return 0;
}
static void memfs_free_inode(struct super_block *sb)
{
}
struct inode *memfs_create_inode(struct super_block *sb, struct inode *dir,
umode_t mode, struct iattr *iattr, dev_t dev,
bool update_link)
{
struct memfs_sb_info *sbinfo = MEMFS_SB(sb);
struct memfs_inode_info *mei;
struct inode *inode;
ENTRY;
inode = new_inode(sb);
if (!inode)
RETURN(ERR_PTR(-ENOMEM));
if (iattr) {
uid_t owner[2] = { 0, 0 };
if (iattr->ia_valid & ATTR_UID)
owner[0] = from_kuid(&init_user_ns, iattr->ia_uid);
if (iattr->ia_valid & ATTR_GID)
owner[1] = from_kgid(&init_user_ns, iattr->ia_gid);
inode->i_mode = mode;
i_uid_write(inode, owner[0]);
i_gid_write(inode, owner[1]);
} else {
inode_init_owner(&nop_mnt_idmap, inode, dir, mode);
}
if (iattr) {
if (iattr->ia_valid & ATTR_CTIME)
inode_set_ctime_to_ts(inode, iattr->ia_ctime);
if (iattr->ia_valid & ATTR_MTIME)
inode_set_mtime_to_ts(inode, iattr->ia_mtime);
if (iattr->ia_valid & ATTR_ATIME)
inode_set_atime_to_ts(inode, iattr->ia_atime);
}
inode->i_blocks = 0;
mei = MEMFS_I(inode);
mei->mei_crtime = inode_get_mtime(inode);
mem_xattrs_init(&mei->mei_xattrs);
mei->mei_index_type = INDEX_TYPE_NONE;
cache_no_acl(inode);
if (sbinfo->msi_noswap)
mapping_set_unevictable(inode->i_mapping);
switch (mode & S_IFMT) {
case S_IFREG:
inode->i_mapping->a_ops = &memfs_aops;
inode->i_op = &memfs_inode_operations;
inode->i_fop = &memfs_file_operations;
break;
case S_IFDIR:
if (update_link)
inc_nlink(inode);
/* Some things misbehave if size == 0 on a directory */
inode->i_size = 2 * BOGO_DIRENT_SIZE;
inode->i_op = &memfs_dir_inode_operations;
inode->i_fop = &memfs_dir_operations;
break;
case S_IFLNK:
break;
default:
CERROR("Unsupport file mode %#o\n", mode);
iput(inode);
/*
* TODO: Add support for other file types.
* Fix the error in sanity/test_28.
*/
RETURN(ERR_PTR(-EOPNOTSUPP));
}
return inode;
}
static int memfs_mknod(struct mnt_idmap *map, struct inode *dir,
struct dentry *dentry, umode_t mode, dev_t dev)
{
struct inode *inode;
ENTRY;
inode = memfs_create_inode(dir->i_sb, dir, mode, NULL, dev, true);
if (IS_ERR(inode))
RETURN(PTR_ERR(inode));
dir->i_size += BOGO_DIRENT_SIZE;
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
d_make_persistent(dentry, inode);
RETURN(0);
}
static inline int do_mkdir(struct mnt_idmap *map, struct inode *dir,
struct dentry *dchild, umode_t mode)
{
int rc;
rc = memfs_mknod(map, dir, dchild, mode | S_IFDIR, 0);
if (rc)
return rc;
inc_nlink(dir);
return 0;
}
#ifdef HAVE_IOPS_MKDIR_RETURNS_DENTRY
static struct dentry *memfs_mkdir(struct mnt_idmap *map, struct inode *dir,
struct dentry *dchild, umode_t mode)
{
int rc = do_mkdir(map, dir, dchild, mode);
if (rc)
return ERR_PTR(rc);
return NULL;
}
#elif defined HAVE_USER_NAMESPACE_ARG
static int memfs_mkdir(struct mnt_idmap *map, struct inode *dir,
struct dentry *dchild, umode_t mode)
{
return do_mkdir(map, dir, dchild, mode);
}
#else
static int memfs_mkdir(struct inode *dir, struct dentry *dchild, umode_t mode)
{
return do_mkdir(NULL, dir, dchild, mode);
}
#endif
static int memfs_create_nd(struct mnt_idmap *map, struct inode *dir,
struct dentry *dentry, umode_t mode, bool want_excl)
{
return memfs_mknod(map, dir, dentry, mode | S_IFREG, 0);
}
static int memfs_unlink(struct inode *dir, struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
if (inode->i_nlink > 1 && !S_ISDIR(inode->i_mode))
memfs_free_inode(inode->i_sb);
dir->i_size -= BOGO_DIRENT_SIZE;
inode_set_mtime_to_ts(dir, inode_set_ctime_to_ts(dir,
inode_set_ctime_current(inode)));
inode_inc_iversion(dir);
drop_nlink(inode);
d_make_discardable(dentry);
return 0;
}
static int memfs_rmdir(struct inode *dir, struct dentry *dchild)
{
if (!simple_empty(dchild))
return -ENOTEMPTY;
drop_nlink(d_inode(dchild));
drop_nlink(dir);
return memfs_unlink(dir, dchild);
}
static int memfs_link(struct dentry *old_dentry, struct inode *dir,
struct dentry *dentry)
{
struct inode *inode = d_inode(old_dentry);
ENTRY;
/*
* No ordinary (disk based) filesystem counts links as inodes;
* but each new link needs a new dentry, pinning lowmem, and
* tmpfs dentries cannot be pruned until they are unlinked.
* But if an O_TMPFILE file is linked into the tmpfs, the
* first link must skip that, to get the accounting right.
*/
if (inode->i_nlink) {
int rc = 0;
rc = memfs_reserve_inode(inode->i_sb);
if (rc)
RETURN(rc);
}
dir->i_size += BOGO_DIRENT_SIZE;
inode_set_mtime_to_ts(dir, inode_set_ctime_to_ts(dir,
inode_set_ctime_current(inode)));
inode_inc_iversion(dir);
inc_nlink(inode);
ihold(inode); /* New dentry reference */
d_make_persistent(dentry, inode);
return 0;
}
#ifdef HAVE_DENTRY_D_CHILDREN
/* parent is locked at least shared */
/*
* Returns an element of siblings' list.
* We are looking for <count>th positive after <p>; if
* found, dentry is grabbed and returned to caller.
* If no such element exists, NULL is returned.
*/
static struct dentry *scan_positives(struct dentry *cursor,
struct hlist_node **p,
loff_t count,
struct dentry *last)
{
struct dentry *dentry = cursor->d_parent, *found = NULL;
spin_lock(&dentry->d_lock);
while (*p) {
struct dentry *d = hlist_entry(*p, struct dentry, d_sib);
p = &d->d_sib.next;
// we must at least skip cursors, to avoid livelocks
if (d->d_flags & DCACHE_DENTRY_CURSOR)
continue;
if (simple_positive(d) && !--count) {
spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED);
if (simple_positive(d))
found = dget_dlock(d);
spin_unlock(&d->d_lock);
if (likely(found))
break;
count = 1;
}
if (need_resched()) {
if (!hlist_unhashed(&cursor->d_sib))
__hlist_del(&cursor->d_sib);
hlist_add_behind(&cursor->d_sib, &d->d_sib);
p = &cursor->d_sib.next;
spin_unlock(&dentry->d_lock);
cond_resched();
spin_lock(&dentry->d_lock);
}
}
spin_unlock(&dentry->d_lock);
dput(last);
return found;
}
/*
* Directory is locked and all positive dentries in it are safe, since
* for ramfs-type trees they can't go away without unlink() or rmdir(),
* both impossible due to the lock on directory.
*/
static int memfs_dcache_readdir(struct file *file, struct dir_context *ctx)
{
struct dentry *dentry = file->f_path.dentry;
struct dentry *cursor = file->private_data;
struct memfs_dir_context *mctx = (struct memfs_dir_context *)ctx;
struct dentry *next = NULL;
struct hlist_node **p;
if (!dir_emit_dots(file, ctx))
return 0;
if (ctx->pos == 2)
p = &dentry->d_children.first;
else
p = &cursor->d_sib.next;
while ((next = scan_positives(cursor, p, 1, next)) != NULL) {
mctx->dentry = next;
if (!dir_emit(ctx, next->d_name.name, next->d_name.len,
d_inode(next)->i_ino,
fs_umode_to_dtype(d_inode(next)->i_mode)))
break;
ctx->pos++;
p = &next->d_sib.next;
}
spin_lock(&dentry->d_lock);
hlist_del_init(&cursor->d_sib);
if (next)
hlist_add_before(&cursor->d_sib, &next->d_sib);
spin_unlock(&dentry->d_lock);
dput(next);
return 0;
}
#else /* !HAVE_DENTRY_D_CHILDREN */
/* Relationship between i_mode and the DT_xxx types */
static inline unsigned char dt_type(struct inode *inode)
{
return (inode->i_mode >> 12) & 15;
}
/*
* linux/fs/libfs.c: simple_positive()
* Public in linux/include/linux/dcache.h
* kernel 4.1-rc3 commit dc3f4198eac14e52a98dfc79cd84b45e280f59cd
*/
static inline int __simple_positive(struct dentry *dentry)
{
return dentry->d_inode && !d_unhashed(dentry);
}
/*
* Returns an element of siblings' list.
* We are looking for <count>th positive after <p>; if
* found, dentry is grabbed and returned to caller.
* If no such element exists, NULL is returned.
*/
/* parent is locked at least shared */
static struct dentry *scan_positives(struct dentry *cursor,
struct list_head *p,
loff_t count,
struct dentry *last)
{
struct dentry *dentry = cursor->d_parent, *found = NULL;
spin_lock(&dentry->d_lock);
while ((p = p->next) != &dentry->d_subdirs) {
struct dentry *d = list_entry(p, struct dentry, d_child);
/* We must at least skip cursors, to avoid livelocks */
if (d->d_flags & DCACHE_DENTRY_CURSOR)
continue;
if (__simple_positive(d) && !--count) {
spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED);
if (__simple_positive(d))
found = dget_dlock(d);
spin_unlock(&d->d_lock);
if (likely(found))
break;
count = 1;
}
if (need_resched()) {
list_move(&cursor->d_child, p);
p = &cursor->d_child;
spin_unlock(&dentry->d_lock);
cond_resched();
spin_lock(&dentry->d_lock);
}
}
spin_unlock(&dentry->d_lock);
dput(last);
return found;
}
/* linux/fs/libfs.c: dcache_readdir() */
/*
* Directory is locked and all positive dentries in it are safe, since
* for ramfs-type trees they can't go away without unlink() or rmdir(),
* both impossible due to the lock on directory.
*/
static int memfs_dcache_readdir(struct file *file, struct dir_context *ctx)
{
struct dentry *dentry = file->f_path.dentry;
struct dentry *cursor = file->private_data;
struct list_head *anchor = &dentry->d_subdirs;
struct memfs_dir_context *mctx = (struct memfs_dir_context *)ctx;
struct dentry *next = NULL;
struct list_head *p;
if (!dir_emit_dots(file, ctx))
return 0;
if (ctx->pos == 2)
p = anchor;
else if (!list_empty(&cursor->d_child))
p = &cursor->d_child;
else
return 0;
while ((next = scan_positives(cursor, p, 1, next)) != NULL) {
/*
* TODO: Add a new f_flags O_HAVE_DIR_CONTEXT_EXT to
* distinguish the normal readdir() access from the user space.
*/
mctx->dentry = next;
if (!dir_emit(ctx, next->d_name.name, next->d_name.len,
d_inode(next)->i_ino, dt_type(d_inode(next))))
break;
ctx->pos++;
p = &next->d_child;
}
spin_lock(&dentry->d_lock);
if (next)
list_move_tail(&cursor->d_child, &next->d_child);
else
list_del_init(&cursor->d_child);
spin_unlock(&dentry->d_lock);
dput(next);
return 0;
}
#endif /* HAVE_DENTRY_D_CHILDREN */
/*
* Copied from @simple_write_end in the kernel.
* It does not export on the new kernel such as rhel9.
*/
static int memfs_write_end(
#ifdef HAVE_WRITE_BEGIN_KIOCB
const struct kiocb *kiocb,
#else
struct file *file,
#endif
struct address_space *mapping,
loff_t pos, unsigned int len, unsigned int copied,
struct wbe_folio *vmfolio, void *fsdata)
{
struct page *page = wbe_folio_page(vmfolio);
struct inode *inode = page->mapping->host;
loff_t last_pos = pos + copied;
/* zero the stale part of the page if we did a short copy */
if (!PageUptodate(page)) {
if (copied < len) {
unsigned int from = pos & (PAGE_SIZE - 1);
unsigned int start = from + copied;
unsigned int size = len - copied;
zero_user_segments(page, start, start + size, 0, 0);
}
SetPageUptodate(page);
}
/*
* No need to use i_size_read() here, the i_size
* cannot change under us because we hold the i_mutex.
*/
if (last_pos > inode->i_size)
i_size_write(inode, last_pos);
set_page_dirty(page);
unlock_page(page);
put_page(page);
return copied;
}
/* TODO: implement file splice read/write interface for MemFS. */
static ssize_t memfs_file_splice_read(struct file *in_file, loff_t *ppos,
struct pipe_inode_info *pipe,
size_t count, unsigned int flags)
{
RETURN(0);
}
/*
* linux/mm/shmem.c
* TODO: mmap support.
*/
static int memfs_getpage(struct inode *inode, pgoff_t index,
struct page **pagep)
{
struct address_space *mapping = inode->i_mapping;
struct page *page;
if (index > (MAX_LFS_FILESIZE >> PAGE_SHIFT))
return -EFBIG;
page = find_lock_page(mapping, index);
/* fallocated page? */
if (page && !PageUptodate(page)) {
unlock_page(page);
put_page(page);
page = NULL;
}
*pagep = page;
return 0;
}
/* linux/mm/shmem.c shmem_file_read_iter() */
static ssize_t memfs_file_read_iter(struct kiocb *iocb,
struct iov_iter *to)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct address_space *mapping = inode->i_mapping;
loff_t *ppos = &iocb->ki_pos;
unsigned long offset;
ssize_t retval = 0;
pgoff_t index;
int error = 0;
ENTRY;
/*
* Might this read be for a stacking filesystem? Then when reading
* holes of a sparse file, we actually need to allocate those pages,
* and even mark them dirty, so it cannot exceed the max_blocks limit.
*/
index = *ppos >> PAGE_SHIFT;
offset = *ppos & ~PAGE_MASK;
for (;;) {
struct page *page = NULL;
pgoff_t end_index;
unsigned long nr, ret;
loff_t i_size = i_size_read(inode);
end_index = i_size >> PAGE_SHIFT;
if (index > end_index)
break;
if (index == end_index) {
nr = i_size & ~PAGE_MASK;
if (nr <= offset)
break;
}
error = memfs_getpage(inode, index, &page);
if (error) {
if (error == -EINVAL)
error = 0;
break;
}
if (page)
unlock_page(page);
/*
* We must evaluate after, since reads (unlike writes)
* are called without i_mutex protection against truncate
*/
nr = PAGE_SIZE;
i_size = i_size_read(inode);
end_index = i_size >> PAGE_SHIFT;
if (index == end_index) {
nr = i_size & ~PAGE_MASK;
if (nr <= offset) {
if (page)
put_page(page);
break;
}
}
nr -= offset;
if (page) {
/*
* If users can be writing to this page using arbitrary
* virtual addresses, take care about potential aliasing
* before reading the page on the kernel side.
*/
if (mapping_writably_mapped(mapping))
flush_dcache_page(page);
/*
* Mark the page accessed if we read the beginning.
*/
if (!offset)
mark_page_accessed(page);
} else {
page = ZERO_PAGE(0);
get_page(page);
}
/*
* Ok, we have the page, and it's up-to-date, so
* now we can copy it to user space...
*/
ret = copy_page_to_iter(page, offset, nr, to);
retval += ret;
offset += ret;
index += offset >> PAGE_SHIFT;
offset &= ~PAGE_MASK;
put_page(page);
if (!iov_iter_count(to))
break;
if (ret < nr) {
error = -EFAULT;
break;
}
cond_resched();
}
*ppos = ((loff_t) index << PAGE_SHIFT) + offset;
file_accessed(file);
return retval ? retval : error;
}
/* TODO: space limiting for write. */
static ssize_t memfs_file_write_iter(struct kiocb *iocb,
struct iov_iter *iter)
{
RETURN(generic_file_write_iter(iocb, iter));
}
static void memfs_put_super(struct super_block *sb)
{
struct memfs_sb_info *sbinfo = MEMFS_SB(sb);
OBD_FREE_PTR(sbinfo);
sb->s_fs_info = NULL;
}
static int memfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct memfs_options *ctx = fc->fs_private;
struct memfs_sb_info *sbinfo;
struct inode *inode;
int rc;
ENTRY;
OBD_ALLOC_PTR(sbinfo);
if (!sbinfo)
return -ENOMEM;
sb->s_fs_info = sbinfo;
sb->s_flags |= SB_NOUSER | SB_NOSEC;
sbinfo->msi_uid = ctx->meo_uid;
sbinfo->msi_gid = ctx->meo_gid;
sbinfo->msi_mode = ctx->meo_mode;
sbinfo->msi_max_blocks = ctx->meo_blocks;
sbinfo->msi_free_inodes = sbinfo->msi_max_inodes = ctx->meo_inodes;
/* Swap space for the larger capacity is not supported. */
sbinfo->msi_noswap = true;
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = WBCFS_MAGIC;
sb->s_op = &memfs_ops;
sb_dentry_not_cache(sb);
sb->s_time_gran = 1;
uuid_gen(&sb->s_uuid);
inode = memfs_create_inode(sb, NULL, S_IFDIR | sbinfo->msi_mode,
NULL, 0, true);
if (IS_ERR(inode))
GOTO(out_fail, rc = PTR_ERR(inode));
inode->i_uid = sbinfo->msi_uid;
inode->i_gid = sbinfo->msi_gid;
sb->s_root = d_make_root(inode);
if (!sb->s_root)
GOTO(out_fail, rc = -ENOMEM);
RETURN(0);
out_fail:
memfs_put_super(sb);
RETURN(rc);
}
static int memfs_get_tree(struct fs_context *fc)
{
return get_tree_nodev(fc, memfs_fill_super);
}
static void memfs_free_fc(struct fs_context *fc)
{
struct memfs_options *ctx = fc->fs_private;
OBD_FREE_PTR(ctx);
}
static const struct fs_context_operations memfs_context_ops = {
.free = memfs_free_fc,
.get_tree = memfs_get_tree,
};
static int memfs_init_fs_context(struct fs_context *fc)
{
struct memfs_options *ctx;
OBD_ALLOC_PTR(ctx);
if (!ctx)
return -ENOMEM;
ctx->meo_mode = 0777 | S_ISVTX;
ctx->meo_uid = current_fsuid();
ctx->meo_gid = current_fsgid();
fc->fs_private = ctx;
fc->ops = &memfs_context_ops;
return 0;
}
static struct kmem_cache *memfs_inode_cachep;
static struct inode *memfs_alloc_inode(struct super_block *sb)
{
struct memfs_inode_info *mei;
mei = kmem_cache_alloc(memfs_inode_cachep, GFP_KERNEL);
if (!mei)
return NULL;
return &mei->mei_vfs_inode;
}
static void memfs_destroy_callback(struct rcu_head *head)
{
struct inode *inode = container_of(head, struct inode, i_rcu);
ENTRY;
/* TOOD: free symlink name. */
kmem_cache_free(memfs_inode_cachep, MEMFS_I(inode));
EXIT;
}
static void memfs_destroy_inode(struct inode *inode)
{
struct memfs_inode_info *mei = MEMFS_I(inode);
if (mei->mei_index_type == INDEX_TYPE_HASH)
hash_index_fini(&mei->mei_hash_index);
call_rcu(&inode->i_rcu, memfs_destroy_callback);
}
static void memfs_init_inode(void *foo)
{
struct memfs_inode_info *mei = (struct memfs_inode_info *)foo;
inode_init_once(&mei->mei_vfs_inode);
}
static void memfs_init_inodecache(void)
{
memfs_inode_cachep = kmem_cache_create("memfs_inode_cache",
sizeof(struct memfs_inode_info),
0, SLAB_PANIC | SLAB_ACCOUNT,
memfs_init_inode);
}
static void memfs_destroy_inodecache(void)
{
kmem_cache_destroy(memfs_inode_cachep);
}
static inline bool memfs_mapping(struct address_space *mapping)
{
return mapping->a_ops == &memfs_aops;
}
static void memfs_evict_inode(struct inode *inode)
{
struct memfs_inode_info *mei = MEMFS_I(inode);
if (memfs_mapping(inode->i_mapping)) {
inode->i_size = 0;
mapping_set_exiting(inode->i_mapping);
truncate_inode_pages_range(inode->i_mapping, 0, (loff_t)-1);
}
mem_xattrs_fini(&mei->mei_xattrs);
memfs_free_inode(inode->i_sb);
clear_inode(inode);
}
static int memfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct memfs_sb_info *sbinfo = MEMFS_SB(dentry->d_sb);
buf->f_type = WBCFS_MAGIC;
buf->f_bsize = PAGE_SIZE;
buf->f_namelen = NAME_MAX;
if (sbinfo->msi_max_blocks) {
buf->f_blocks = sbinfo->msi_max_blocks;
buf->f_bavail =
buf->f_bfree = sbinfo->msi_max_blocks -
percpu_counter_sum(&sbinfo->msi_used_blocks);
}
if (sbinfo->msi_max_inodes) {
buf->f_files = sbinfo->msi_max_inodes;
buf->f_ffree = sbinfo->msi_free_inodes;
}
/* else leave those fields 0 like simple_statfs */
return 0;
}
static const struct super_operations memfs_ops = {
.alloc_inode = memfs_alloc_inode,
.destroy_inode = memfs_destroy_inode,
.statfs = memfs_statfs,
.evict_inode = memfs_evict_inode,
.drop_inode = inode_just_drop,
.put_super = memfs_put_super,
};
/*
* TODO: Using the new kernel data structure Maple Tree:
* @simple_offset_dir_operations to manage and access the dentries
* within a directory. It is much efficient than linear list.
*/
static const struct file_operations memfs_dir_operations = {
.open = dcache_dir_open,
.release = dcache_dir_close,
.llseek = dcache_dir_lseek,
.read = generic_read_dir,
.iterate_shared = memfs_dcache_readdir,
.fsync = noop_fsync,
};
static const struct inode_operations memfs_dir_inode_operations = {
.mknod = memfs_mknod,
.lookup = simple_lookup,
.create = memfs_create_nd,
.unlink = memfs_unlink,
.mkdir = memfs_mkdir,
.rmdir = memfs_rmdir,
.link = memfs_link,
.setattr = simple_setattr,
.getattr = simple_getattr,
};
static const struct file_operations memfs_file_operations = {
.read_iter = memfs_file_read_iter,
.write_iter = memfs_file_write_iter,
.mmap = generic_file_mmap,
.llseek = generic_file_llseek,
.splice_read = memfs_file_splice_read,
.fsync = noop_fsync,
};
static const struct address_space_operations memfs_aops = {
#ifdef HAVE_DIRTY_FOLIO
.dirty_folio = noop_dirty_folio,
#else
/*
* TODO: reimplemet ->set_page_dirty() interface.
* - The call __set_page_dirty_nobuffers will mark the inode dirty and
* put the inode into the writeback control list. Instead, it would
* better to call mark_inode_dirty() only one time when close the file
* once the file data was modified.
* - Here it can be optimized to use light weight function:
* __set_page_dirty_no_writeback(); The writeback related data
* structure can be delayed to initilize during data assimliation.
*/
.set_page_dirty = __set_page_dirty_nobuffers,
#endif
.write_begin = simple_write_begin,
.write_end = memfs_write_end,
};
static void memfs_kill_super(struct super_block *sb)
{
/*
* On kernels that still have kill_litter_super(), it walks the dentry
* tree via d_genocide() to drop the extra refs left by the
* d_instantiate()+dget() compat fallback for d_make_persistent().
* Newer kernels expose neither, but kill_anon_super() now strips
* DCACHE_PERSISTENT during shrink_dcache_for_umount() instead.
*/
#ifdef HAVE_KILL_LITTER_SUPER
kill_litter_super(sb);
#else
kill_anon_super(sb);
#endif
}
static struct file_system_type memfs_fstype = {
.owner = THIS_MODULE,
.name = "wbcfs",
.init_fs_context = memfs_init_fs_context,
.kill_sb = memfs_kill_super,
.fs_flags = FS_USERNS_MOUNT,
};
int memfs_init(void)
{
int rc;
memfs_init_inodecache();
rc = register_filesystem(&memfs_fstype);
if (rc)
memfs_destroy_inodecache();
return rc;
}
void memfs_fini(void)
{
unregister_filesystem(&memfs_fstype);
memfs_destroy_inodecache();
}