| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2000-2001 Christoph Hellwig. |
| * Copyright (c) 2016 Krzysztof Blaszkowski |
| */ |
| |
| /* |
| * Veritas filesystem driver - superblock related routines. |
| */ |
| #include <linux/init.h> |
| #include <linux/module.h> |
| |
| #include <linux/blkdev.h> |
| #include <linux/fs.h> |
| #include <linux/buffer_head.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/stat.h> |
| #include <linux/vfs.h> |
| #include <linux/fs_context.h> |
| |
| #include "vxfs.h" |
| #include "vxfs_extern.h" |
| #include "vxfs_dir.h" |
| #include "vxfs_inode.h" |
| |
| |
| MODULE_AUTHOR("Christoph Hellwig, Krzysztof Blaszkowski"); |
| MODULE_DESCRIPTION("Veritas Filesystem (VxFS) driver"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| |
| static struct kmem_cache *vxfs_inode_cachep; |
| |
| /** |
| * vxfs_put_super - free superblock resources |
| * @sbp: VFS superblock. |
| * |
| * Description: |
| * vxfs_put_super frees all resources allocated for @sbp |
| * after the last instance of the filesystem is unmounted. |
| */ |
| |
| static void |
| vxfs_put_super(struct super_block *sbp) |
| { |
| struct vxfs_sb_info *infp = VXFS_SBI(sbp); |
| |
| iput(infp->vsi_fship); |
| iput(infp->vsi_ilist); |
| iput(infp->vsi_stilist); |
| |
| brelse(infp->vsi_bp); |
| kfree(infp); |
| } |
| |
| /** |
| * vxfs_statfs - get filesystem information |
| * @dentry: VFS dentry to locate superblock |
| * @bufp: output buffer |
| * |
| * Description: |
| * vxfs_statfs fills the statfs buffer @bufp with information |
| * about the filesystem described by @dentry. |
| * |
| * Returns: |
| * Zero. |
| * |
| * Locking: |
| * No locks held. |
| * |
| * Notes: |
| * This is everything but complete... |
| */ |
| static int |
| vxfs_statfs(struct dentry *dentry, struct kstatfs *bufp) |
| { |
| struct vxfs_sb_info *infp = VXFS_SBI(dentry->d_sb); |
| struct vxfs_sb *raw_sb = infp->vsi_raw; |
| u64 id = huge_encode_dev(dentry->d_sb->s_bdev->bd_dev); |
| |
| bufp->f_type = VXFS_SUPER_MAGIC; |
| bufp->f_bsize = dentry->d_sb->s_blocksize; |
| bufp->f_blocks = fs32_to_cpu(infp, raw_sb->vs_dsize); |
| bufp->f_bfree = fs32_to_cpu(infp, raw_sb->vs_free); |
| bufp->f_bavail = 0; |
| bufp->f_files = 0; |
| bufp->f_ffree = fs32_to_cpu(infp, raw_sb->vs_ifree); |
| bufp->f_fsid = u64_to_fsid(id); |
| bufp->f_namelen = VXFS_NAMELEN; |
| |
| return 0; |
| } |
| |
| static int vxfs_reconfigure(struct fs_context *fc) |
| { |
| sync_filesystem(fc->root->d_sb); |
| fc->sb_flags |= SB_RDONLY; |
| return 0; |
| } |
| |
| static struct inode *vxfs_alloc_inode(struct super_block *sb) |
| { |
| struct vxfs_inode_info *vi; |
| |
| vi = alloc_inode_sb(sb, vxfs_inode_cachep, GFP_KERNEL); |
| if (!vi) |
| return NULL; |
| inode_init_once(&vi->vfs_inode); |
| return &vi->vfs_inode; |
| } |
| |
| static void vxfs_free_inode(struct inode *inode) |
| { |
| kmem_cache_free(vxfs_inode_cachep, VXFS_INO(inode)); |
| } |
| |
| static const struct super_operations vxfs_super_ops = { |
| .alloc_inode = vxfs_alloc_inode, |
| .free_inode = vxfs_free_inode, |
| .evict_inode = vxfs_evict_inode, |
| .put_super = vxfs_put_super, |
| .statfs = vxfs_statfs, |
| }; |
| |
| static int vxfs_try_sb_magic(struct super_block *sbp, struct fs_context *fc, |
| unsigned blk, __fs32 magic) |
| { |
| struct buffer_head *bp; |
| struct vxfs_sb *rsbp; |
| struct vxfs_sb_info *infp = VXFS_SBI(sbp); |
| int silent = fc->sb_flags & SB_SILENT; |
| int rc = -ENOMEM; |
| |
| bp = sb_bread(sbp, blk); |
| do { |
| if (!bp || !buffer_mapped(bp)) { |
| if (!silent) { |
| warnf(fc, |
| "vxfs: unable to read disk superblock at %u", |
| blk); |
| } |
| break; |
| } |
| |
| rc = -EINVAL; |
| rsbp = (struct vxfs_sb *)bp->b_data; |
| if (rsbp->vs_magic != magic) { |
| if (!silent) |
| infof(fc, |
| "vxfs: WRONG superblock magic %08x at %u", |
| rsbp->vs_magic, blk); |
| break; |
| } |
| |
| rc = 0; |
| infp->vsi_raw = rsbp; |
| infp->vsi_bp = bp; |
| } while (0); |
| |
| if (rc) { |
| infp->vsi_raw = NULL; |
| infp->vsi_bp = NULL; |
| brelse(bp); |
| } |
| |
| return rc; |
| } |
| |
| /** |
| * vxfs_fill_super - read superblock into memory and initialize filesystem |
| * @sbp: VFS superblock (to fill) |
| * @fc: filesytem context |
| * |
| * Description: |
| * We are called on the first mount of a filesystem to read the |
| * superblock into memory and do some basic setup. |
| * |
| * Returns: |
| * The superblock on success, else %NULL. |
| * |
| * Locking: |
| * We are under @sbp->s_lock. |
| */ |
| static int vxfs_fill_super(struct super_block *sbp, struct fs_context *fc) |
| { |
| struct vxfs_sb_info *infp; |
| struct vxfs_sb *rsbp; |
| u_long bsize; |
| struct inode *root; |
| int ret = -EINVAL; |
| int silent = fc->sb_flags & SB_SILENT; |
| u32 j; |
| |
| sbp->s_flags |= SB_RDONLY; |
| |
| infp = kzalloc(sizeof(*infp), GFP_KERNEL); |
| if (!infp) { |
| warnf(fc, "vxfs: unable to allocate incore superblock"); |
| return -ENOMEM; |
| } |
| |
| bsize = sb_min_blocksize(sbp, BLOCK_SIZE); |
| if (!bsize) { |
| warnf(fc, "vxfs: unable to set blocksize"); |
| goto out; |
| } |
| |
| sbp->s_op = &vxfs_super_ops; |
| sbp->s_fs_info = infp; |
| sbp->s_time_min = 0; |
| sbp->s_time_max = U32_MAX; |
| |
| if (!vxfs_try_sb_magic(sbp, fc, 1, |
| (__force __fs32)cpu_to_le32(VXFS_SUPER_MAGIC))) { |
| /* Unixware, x86 */ |
| infp->byte_order = VXFS_BO_LE; |
| } else if (!vxfs_try_sb_magic(sbp, fc, 8, |
| (__force __fs32)cpu_to_be32(VXFS_SUPER_MAGIC))) { |
| /* HP-UX, parisc */ |
| infp->byte_order = VXFS_BO_BE; |
| } else { |
| if (!silent) |
| infof(fc, "vxfs: can't find superblock."); |
| goto out; |
| } |
| |
| rsbp = infp->vsi_raw; |
| j = fs32_to_cpu(infp, rsbp->vs_version); |
| if ((j < 2 || j > 4) && !silent) { |
| infof(fc, "vxfs: unsupported VxFS version (%d)", j); |
| goto out; |
| } |
| |
| #ifdef DIAGNOSTIC |
| printk(KERN_DEBUG "vxfs: supported VxFS version (%d)\n", j); |
| printk(KERN_DEBUG "vxfs: blocksize: %d\n", |
| fs32_to_cpu(infp, rsbp->vs_bsize)); |
| #endif |
| |
| sbp->s_magic = fs32_to_cpu(infp, rsbp->vs_magic); |
| |
| infp->vsi_oltext = fs32_to_cpu(infp, rsbp->vs_oltext[0]); |
| infp->vsi_oltsize = fs32_to_cpu(infp, rsbp->vs_oltsize); |
| |
| j = fs32_to_cpu(infp, rsbp->vs_bsize); |
| if (!sb_set_blocksize(sbp, j)) { |
| warnf(fc, "vxfs: unable to set final block size"); |
| goto out; |
| } |
| |
| if (vxfs_read_olt(sbp, bsize)) { |
| warnf(fc, "vxfs: unable to read olt"); |
| goto out; |
| } |
| |
| if (vxfs_read_fshead(sbp)) { |
| warnf(fc, "vxfs: unable to read fshead"); |
| goto out; |
| } |
| |
| root = vxfs_iget(sbp, VXFS_ROOT_INO); |
| if (IS_ERR(root)) { |
| ret = PTR_ERR(root); |
| goto out; |
| } |
| sbp->s_root = d_make_root(root); |
| if (!sbp->s_root) { |
| warnf(fc, "vxfs: unable to get root dentry."); |
| goto out_free_ilist; |
| } |
| |
| return 0; |
| |
| out_free_ilist: |
| iput(infp->vsi_fship); |
| iput(infp->vsi_ilist); |
| iput(infp->vsi_stilist); |
| out: |
| brelse(infp->vsi_bp); |
| kfree(infp); |
| return ret; |
| } |
| |
| /* |
| * The usual module blurb. |
| */ |
| static int vxfs_get_tree(struct fs_context *fc) |
| { |
| return get_tree_bdev(fc, vxfs_fill_super); |
| } |
| |
| static const struct fs_context_operations vxfs_context_ops = { |
| .get_tree = vxfs_get_tree, |
| .reconfigure = vxfs_reconfigure, |
| }; |
| |
| static int vxfs_init_fs_context(struct fs_context *fc) |
| { |
| fc->ops = &vxfs_context_ops; |
| |
| return 0; |
| } |
| |
| static struct file_system_type vxfs_fs_type = { |
| .owner = THIS_MODULE, |
| .name = "vxfs", |
| .kill_sb = kill_block_super, |
| .fs_flags = FS_REQUIRES_DEV, |
| .init_fs_context = vxfs_init_fs_context, |
| }; |
| MODULE_ALIAS_FS("vxfs"); /* makes mount -t vxfs autoload the module */ |
| MODULE_ALIAS("vxfs"); |
| |
| static int __init |
| vxfs_init(void) |
| { |
| int rv; |
| |
| vxfs_inode_cachep = kmem_cache_create_usercopy("vxfs_inode", |
| sizeof(struct vxfs_inode_info), 0, |
| SLAB_RECLAIM_ACCOUNT, |
| offsetof(struct vxfs_inode_info, vii_immed.vi_immed), |
| sizeof_field(struct vxfs_inode_info, |
| vii_immed.vi_immed), |
| NULL); |
| if (!vxfs_inode_cachep) |
| return -ENOMEM; |
| rv = register_filesystem(&vxfs_fs_type); |
| if (rv < 0) |
| kmem_cache_destroy(vxfs_inode_cachep); |
| return rv; |
| } |
| |
| static void __exit |
| vxfs_cleanup(void) |
| { |
| unregister_filesystem(&vxfs_fs_type); |
| /* |
| * Make sure all delayed rcu free inodes are flushed before we |
| * destroy cache. |
| */ |
| rcu_barrier(); |
| kmem_cache_destroy(vxfs_inode_cachep); |
| } |
| |
| module_init(vxfs_init); |
| module_exit(vxfs_cleanup); |