| /* |
| * Copyright 2016, NICTA |
| * |
| * This software may be distributed and modified according to the terms of |
| * the GNU General Public License version 2. Note that NO WARRANTY is provided. |
| * See "LICENSE_GPLv2.txt" for details. |
| * |
| * @TAG(NICTA_GPL) |
| */ |
| |
| #include <linux/version.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/namei.h> |
| #include <linux/ctype.h> |
| #include <linux/parser.h> |
| #include <linux/seq_file.h> |
| #include <linux/mount.h> |
| #include <bilbyfs.h> |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0) |
| #include <uapi/linux/mount.h> |
| #endif |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,6,0) |
| #define put_page(page) page_cache_release(page) |
| #endif |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,18,0) |
| #define timespec64_trunc timespec_trunc |
| #endif |
| |
| static int init_inode_by_type(struct backing_dev_info *bdi, struct inode *inode) |
| { |
| int err = 0; |
| |
| /* Disable read-ahead */ |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) |
| inode->i_mapping->backing_dev_info = bdi; |
| #endif |
| |
| switch (inode->i_mode & S_IFMT) { |
| case S_IFREG: |
| inode->i_mapping->a_ops = &bilbyfs_file_address_operations; |
| inode->i_op = &bilbyfs_file_inode_operations; |
| inode->i_fop = &bilbyfs_file_operations; |
| break; |
| case S_IFDIR: |
| inode->i_op = &bilbyfs_dir_inode_operations; |
| inode->i_fop = &bilbyfs_dir_operations; |
| inode->i_size = BILBYFS_INODE_SZ; |
| break; |
| case S_IFLNK: |
| /* Symlinks have data blocks handled through the page cache */ |
| // inode->i_mapping->a_ops = &bilbyfs_file_address_operations; |
| inode->i_op = &bilbyfs_symlink_inode_operations; |
| inode->i_link = NULL; |
| break; |
| case S_IFBLK: |
| case S_IFCHR: |
| case S_IFSOCK: |
| case S_IFIFO: |
| bilbyfs_err("Inode type not yet supported (%x)", |
| (unsigned int)(inode->i_mode & S_IFMT)); |
| err = -EINVAL; |
| break; |
| default: |
| bilbyfs_err("Unknown inode type. This should not happen!"); |
| err = -EINVAL; |
| break; |
| } |
| return err; |
| } |
| |
| static struct inode *bilbyfs_iget(struct bilbyfs_info *bi, unsigned long inum) |
| { |
| struct inode *inode; |
| int err = 0; |
| |
| inode = iget_locked(bi->vfs_sb, inum); |
| if (!inode) |
| return ERR_PTR(-ENOMEM); |
| if (!(inode->i_state & I_NEW)) |
| return inode; |
| down(&bi->wd.lock); |
| err = fsop_iget(bi, inum, inode); |
| up(&bi->wd.lock); |
| if (!err) { |
| init_inode_by_type(bi->vfs_sb->s_bdi, inode); |
| unlock_new_inode(inode); |
| return inode; |
| } |
| bilbyfs_err("BilbyFsError: fsop_iget(ino=%lu) = %d\n", inum, err); |
| iget_failed(inode); |
| return ERR_PTR(err); |
| } |
| |
| static struct dentry *bilbyfs_lookup(struct inode *dir, struct dentry *dentry, |
| unsigned int flags) |
| { |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| struct inode *inode; |
| ino_t inum; |
| int err; |
| |
| down(&bi->wd.lock); |
| err = fsop_lookup(bi, dir, dentry->d_name.name, &inum); |
| up(&bi->wd.lock); |
| if (err == -ENOENT) { |
| return NULL; |
| } |
| if (!err) { |
| inode = bilbyfs_iget(bi, inum); |
| if (!IS_ERR(inode)) { |
| d_add(dentry, inode); |
| return NULL; |
| } |
| err = PTR_ERR(inode); |
| } |
| bilbyfs_err("BilbyError: bilbyfs_lookup(dir.ino = %lu, name=%.*s, flags=%x)=%d\n", dir->i_ino, dentry->d_name.len, dentry->d_name.name, flags, err); |
| return ERR_PTR(err); |
| } |
| |
| /* Allocates and initialises an inode. |
| * @dir: pointer to the directory inode, is NULL iff we allocate root. |
| * @mode: type + permissions of the inode |
| * |
| * The function returns a pointer to the inode or a negative error-code. |
| */ |
| static struct inode *bilbyfs_new_inode(struct super_block *sb, |
| struct inode *dir, umode_t mode) |
| { |
| struct inode *inode; |
| int err; |
| |
| bilbyfs_debug("bilbyfs_new_inode(mode = 0x%x)\n", mode); |
| inode = new_inode(sb); |
| if (!inode) { |
| bilbyfs_err("BilbyFsError: new_inode() = -ENOMEM"); |
| return ERR_PTR(-ENOMEM); |
| } |
| /* FIXME: UBIFS has the following line that doesn't seem |
| * required, needs to be double checked |
| */ |
| inode->i_mapping->nrpages = 0; |
| inode_init_perm(inode, dir, mode); |
| err = init_inode_by_type(sb->s_bdi, inode); |
| if (!err) |
| return inode; |
| make_bad_inode(inode); |
| iput(inode); |
| bilbyfs_err("BilbyFsError: bilbyfs_new_inode(mode = 0x%x) = %d\n", mode, err); |
| return ERR_PTR(err); |
| } |
| |
| static int bilbyfs_link(struct dentry *old_dentry, struct inode *dir, |
| struct dentry *dentry) |
| { |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| int err; |
| |
| down(&bi->wd.lock); |
| err = fsop_link(dir->i_sb->s_fs_info, old_dentry->d_inode, dir, dentry->d_name.name); |
| up(&bi->wd.lock); |
| if (!err) { |
| ihold(old_dentry->d_inode); |
| d_instantiate(dentry, old_dentry->d_inode); |
| } else |
| bilbyfs_err("BilbyFsError: bilbyfs_link() = %d\n", err); |
| return err; |
| } |
| |
| static int bilbyfs_create(struct inode *dir, struct dentry *dentry, |
| umode_t mode, bool excl) |
| { |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| struct inode *inode; |
| int err; |
| |
| bilbyfs_debug("bilbyfs_create (dir.ino = %lu, name=%.*s, mode=%x)\n", dir->i_ino, dentry->d_name.len, dentry->d_name.name, mode); |
| inode = bilbyfs_new_inode(dir->i_sb, dir, S_IFREG | mode); |
| if (IS_ERR(inode)) |
| return PTR_ERR(inode); |
| down(&bi->wd.lock); |
| err = fsop_create(bi, dir, dentry->d_name.name, mode, excl, inode); |
| up(&bi->wd.lock); |
| if (!err) { |
| insert_inode_hash(inode); |
| d_instantiate(dentry, inode); |
| return 0; |
| } else |
| bilbyfs_err("BilbyFsError: fsop_create() = %d\n", err); |
| make_bad_inode(inode); |
| iput(inode); |
| return err; |
| } |
| |
| static int bilbyfs_unlink(struct inode *dir, struct dentry *dentry) |
| { |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| int err; |
| |
| down(&bi->wd.lock); |
| err = fsop_unlink(bi, dir, dentry->d_name.name, dentry->d_inode); |
| up(&bi->wd.lock); |
| if (err) |
| bilbyfs_err("BilbyFsError: fsop_unlink() = %d\n", err); |
| return err; |
| } |
| |
| static int bilbyfs_rmdir(struct inode *dir, struct dentry *dentry) |
| { |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| int err; |
| |
| down(&bi->wd.lock); |
| err = fsop_rmdir(bi, dir, dentry->d_name.name, dentry->d_inode); |
| up(&bi->wd.lock); |
| if (err) |
| bilbyfs_err("BilbyFsError: fsop_rmdir() = %d\n", err); |
| |
| return err; |
| } |
| |
| static int bilbyfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) |
| { |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| struct inode *inode; |
| int err; |
| |
| bilbyfs_debug("bilbyfs_mkdir (dir.ino = %lu, name=%.*s, mode=%x)\n", dir->i_ino, dentry->d_name.len, dentry->d_name.name, mode); |
| inode = bilbyfs_new_inode(dir->i_sb, dir, S_IFDIR | mode); |
| if (IS_ERR(inode)) |
| return PTR_ERR(inode); |
| down(&bi->wd.lock); |
| err = fsop_mkdir(bi, dir, dentry->d_name.name, mode, inode); |
| up(&bi->wd.lock); |
| if (!err) { |
| insert_inode_hash(inode); |
| d_instantiate(dentry, inode); |
| return 0; |
| } else |
| bilbyfs_err("BilbyFsError: bilbyfs_mkdir() = %d\n", err); |
| make_bad_inode(inode); |
| iput(inode); |
| return err; |
| } |
| |
| static int bilbyfs_readdir(struct file *file, struct dir_context *ctx) |
| { |
| struct dentry *dentry = file->f_path.dentry; |
| struct inode *dir = dentry->d_inode; |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| int err; |
| |
| bilbyfs_debug("bilbyfs_readdir (dir.ino = %lu, name=%.*s, pos=%lld)\n", |
| dir->i_ino, dentry->d_name.len, dentry->d_name.name, ctx->pos); |
| if (ctx->pos == 2) /* pos == 2 means an error occured */ |
| return 0; |
| if (ctx->pos < 2) { |
| if (!dir_emit_dots(file, ctx)) |
| return 0; |
| ctx->pos = 3; |
| } |
| down(&bi->wd.lock); |
| err = fsop_readdir(bi, dir, ctx, (struct fsop_readdir_ctx **) &file->private_data); |
| up(&bi->wd.lock); |
| if (err) { |
| bilbyfs_err("BilbyFsError: bilbyfs_readdir() = %d\n", err); |
| ctx->pos = 2; |
| return err; |
| } |
| return 0; |
| } |
| |
| static int bilbyfs_dir_release(struct inode *dir, struct file *file) |
| { |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| bilbyfs_debug("bilbyfs_dir_release()\n"); |
| down(&bi->wd.lock); |
| fsop_dir_release((struct fsop_readdir_ctx *)file->private_data); |
| up(&bi->wd.lock); |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static loff_t bilbyfs_dir_llseek(struct file *file, loff_t offset, int origin) |
| { |
| struct inode *inode = file->f_path.dentry->d_inode; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| int err; |
| /* When a directory is seeked 2 thing: |
| * - The current bri becomes useless |
| * - Seeking a directory is moronic, we do not support |
| * FIXME: Check whether not supporting is acceptable |
| * The alternative is to ignore, worst case hack something |
| * around to emulate. |
| */ |
| bilbyfs_err("Warning: bilbyfs_dir_llseek(), this function is deprecated!"); |
| down(&bi->wd.lock); |
| err = bilbyfs_dir_release(inode, file); |
| up(&bi->wd.lock); |
| return err; |
| } |
| |
| static int bilbyfs_symlink(struct inode *dir, struct dentry *dentry, |
| const char *symname) |
| { |
| struct bilbyfs_info *bi = dir->i_sb->s_fs_info; |
| struct inode *inode; |
| int err; |
| |
| bilbyfs_debug("bilbyfs_symlink (dir.ino = %lu, name=%.*s, symname=%s)\n", dir->i_ino, dentry->d_name.len, dentry->d_name.name, symname); |
| inode = bilbyfs_new_inode(dir->i_sb, dir, S_IFLNK | S_IRWXUGO); |
| if (IS_ERR(inode)) |
| return PTR_ERR(inode); |
| down(&bi->wd.lock); |
| err = fsop_symlink(bi, dir, dentry->d_name.name, symname, inode); |
| up(&bi->wd.lock); |
| if (!err) { |
| insert_inode_hash(inode); |
| d_instantiate(dentry, inode); |
| return 0; |
| } |
| bilbyfs_err("BilbyFsError: bilbyfs_symlink() = %d\n", err); |
| make_bad_inode(inode); |
| iput(inode); |
| return err; |
| } |
| |
| static int bilbyfs_rename(struct inode *old_dir, struct dentry *old_dentry, |
| struct inode *new_dir, struct dentry *new_dentry |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) |
| , unsigned int flags |
| #endif |
| ) |
| { |
| struct bilbyfs_info *bi = old_dir->i_sb->s_fs_info; |
| int err; |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) |
| if (flags & ~RENAME_NOREPLACE) |
| return -EINVAL; |
| #endif |
| down(&bi->wd.lock); |
| err = fsop_rename(bi, old_dir, old_dentry->d_name.name, |
| old_dentry->d_inode, new_dir, new_dentry->d_name.name, |
| new_dentry->d_inode); |
| up(&bi->wd.lock); |
| if (err) |
| bilbyfs_err("BilbyFsError: bilbyfs_rename() = %d\n", err); |
| return err; |
| } |
| |
| static int bilbyfs_fsync(struct file *filp, loff_t start, loff_t end, int datasync) |
| { |
| struct inode *inode = filp->f_mapping->host; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| int err; |
| |
| err = filemap_write_and_wait_range(inode->i_mapping, start, end); |
| if (!err) { |
| down(&bi->wd.lock); |
| err = fsop_sync_fs(bi, inode->i_sb, 1); |
| up(&bi->wd.lock); |
| if (err) |
| bilbyfs_err("BilbyFsError: bilbyfs_fsync() = %d\n", err); |
| } else { |
| bilbyfs_err("BilbyFsError: filemap_and_wait_range() = %d\n", err); |
| } |
| return err; |
| } |
| |
| static int bilbyfs_readpage(struct file *filp, struct page *page) |
| { |
| struct inode *inode = page->mapping->host; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| void *addr; |
| int err; |
| |
| bilbyfs_debug("[0] bilbyfs_readpage(page = %p)\n", page); |
| addr = kmap(page); |
| down(&bi->wd.lock); |
| err = fsop_readpage(bi, inode, page->index, addr); |
| up(&bi->wd.lock); |
| if (err == -ENOENT) { |
| SetPageChecked(page); |
| err = 0; |
| } |
| if (err) { |
| ClearPageUptodate(page); |
| SetPageError(page); |
| flush_dcache_page(page); |
| kunmap(page); |
| unlock_page(page); |
| bilbyfs_err("BilbyFsError: bilbyfs_readpage() = %d\n", err); |
| return err; |
| } |
| SetPageUptodate(page); |
| ClearPageError(page); |
| flush_dcache_page(page); |
| kunmap(page); |
| unlock_page(page); |
| bilbyfs_debug("[2] bilbyfs_readpage() = %d\n", 0); |
| return 0; |
| } |
| |
| static int bilbyfs_write_begin(struct file *filp, struct address_space *mapping, |
| loff_t pos, unsigned len, unsigned flags, |
| struct page **pagep, void **fsdata) |
| { |
| struct page *page; |
| struct inode *inode = mapping->host; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| pgoff_t block = pos >> BILBYFS_BLOCK_SHIFT; |
| void *addr; |
| int err = 0; |
| |
| bilbyfs_debug("[0] bilbyfs_write_begin()\n"); |
| page = grab_cache_page_write_begin(mapping, block, flags); |
| if (!page) |
| return -ENOMEM; |
| |
| addr = kmap(page); |
| |
| if (!PageUptodate(page)) { /* overwriting data */ |
| /* FIXME: optimisation, check whether the entire page is gonna be |
| * overwritten and set PageChecked accordingly |
| */ |
| down(&bi->wd.lock); |
| err = fsop_write_begin(bi, inode, pos, len, addr); |
| up(&bi->wd.lock); |
| if (err) { |
| ClearPageUptodate(page); |
| SetPageError(page); |
| kunmap(page); |
| unlock_page(page); |
| put_page(page); |
| bilbyfs_err("BilbyFsError: bilbyfs_write_begin() = %d\n", err); |
| return err; |
| } |
| } |
| SetPageUptodate(page); |
| ClearPageError(page); |
| kunmap(page); |
| *pagep = page; |
| bilbyfs_debug("[2] bilbyfs_write_begin() = 0\n"); |
| return 0; |
| } |
| |
| static int bilbyfs_write_end(struct file *filp, struct address_space *mapping, |
| loff_t pos, unsigned len, unsigned copied, |
| struct page *page, void *fsdata) |
| { |
| struct inode *inode = mapping->host; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| void *addr; |
| int err; |
| |
| bilbyfs_debug("[S] bilbyfs_write_end()\n"); |
| BUG_ON(!PageUptodate(page)); |
| /* if len > PAGE_SIZE then some data hasn't been copied by |
| * bilbyfs |
| */ |
| if (copied < len) { |
| /* Something went wrong VFS did not copy all data initially |
| * announced in write_begin. |
| * FIXME Is returning 0 really asking VFS to retry? |
| * FIXME Or should we just write the provided data ? |
| */ |
| /* 23/10/2013: This can safely be ignored because BilbyFs |
| * always read a PAGE_SIZE data block in write_begin |
| * which means that as long as @len is < PAGE_SIZE |
| * we have read enough data. |
| * FIXME: Can this ever happen without concurrency? |
| */ |
| bilbyfs_debug("copied < len : %u < %u. But it's safe to ignore as write_begin read %lu bytes.\n", |
| copied, len, PAGE_SIZE); |
| /* return 0; */ |
| } |
| addr = kmap(page); |
| down(&bi->wd.lock); |
| err = fsop_write_end(bi, inode, pos, copied, addr); |
| up(&bi->wd.lock); |
| if (!err) { |
| kunmap(page); |
| unlock_page(page); |
| put_page(page); |
| bilbyfs_debug("[1] bilbyfs_write_end() = %d\n", copied); |
| return copied; |
| } |
| SetPageError(page); |
| kunmap(page); |
| unlock_page(page); |
| put_page(page); |
| bilbyfs_err("BilbyFsError: bilbyfs_write_end() = %d\n", err); |
| return err; |
| } |
| |
| static int bilbyfs_write_end_writeback(struct file *filp, struct address_space *mapping, |
| loff_t pos, unsigned len, unsigned copied, |
| struct page *page, void *fsdata) |
| { |
| struct inode *inode = mapping->host; |
| loff_t end_pos = pos + len; |
| int appending = !!(end_pos > inode->i_size); |
| |
| bilbyfs_assert(PAGE_SIZE == BILBYFS_BLOCK_SIZE); |
| bilbyfs_debug("[S] bilbyfs_write_end_writeback(pos = %llu, len =%u, copied = %u)\n",pos, len, copied); |
| BUG_ON(!PageUptodate(page)); |
| bilbyfs_assert(len <= PAGE_SIZE); |
| if (copied < len) { |
| /* 23/10/2013: This can safely be ignored because BilbyFs |
| * always read a PAGE_SIZE data block in write_begin |
| * which means that as long as @len is < PAGE_SIZE |
| * we have read enough data. |
| * FIXME: Can this ever happen without concurrency? |
| */ |
| bilbyfs_debug("copied < len : %u < %u. But it's safe to ignore as write_begin read %lu bytes.\n", |
| copied, len, PAGE_SIZE); |
| } |
| __set_page_dirty_nobuffers(page); |
| if (appending) { |
| i_size_write(inode, end_pos); |
| /* |
| * Note, we do not set @I_DIRTY_PAGES (which means that the |
| * inode has dirty pages), this has been done in |
| * '__set_page_dirty_nobuffers()'. |
| */ |
| __mark_inode_dirty(inode, I_DIRTY_DATASYNC); |
| } |
| unlock_page(page); |
| put_page(page); |
| bilbyfs_debug("[1] bilbyfs_write_end_writeback() = %d (nb bytes copied)\n", copied); |
| return copied; |
| } |
| |
| static int bilbyfs_writepage(struct page *page, struct writeback_control *wbc) |
| { |
| struct inode *inode = page->mapping->host; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| loff_t i_size = i_size_read(inode); |
| pgoff_t end_index = i_size >> PAGE_SHIFT; |
| loff_t pos = page->index << PAGE_SHIFT; |
| int err, len = i_size & (PAGE_SIZE - 1); |
| void *kaddr; |
| |
| bilbyfs_debug("bilbyfs_writepage()\n"); |
| |
| /* Is the page fully outside @i_size? (truncate in progress) */ |
| if (page->index > end_index || (page->index == end_index && !len)) { |
| unlock_page(page); |
| return 0; |
| } |
| bilbyfs_debug("write-to-disk\n"); |
| if (page->index != end_index) |
| len = PAGE_SIZE; |
| set_page_writeback(page); |
| kaddr = kmap_atomic(page); |
| memset(kaddr + len, 0, PAGE_SIZE - len); |
| flush_dcache_page(page); |
| kunmap_atomic(kaddr); |
| |
| kaddr = kmap(page); |
| down(&bi->wd.lock); |
| err = fsop_write_end(bi, inode, pos, len, kaddr); |
| up(&bi->wd.lock); |
| kunmap(page); |
| unlock_page(page); |
| end_page_writeback(page); |
| |
| if (err) |
| bilbyfs_err("BilbyFsError: bilbyfs_writepage() = %d\n", err); |
| return err; |
| } |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) |
| static void *bilbyfs_follow_link(struct dentry *dentry, struct nameidata *nd) |
| { |
| struct bilbyfs_info *bi = dentry->d_inode->i_sb->s_fs_info; |
| struct inode *inode = dentry->d_inode; |
| int err; |
| |
| bilbyfs_debug("bilbyfs_follow_link() \n"); |
| if (!inode->i_private) { |
| inode->i_private = kmalloc(PATH_MAX); |
| if (!inode->i_private) |
| return ERR_PTR(-ENOMEM); |
| |
| down(&bi->wd.lock); |
| err = fsop_follow_link(bi, dentry->d_inode, inode->i_private); |
| up(&bi->wd.lock); |
| if (err) { |
| bilbyfs_err("BilbyFsError: bilbyfs_follow_link() = %d\n", err); |
| kfree(inode->i_private); |
| return ERR_PTR(err); |
| } |
| |
| } |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0) |
| nd_set_link(nd, inode->i_private); |
| #endif |
| return NULL; |
| } |
| #else |
| const char *bilbyfs_get_link(struct dentry *dentry, struct inode *inode, |
| struct delayed_call *done) |
| { |
| struct bilbyfs_info *bi = dentry->d_inode->i_sb->s_fs_info; |
| int err; |
| |
| if (!inode->i_link) { |
| inode->i_link = kmalloc(PATH_MAX); |
| if (!inode->i_link) |
| return ERR_PTR(-ENOMEM); |
| down(&bi->wd.lock); |
| err = fsop_follow_link(bi, dentry->d_inode, inode->i_link); |
| up(&bi->wd.lock); |
| |
| if (err) { |
| bilbyfs_err("BilbyFsError: bilbyfs_follow_link() = %d\n", err); |
| kfree(inode->i_link); |
| inode->i_link = NULL; |
| return ERR_PTR(err); |
| } |
| } |
| return inode->i_link; |
| } |
| #endif |
| |
| static int bilbyfs_setattr(struct dentry *dentry, struct iattr *attr) |
| { |
| struct inode *inode = dentry->d_inode; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| int err; |
| |
| bilbyfs_debug("bilbyfs_setattr(ino %lu, mode %#x, ia_valid %#x)\n", |
| inode->i_ino, inode->i_mode, attr->ia_valid); |
| #if LINUX_VERSION_CODE > KERNEL_VERSION(4,9,0) |
| err = setattr_prepare(dentry, attr); |
| #else |
| err = inode_change_ok(inode, attr); |
| #endif |
| |
| if (err) |
| return err; |
| |
| if (attr->ia_valid & ATTR_MODE) { |
| if (!in_group_p(inode->i_gid) && !capable(CAP_FSETID)) |
| attr->ia_mode &= ~S_ISGID; |
| } |
| |
| down(&bi->wd.lock); |
| err = fsop_setattr(bi, inode, attr); |
| up(&bi->wd.lock); |
| if (err) { |
| bilbyfs_err("BilbyFsError: fsop_setattr() = %d\n", err); |
| return err; |
| } |
| /* truncate_setsize calls i_size_write and frees pages in the |
| * page cache */ |
| if (attr->ia_valid & ATTR_SIZE) |
| truncate_setsize(inode, inode->i_size); |
| if (attr->ia_valid & ATTR_ATIME) |
| inode->i_atime = timespec64_trunc(inode->i_atime, |
| inode->i_sb->s_time_gran); |
| if (attr->ia_valid & ATTR_MTIME) |
| inode->i_mtime = timespec64_trunc(inode->i_mtime, |
| inode->i_sb->s_time_gran); |
| if (attr->ia_valid & ATTR_CTIME) |
| inode->i_ctime = timespec64_trunc(inode->i_ctime, |
| inode->i_sb->s_time_gran); |
| return 0; |
| } |
| |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0) |
| static int bilbyfs_getattr(struct vfsmount *mnt, struct dentry *dentry, |
| struct kstat *stat) |
| { |
| struct inode *inode = dentry->d_inode; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| int err; |
| |
| down(&bi->wd.lock); |
| err = fsop_getattr(bi, inode, stat); |
| up(&bi->wd.lock); |
| if (!err) { |
| stat->dev = dentry->d_sb->s_dev; |
| stat->rdev = inode->i_rdev; |
| } else { |
| bilbyfs_err("BilbyFsError: fsop_getattr = %d", err); |
| } |
| return err; |
| } |
| #else |
| int bilbyfs_getattr(const struct path *path, struct kstat *stat, |
| u32 mask, unsigned int flags) |
| { |
| struct dentry *dentry = path->dentry; |
| struct inode *inode = d_inode(dentry); |
| int err; |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| |
| down(&bi->wd.lock); |
| err = fsop_getattr(bi, inode, stat); |
| up(&bi->wd.lock); |
| |
| if (!err) { |
| stat->dev = dentry->d_sb->s_dev; |
| stat->rdev = inode->i_rdev; |
| } else { |
| bilbyfs_err("BilbyFsError: fsop_getattr = %d", err); |
| } |
| return err; |
| } |
| #endif |
| |
| const struct file_operations bilbyfs_file_operations = |
| { |
| .llseek = generic_file_llseek, |
| .open = generic_file_open, |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) |
| .read = new_sync_read, |
| .write = new_sync_write, |
| #endif |
| .read_iter = generic_file_read_iter, |
| .write_iter = generic_file_write_iter, |
| .mmap = generic_file_readonly_mmap, |
| .fsync = bilbyfs_fsync, |
| .splice_read = generic_file_splice_read, |
| }; |
| |
| const struct inode_operations bilby_file_inode_operations = |
| { |
| .setattr = bilbyfs_setattr, |
| }; |
| |
| const struct inode_operations bilbyfs_symlink_inode_operations = { |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) |
| .readlink = generic_readlink, |
| .follow_link = bilbyfs_follow_link, |
| #else |
| .get_link = bilbyfs_get_link, |
| #endif |
| .setattr = bilbyfs_setattr, |
| .getattr = bilbyfs_getattr, |
| }; |
| |
| |
| const struct inode_operations bilbyfs_file_inode_operations = { |
| .setattr = bilbyfs_setattr, |
| .getattr = bilbyfs_getattr, |
| }; |
| |
| const struct inode_operations bilbyfs_dir_inode_operations = { |
| .lookup = bilbyfs_lookup, |
| .create = bilbyfs_create, |
| .link = bilbyfs_link, |
| .symlink = bilbyfs_symlink, |
| .unlink = bilbyfs_unlink, |
| .mkdir = bilbyfs_mkdir, |
| .rmdir = bilbyfs_rmdir, |
| .rename = bilbyfs_rename, |
| .setattr = bilbyfs_setattr, |
| /* |
| .mknod = bilbyfs_mknod, |
| */ |
| .getattr = bilbyfs_getattr, |
| }; |
| |
| const struct file_operations bilbyfs_dir_operations = { |
| .llseek = bilbyfs_dir_llseek, |
| .release = bilbyfs_dir_release, |
| .fsync = bilbyfs_fsync, |
| .read = generic_read_dir, |
| .iterate = bilbyfs_readdir, |
| }; |
| |
| const struct address_space_operations bilbyfs_file_address_operations = |
| { |
| .readpage = bilbyfs_readpage, |
| .write_begin = bilbyfs_write_begin, |
| #ifdef NOWRITEBACK |
| .write_end = bilbyfs_write_end, |
| #else |
| .writepage = bilbyfs_writepage, |
| .write_end = bilbyfs_write_end_writeback, |
| #endif |
| }; |
| |
| static int bilbyfs_statfs(struct dentry *dentry, struct kstatfs *kstat) |
| { |
| struct bilbyfs_info *bi = dentry->d_inode->i_sb->s_fs_info; |
| int err; |
| |
| down(&bi->wd.lock); |
| err = fsop_statfs(bi, kstat); |
| up(&bi->wd.lock); |
| return err; |
| } |
| |
| static int bilbyfs_sync_fs(struct super_block *sb, int wait) |
| { |
| struct bilbyfs_info *bi = sb->s_fs_info; |
| int err; |
| |
| down(&bi->wd.lock); |
| err = fsop_sync_fs(bi, sb, wait); |
| up(&bi->wd.lock); |
| if (err) |
| bilbyfs_err("BilbyFsError: bilbyfs_link() = %d\n", err); |
| return err; |
| } |
| |
| /** |
| * bilbyfs_unmount - unmount BilbyFs |
| * @bi: global fs info |
| * |
| */ |
| static void bilbyfs_unmount(struct bilbyfs_info *bi) |
| { |
| bilbyfs_debug("bilbyfs_unmount()\n"); |
| down(&bi->wd.lock); |
| fsop_unmount(bi); |
| up(&bi->wd.lock); |
| } |
| |
| static void bilbyfs_put_super(struct super_block *sb) |
| { |
| struct bilbyfs_info *bi = sb->s_fs_info; |
| |
| bilbyfs_msg("unmount UBI device %d, volume %d", bi->vi.ubi_num, |
| bi->vi.vol_id); |
| |
| bilbyfs_debug("bilbyfs_put_super()\n"); |
| bilbyfs_unmount(bi); |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0) |
| bdi_destroy(sb->s_bdi); |
| #endif |
| ubi_close_volume(bi->ubi); /* FIXME: This should be in umount */ |
| } |
| |
| /** |
| * open_ubi - parse UBI device name string and open the UBI device. |
| * @name: UBI volume name |
| * @mode: UBI volume open mode |
| * |
| * The primary method of mounting BilbyFs is by specifying the UBI volume |
| * character device node path. However, BilbyFs may also be mounted withoug any |
| * character device node using one of the following methods: |
| * |
| * o ubiX_Y - mount UBI device number X, volume Y; |
| * o ubiY - mount UBI device number 0, volume Y; |
| * o ubiX:NAME - mount UBI device X, volume with name NAME; |
| * o ubi:NAME - mount UBI device 0, volume with name NAME. |
| * |
| * Alternative '!' separator may be used instead of ':' (because some shells |
| * like busybox may interpret ':' as an NFS host name separator). This function |
| * returns UBI volume description object in case of success and a negative |
| * error code in case of failure. |
| */ |
| struct ubi_volume_desc *open_ubi(const char *name, int mode) |
| { |
| struct ubi_volume_desc *ubi; |
| int dev, vol; |
| char *endptr; |
| |
| /* First, try to open using the device node path method */ |
| ubi = ubi_open_volume_path(name, mode); |
| if (!IS_ERR(ubi)) |
| return ubi; |
| |
| /* Try the "nodev" method */ |
| if (name[0] != 'u' || name[1] != 'b' || name[2] != 'i') |
| return ERR_PTR(-EINVAL); |
| |
| /* ubi:NAME method */ |
| if ((name[3] == ':' || name[3] == '!') && name[4] != '\0') |
| return ubi_open_volume_nm(0, name + 4, mode); |
| |
| if (!isdigit(name[3])) |
| return ERR_PTR(-EINVAL); |
| |
| dev = simple_strtoul(name + 3, &endptr, 0); |
| |
| /* ubiY method */ |
| if (*endptr == '\0') |
| return ubi_open_volume(0, dev, mode); |
| |
| /* ubiX_Y method */ |
| if (*endptr == '_' && isdigit(endptr[1])) { |
| vol = simple_strtoul(endptr + 1, &endptr, 0); |
| if (*endptr != '\0') |
| return ERR_PTR(-EINVAL); |
| return ubi_open_volume(dev, vol, mode); |
| } |
| |
| /* ubiX:NAME method */ |
| if ((*endptr == ':' || *endptr == '!') && endptr[1] != '\0') |
| return ubi_open_volume_nm(dev, ++endptr, mode); |
| |
| return ERR_PTR(-EINVAL); |
| } |
| |
| enum { |
| BilbyFs_Opt_no_summary, |
| BilbyFs_Opt_compr, |
| BilbyFs_Opt_end, |
| }; |
| |
| static const match_table_t bilbyfs_options = { |
| {BilbyFs_Opt_no_summary, "no_summary"}, |
| {BilbyFs_Opt_compr, "compr=%s"}, |
| {BilbyFs_Opt_end, NULL}, |
| }; |
| |
| static int bilbyfs_parse_options(struct bilbyfs_info *bi, char *options) |
| { |
| char *s; |
| substring_t args[MAX_OPT_ARGS]; |
| |
| if (!options) |
| return 0; |
| |
| while ((s = strsep(&options, ",")) && *s) { |
| int opt; |
| |
| opt = match_token(s, bilbyfs_options, args); |
| switch (opt) { |
| case BilbyFs_Opt_no_summary: |
| bi->no_summary = 1; |
| bilbyfs_err("BilbyFs option: no_summary\n"); |
| break; |
| case BilbyFs_Opt_compr: |
| bilbyfs_err("BilbyFs option: compr=%%s ignored (No compression)\n"); |
| break; |
| default: |
| { |
| bilbyfs_err("unreconized mount option \"%s\"\n", s); |
| return -EINVAL; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| |
| static int bilbyfs_fill_super(struct super_block *sb, void *options, int silent, char *bd_name) |
| { |
| struct bilbyfs_info *bi = sb->s_fs_info; |
| struct inode *root; |
| int err; |
| ino_t rootino; |
| |
| err = bilbyfs_parse_options(bi, options); |
| if (err) |
| return err; |
| /* |
| * Disabling VFS's read-ahead feature. |
| * Read-ahead will be disabled because bdi->ra_pages is 0. |
| */ |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) |
| |
| sb->s_bdi = &bi->wd.bdi; |
| sb->s_bdi->name = "bilbyfs"; |
| sb->s_bdi->capabilities = BDI_CAP_MAP_COPY; |
| err = bdi_init(sb->s_bdi); |
| if (!err) { |
| err = bdi_register(sb->s_bdi, NULL, bd_name); |
| if (!err) { |
| sb->s_fs_info = bi; |
| root = bilbyfs_new_inode(sb, NULL, S_IFDIR | S_IRUGO | S_IWUSR | S_IXUGO); |
| if (root) { |
| set_nlink(root, 2); |
| down(&bi->wd.lock); |
| err = fsop_fill_super(bi, sb, silent, &rootino, root); |
| up(&bi->wd.lock); |
| iput(root); |
| } else { |
| err = PTR_ERR(root); |
| } |
| |
| if (!err) { |
| /* Reads the root inode */ |
| root = bilbyfs_iget(bi, rootino); |
| if (!IS_ERR(root)) { |
| sb->s_root = d_make_root(root); |
| if (sb->s_root) |
| return 0; |
| err = -EINVAL; |
| } else { |
| err = PTR_ERR(root); |
| } |
| bilbyfs_unmount(bi); |
| } |
| bdi_destroy(sb->s_bdi); |
| } |
| } |
| #else |
| super_setup_bdi(sb); |
| // sb->s_fs_info = wd; |
| root = bilbyfs_new_inode(sb, NULL, S_IFDIR | S_IRUGO | S_IWUSR | S_IXUGO); |
| if (root) { |
| set_nlink(root, 2); |
| err = fsop_fill_super(bi, sb, silent, &rootino, root); |
| iput(root); |
| } else { |
| err = PTR_ERR(root); |
| } |
| |
| if (!err) { |
| /* Reads the root inode */ |
| root = bilbyfs_iget(bi, root->i_ino); |
| if (!IS_ERR(root)) { |
| sb->s_root = d_make_root(root); |
| if (sb->s_root) |
| return 0; |
| err = -EINVAL; |
| } else { |
| err = PTR_ERR(root); |
| } |
| bilbyfs_unmount(bi); |
| } |
| #endif |
| sb->s_bdi = NULL; |
| return err; |
| } |
| |
| static int sb_test(struct super_block *sb, void *data) |
| { |
| struct bilbyfs_info *bi = sb->s_fs_info; |
| int err; |
| |
| down(&bi->wd.lock); |
| err = fsop_test_is_mount(data, bi); |
| up(&bi->wd.lock); |
| return err; |
| } |
| |
| static int sb_set(struct super_block *sb, void *data) |
| { |
| sb->s_fs_info = data; |
| return set_anon_super(sb, NULL); |
| } |
| |
| static const struct super_operations bilbyfs_super_operations; |
| |
| /** |
| * bilbyfs_mount: mount the file system |
| * @fs_type: Linux file system type reference |
| * @flags: MS_* flags (MS_RDONLY, MS_NOATIME, ...) |
| * @name: name of the ubi volume to mount |
| * @data: private pointer to store some fs specific data |
| */ |
| struct dentry *bilbyfs_mount(struct file_system_type *fs_type, int flags, |
| const char *name, void *data) |
| { |
| struct bilbyfs_info *bi; |
| struct super_block *sb; |
| char bd_name[BILBYFS_BD_MAX_NLEN]; |
| int err; |
| |
| bilbyfs_err("Mounting BilbyFs %s, flags %#x\n", name, flags); |
| bi = kzalloc(sizeof(struct bilbyfs_info)); |
| if (bi) { |
| wrapper_init(&bi->wd); |
| down(&bi->wd.lock); |
| err = fsop_init(bi, name, bd_name); |
| up(&bi->wd.lock); |
| if (!err) { |
| sb = sget(fs_type, sb_test, sb_set, flags, bi); |
| if (!IS_ERR(sb)) { |
| /* 'fill_super()' opens ubi again so we must close it here */ |
| if (sb->s_root) { |
| kfree(bi); |
| /* A new mount point for already mounted BilbyFs */ |
| return dget(sb->s_root); |
| } else { |
| sb->s_flags = flags; |
| sb->s_op = &bilbyfs_super_operations; |
| err = bilbyfs_fill_super(sb, data, |
| !!(flags & MS_SILENT), bd_name); |
| if (!err) { |
| /* We do not support atime */ |
| sb->s_flags |= MS_ACTIVE | MS_NOATIME; |
| bilbyfs_debug("bilbyfs_mount() = %d\n", err); |
| return dget(sb->s_root); |
| } |
| deactivate_locked_super(sb); |
| } |
| } else { |
| err = PTR_ERR(sb); |
| kfree(bi); |
| } |
| } |
| } else { |
| err = -ENOMEM; |
| } |
| bilbyfs_debug("bilbyfs_mount() = %d\n", err); |
| return ERR_PTR(err); |
| } |
| |
| /** |
| * bilbyfs_kill_super: free linux super block instance. |
| * @sb: Linux super block reference |
| */ |
| void bilbyfs_kill_super(struct super_block *sb) |
| { |
| struct bilbyfs_info *bi = sb->s_fs_info; |
| |
| bilbyfs_debug("bilbyfs_kill_super()\n"); |
| kill_anon_super(sb); |
| down(&bi->wd.lock); |
| fsop_clean(bi); |
| up(&bi->wd.lock); |
| kfree(bi); |
| } |
| |
| struct kmem_cache *bilbyfs_inode_slab; |
| |
| static struct inode *bilbyfs_alloc_inode(struct super_block *sb) |
| { |
| struct bilbyfs_inode *binode; |
| |
| binode = kmem_cache_alloc(bilbyfs_inode_slab, GFP_NOFS); |
| if (!binode) |
| return NULL; |
| |
| memset((void *)binode + sizeof(struct inode), 0, |
| sizeof(struct bilbyfs_inode) - sizeof(struct inode)); |
| return &binode->vfs_inode; |
| } |
| |
| static void bilbyfs_free_inode(struct rcu_head *head) |
| { |
| struct inode *inode = container_of(head, struct inode, i_rcu); |
| struct bilbyfs_inode *binode = inode_to_binode(inode); |
| |
| bilbyfs_debug("bilbyfs_free_inode(ino = %lu)\n", inode->i_ino); |
| |
| kfree(inode->i_link); |
| kmem_cache_free(bilbyfs_inode_slab, binode); |
| } |
| |
| static void bilbyfs_destroy_inode(struct inode *inode) |
| { |
| bilbyfs_debug("bilbyfs_destroy_inode(ino=%lu)\n", inode->i_ino); |
| kfree(inode->i_private); |
| inode->i_private = NULL; |
| |
| call_rcu(&inode->i_rcu, bilbyfs_free_inode); |
| } |
| |
| static void bilbyfs_evict_inode(struct inode *inode) |
| { |
| struct bilbyfs_info *bi = inode->i_sb->s_fs_info; |
| |
| bilbyfs_debug("bilbyfs_evict_inode(ino = %lu)\n", inode->i_ino); |
| bilbyfs_assert(!atomic_read(&inode->i_count)); |
| |
| truncate_inode_pages(&inode->i_data, 0); |
| if (!is_bad_inode(inode)) { |
| down(&bi->wd.lock); |
| fsop_evict_inode(bi, inode); |
| up(&bi->wd.lock); |
| } |
| clear_inode(inode); |
| } |
| |
| struct timespec64 inode_current_time(struct inode *inode) |
| { |
| return current_time(inode); |
| } |
| |
| static const struct super_operations bilbyfs_super_operations = |
| { |
| .alloc_inode = bilbyfs_alloc_inode, |
| .destroy_inode = bilbyfs_destroy_inode, |
| .put_super = bilbyfs_put_super, |
| .statfs = bilbyfs_statfs, |
| /* .remount_fs = bilbyfs_remount_fs, */ |
| .evict_inode = bilbyfs_evict_inode, |
| .sync_fs = bilbyfs_sync_fs, |
| }; |
| |
| static struct file_system_type bilbyfs_fs_type = { |
| .name = "bilbyfs", |
| .owner = THIS_MODULE, |
| .mount = bilbyfs_mount, |
| .kill_sb = bilbyfs_kill_super, |
| }; |
| |
| /* |
| * Inode slab cache constructor. |
| */ |
| static void inode_slab_ctor(void *obj) |
| { |
| struct bilbyfs_inode *bi = obj; |
| inode_init_once(&bi->vfs_inode); |
| } |
| |
| static int __init bilbyfs_init(void) |
| { |
| int err; |
| |
| /* Make sure node sizes are 8-byte aligned (BILBYFS_OBJ_PADDING) */ |
| BUILD_BUG_ON((BILBYFS_OBJ_PADDING - 1) != 7); |
| BUILD_BUG_ON(BILBYFS_CH_SZ & 7); |
| BUILD_BUG_ON(BILBYFS_INODE_SZ & 7); |
| BUILD_BUG_ON(BILBYFS_DENTARR_SZ & 7); |
| BUILD_BUG_ON(BILBYFS_DATA_SZ & 7); |
| BUILD_BUG_ON(BILBYFS_DATA_SZ & 7); |
| BUILD_BUG_ON(BILBYFS_MAX_DATA_SZ & 7); |
| BUILD_BUG_ON(BILBYFS_MAX_DENTARR_SZ & 7); |
| BUILD_BUG_ON(BILBYFS_SUPER_SZ & 7); |
| |
| BUILD_BUG_ON(BILBYFS_FIRST_SQNUM <= 0); |
| BUILD_BUG_ON(BILBYFS_FIRST_INO <= 0); |
| BUILD_BUG_ON(BILBYFS_ROOT_INO >= BILBYFS_FIRST_INO); |
| |
| /* Ensuring SUP_LNUM < LOG_FST_LNUM */ |
| BUILD_BUG_ON(BILBYFS_SUP_LNUM >= BILBYFS_LOG_FST_LNUM); |
| |
| /* All obj types fit in 3 bits */ |
| BUILD_BUG_ON(BILBYFS_OBJ_TYPES_CNT > 7); |
| |
| BUILD_BUG_ON(PAGE_SIZE != BILBYFS_BLOCK_SIZE); |
| BUILD_BUG_ON(PAGE_SHIFT != BILBYFS_BLOCK_SHIFT); |
| |
| bilbyfs_inode_slab = kmem_cache_create("bilbyfs_inode_slab", |
| sizeof(struct bilbyfs_inode), 0, |
| SLAB_MEM_SPREAD | SLAB_RECLAIM_ACCOUNT, |
| &inode_slab_ctor); |
| if (!bilbyfs_inode_slab) |
| return -ENOMEM; |
| |
| err = register_filesystem(&bilbyfs_fs_type); |
| if (err) { |
| bilbyfs_err("cannot register file system, error %d", err); |
| kmem_cache_destroy(bilbyfs_inode_slab); |
| return err; |
| } |
| return 0; |
| } |
| module_init(bilbyfs_init); |
| |
| static void __exit bilbyfs_exit(void) |
| { |
| /* Not sure why UBIFS destroy the slab before unregistering |
| * but for now we do the same. FIXME More investigation required. |
| */ |
| rcu_barrier(); |
| kmem_cache_destroy(bilbyfs_inode_slab); |
| unregister_filesystem(&bilbyfs_fs_type); |
| } |
| module_exit(bilbyfs_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION(__stringify(BILBYFS_VERSION)); |
| MODULE_AUTHOR("Sidney Amani"); |
| MODULE_DESCRIPTION("BilbyFs - Bilby File System"); |
| |