super.c 8.24 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
Linus Torvalds's avatar
Linus Torvalds committed
2 3 4 5 6 7 8 9 10 11
/*
 * super.c
 *
 * Copyright (c) 1999 Al Smith
 *
 * Portions derived from work (c) 1995,1996 Christian Vogelgsang.
 */

#include <linux/init.h>
#include <linux/module.h>
12
#include <linux/exportfs.h>
Linus Torvalds's avatar
Linus Torvalds committed
13 14 15 16
#include <linux/slab.h>
#include <linux/buffer_head.h>
#include <linux/vfs.h>

17 18 19 20
#include "efs.h"
#include <linux/efs_vh.h>
#include <linux/efs_fs_sb.h>

21
static int efs_statfs(struct dentry *dentry, struct kstatfs *buf);
Linus Torvalds's avatar
Linus Torvalds committed
22 23
static int efs_fill_super(struct super_block *s, void *d, int silent);

Al Viro's avatar
Al Viro committed
24 25
static struct dentry *efs_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
Linus Torvalds's avatar
Linus Torvalds committed
26
{
Al Viro's avatar
Al Viro committed
27
	return mount_bdev(fs_type, flags, dev_name, data, efs_fill_super);
Linus Torvalds's avatar
Linus Torvalds committed
28 29
}

Al Viro's avatar
Al Viro committed
30 31 32 33 34 35 36
static void efs_kill_sb(struct super_block *s)
{
	struct efs_sb_info *sbi = SUPER_INFO(s);
	kill_block_super(s);
	kfree(sbi);
}

Linus Torvalds's avatar
Linus Torvalds committed
37 38 39
static struct file_system_type efs_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "efs",
Al Viro's avatar
Al Viro committed
40
	.mount		= efs_mount,
Al Viro's avatar
Al Viro committed
41
	.kill_sb	= efs_kill_sb,
Linus Torvalds's avatar
Linus Torvalds committed
42 43
	.fs_flags	= FS_REQUIRES_DEV,
};
44
MODULE_ALIAS_FS("efs");
Linus Torvalds's avatar
Linus Torvalds committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

static struct pt_types sgi_pt_types[] = {
	{0x00,		"SGI vh"},
	{0x01,		"SGI trkrepl"},
	{0x02,		"SGI secrepl"},
	{0x03,		"SGI raw"},
	{0x04,		"SGI bsd"},
	{SGI_SYSV,	"SGI sysv"},
	{0x06,		"SGI vol"},
	{SGI_EFS,	"SGI efs"},
	{0x08,		"SGI lv"},
	{0x09,		"SGI rlv"},
	{0x0A,		"SGI xfs"},
	{0x0B,		"SGI xfslog"},
	{0x0C,		"SGI xlv"},
	{0x82,		"Linux swap"},
	{0x83,		"Linux native"},
	{0,		NULL}
};


66
static struct kmem_cache * efs_inode_cachep;
Linus Torvalds's avatar
Linus Torvalds committed
67 68 69 70

static struct inode *efs_alloc_inode(struct super_block *sb)
{
	struct efs_inode_info *ei;
Firo Yang's avatar
Firo Yang committed
71
	ei = kmem_cache_alloc(efs_inode_cachep, GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
72 73 74 75 76
	if (!ei)
		return NULL;
	return &ei->vfs_inode;
}

Nick Piggin's avatar
Nick Piggin committed
77
static void efs_i_callback(struct rcu_head *head)
Linus Torvalds's avatar
Linus Torvalds committed
78
{
Nick Piggin's avatar
Nick Piggin committed
79
	struct inode *inode = container_of(head, struct inode, i_rcu);
Linus Torvalds's avatar
Linus Torvalds committed
80 81 82
	kmem_cache_free(efs_inode_cachep, INODE_INFO(inode));
}

Nick Piggin's avatar
Nick Piggin committed
83 84 85 86 87
static void efs_destroy_inode(struct inode *inode)
{
	call_rcu(&inode->i_rcu, efs_i_callback);
}

88
static void init_once(void *foo)
Linus Torvalds's avatar
Linus Torvalds committed
89 90 91
{
	struct efs_inode_info *ei = (struct efs_inode_info *) foo;

92
	inode_init_once(&ei->vfs_inode);
Linus Torvalds's avatar
Linus Torvalds committed
93
}
94

95
static int __init init_inodecache(void)
Linus Torvalds's avatar
Linus Torvalds committed
96 97
{
	efs_inode_cachep = kmem_cache_create("efs_inode_cache",
98 99 100
				sizeof(struct efs_inode_info), 0,
				SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD|
				SLAB_ACCOUNT, init_once);
Linus Torvalds's avatar
Linus Torvalds committed
101 102 103 104 105 106 107
	if (efs_inode_cachep == NULL)
		return -ENOMEM;
	return 0;
}

static void destroy_inodecache(void)
{
108 109 110 111 112
	/*
	 * Make sure all delayed rcu free inodes are flushed before we
	 * destroy cache.
	 */
	rcu_barrier();
113
	kmem_cache_destroy(efs_inode_cachep);
Linus Torvalds's avatar
Linus Torvalds committed
114 115 116 117
}

static int efs_remount(struct super_block *sb, int *flags, char *data)
{
118
	sync_filesystem(sb);
119
	*flags |= SB_RDONLY;
Linus Torvalds's avatar
Linus Torvalds committed
120 121 122
	return 0;
}

123
static const struct super_operations efs_superblock_operations = {
Linus Torvalds's avatar
Linus Torvalds committed
124 125 126 127 128 129
	.alloc_inode	= efs_alloc_inode,
	.destroy_inode	= efs_destroy_inode,
	.statfs		= efs_statfs,
	.remount_fs	= efs_remount,
};

130
static const struct export_operations efs_export_ops = {
Christoph Hellwig's avatar
Christoph Hellwig committed
131 132
	.fh_to_dentry	= efs_fh_to_dentry,
	.fh_to_parent	= efs_fh_to_parent,
Linus Torvalds's avatar
Linus Torvalds committed
133 134 135 136 137
	.get_parent	= efs_get_parent,
};

static int __init init_efs_fs(void) {
	int err;
138
	pr_info(EFS_VERSION" - http://aeschi.ch.eu.org/efs/\n");
Linus Torvalds's avatar
Linus Torvalds committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
	err = init_inodecache();
	if (err)
		goto out1;
	err = register_filesystem(&efs_fs_type);
	if (err)
		goto out;
	return 0;
out:
	destroy_inodecache();
out1:
	return err;
}

static void __exit exit_efs_fs(void) {
	unregister_filesystem(&efs_fs_type);
	destroy_inodecache();
}

module_init(init_efs_fs)
module_exit(exit_efs_fs)

static efs_block_t efs_validate_vh(struct volume_header *vh) {
	int		i;
	__be32		cs, *ui;
	int		csum;
	efs_block_t	sblock = 0; /* shuts up gcc */
	struct pt_types	*pt_entry;
	int		pt_type, slice = -1;

	if (be32_to_cpu(vh->vh_magic) != VHMAGIC) {
		/*
		 * assume that we're dealing with a partition and allow
		 * read_super() to try and detect a valid superblock
		 * on the next block.
		 */
		return 0;
	}

	ui = ((__be32 *) (vh + 1)) - 1;
	for(csum = 0; ui >= ((__be32 *) vh);) {
		cs = *ui--;
		csum += be32_to_cpu(cs);
	}
	if (csum) {
183
		pr_warn("SGI disklabel: checksum bad, label corrupted\n");
Linus Torvalds's avatar
Linus Torvalds committed
184 185 186 187
		return 0;
	}

#ifdef DEBUG
188
	pr_debug("bf: \"%16s\"\n", vh->vh_bootfile);
Linus Torvalds's avatar
Linus Torvalds committed
189 190 191 192 193 194 195 196 197 198 199

	for(i = 0; i < NVDIR; i++) {
		int	j;
		char	name[VDNAMESIZE+1];

		for(j = 0; j < VDNAMESIZE; j++) {
			name[j] = vh->vh_vd[i].vd_name[j];
		}
		name[j] = (char) 0;

		if (name[0]) {
200 201
			pr_debug("vh: %8s block: 0x%08x size: 0x%08x\n",
				name, (int) be32_to_cpu(vh->vh_vd[i].vd_lbn),
Linus Torvalds's avatar
Linus Torvalds committed
202 203 204 205 206 207 208 209 210 211 212 213
				(int) be32_to_cpu(vh->vh_vd[i].vd_nbytes));
		}
	}
#endif

	for(i = 0; i < NPARTAB; i++) {
		pt_type = (int) be32_to_cpu(vh->vh_pt[i].pt_type);
		for(pt_entry = sgi_pt_types; pt_entry->pt_name; pt_entry++) {
			if (pt_type == pt_entry->pt_type) break;
		}
#ifdef DEBUG
		if (be32_to_cpu(vh->vh_pt[i].pt_nblks)) {
214 215 216 217 218
			pr_debug("pt %2d: start: %08d size: %08d type: 0x%02x (%s)\n",
				 i, (int)be32_to_cpu(vh->vh_pt[i].pt_firstlbn),
				 (int)be32_to_cpu(vh->vh_pt[i].pt_nblks),
				 pt_type, (pt_entry->pt_name) ?
				 pt_entry->pt_name : "unknown");
Linus Torvalds's avatar
Linus Torvalds committed
219 220 221 222 223 224 225 226 227
		}
#endif
		if (IS_EFS(pt_type)) {
			sblock = be32_to_cpu(vh->vh_pt[i].pt_firstlbn);
			slice = i;
		}
	}

	if (slice == -1) {
228
		pr_notice("partition table contained no EFS partitions\n");
Linus Torvalds's avatar
Linus Torvalds committed
229 230
#ifdef DEBUG
	} else {
231
		pr_info("using slice %d (type %s, offset 0x%x)\n", slice,
Linus Torvalds's avatar
Linus Torvalds committed
232 233 234 235
			(pt_entry->pt_name) ? pt_entry->pt_name : "unknown",
			sblock);
#endif
	}
236
	return sblock;
Linus Torvalds's avatar
Linus Torvalds committed
237 238 239 240
}

static int efs_validate_super(struct efs_sb_info *sb, struct efs_super *super) {

241 242
	if (!IS_EFS_MAGIC(be32_to_cpu(super->fs_magic)))
		return -1;
Linus Torvalds's avatar
Linus Torvalds committed
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261

	sb->fs_magic     = be32_to_cpu(super->fs_magic);
	sb->total_blocks = be32_to_cpu(super->fs_size);
	sb->first_block  = be32_to_cpu(super->fs_firstcg);
	sb->group_size   = be32_to_cpu(super->fs_cgfsize);
	sb->data_free    = be32_to_cpu(super->fs_tfree);
	sb->inode_free   = be32_to_cpu(super->fs_tinode);
	sb->inode_blocks = be16_to_cpu(super->fs_cgisize);
	sb->total_groups = be16_to_cpu(super->fs_ncg);
    
	return 0;    
}

static int efs_fill_super(struct super_block *s, void *d, int silent)
{
	struct efs_sb_info *sb;
	struct buffer_head *bh;
	struct inode *root;

262
 	sb = kzalloc(sizeof(struct efs_sb_info), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
263 264 265 266 267 268
	if (!sb)
		return -ENOMEM;
	s->s_fs_info = sb;
 
	s->s_magic		= EFS_SUPER_MAGIC;
	if (!sb_set_blocksize(s, EFS_BLOCKSIZE)) {
269
		pr_err("device does not support %d byte blocks\n",
Linus Torvalds's avatar
Linus Torvalds committed
270
			EFS_BLOCKSIZE);
Al Viro's avatar
Al Viro committed
271
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
272 273 274 275 276 277
	}
  
	/* read the vh (volume header) block */
	bh = sb_bread(s, 0);

	if (!bh) {
278
		pr_err("cannot read volume header\n");
279
		return -EIO;
Linus Torvalds's avatar
Linus Torvalds committed
280 281 282 283 284 285 286 287 288 289 290
	}

	/*
	 * if this returns zero then we didn't find any partition table.
	 * this isn't (yet) an error - just assume for the moment that
	 * the device is valid and go on to search for a superblock.
	 */
	sb->fs_start = efs_validate_vh((struct volume_header *) bh->b_data);
	brelse(bh);

	if (sb->fs_start == -1) {
Al Viro's avatar
Al Viro committed
291
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
292 293 294 295
	}

	bh = sb_bread(s, sb->fs_start + EFS_SUPER);
	if (!bh) {
296
		pr_err("cannot read superblock\n");
297
		return -EIO;
Linus Torvalds's avatar
Linus Torvalds committed
298 299 300 301
	}
		
	if (efs_validate_super(sb, (struct efs_super *) bh->b_data)) {
#ifdef DEBUG
302 303
		pr_warn("invalid superblock at block %u\n",
			sb->fs_start + EFS_SUPER);
Linus Torvalds's avatar
Linus Torvalds committed
304 305
#endif
		brelse(bh);
Al Viro's avatar
Al Viro committed
306
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
307 308 309
	}
	brelse(bh);

310
	if (!sb_rdonly(s)) {
Linus Torvalds's avatar
Linus Torvalds committed
311
#ifdef DEBUG
312
		pr_info("forcing read-only mode\n");
Linus Torvalds's avatar
Linus Torvalds committed
313
#endif
314
		s->s_flags |= SB_RDONLY;
Linus Torvalds's avatar
Linus Torvalds committed
315 316 317
	}
	s->s_op   = &efs_superblock_operations;
	s->s_export_op = &efs_export_ops;
318 319
	root = efs_iget(s, EFS_ROOTINODE);
	if (IS_ERR(root)) {
320
		pr_err("get root inode failed\n");
Al Viro's avatar
Al Viro committed
321
		return PTR_ERR(root);
322 323
	}

324
	s->s_root = d_make_root(root);
Linus Torvalds's avatar
Linus Torvalds committed
325
	if (!(s->s_root)) {
326
		pr_err("get root dentry failed\n");
Al Viro's avatar
Al Viro committed
327
		return -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
328 329 330 331 332
	}

	return 0;
}

333
static int efs_statfs(struct dentry *dentry, struct kstatfs *buf) {
334 335 336
	struct super_block *sb = dentry->d_sb;
	struct efs_sb_info *sbi = SUPER_INFO(sb);
	u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
Linus Torvalds's avatar
Linus Torvalds committed
337 338 339

	buf->f_type    = EFS_SUPER_MAGIC;	/* efs magic number */
	buf->f_bsize   = EFS_BLOCKSIZE;		/* blocksize */
340 341 342 343 344 345
	buf->f_blocks  = sbi->total_groups *	/* total data blocks */
			(sbi->group_size - sbi->inode_blocks);
	buf->f_bfree   = sbi->data_free;	/* free data blocks */
	buf->f_bavail  = sbi->data_free;	/* free blocks for non-root */
	buf->f_files   = sbi->total_groups *	/* total inodes */
			sbi->inode_blocks *
Linus Torvalds's avatar
Linus Torvalds committed
346
			(EFS_BLOCKSIZE / sizeof(struct efs_dinode));
347 348 349
	buf->f_ffree   = sbi->inode_free;	/* free inodes */
	buf->f_fsid.val[0] = (u32)id;
	buf->f_fsid.val[1] = (u32)(id >> 32);
Linus Torvalds's avatar
Linus Torvalds committed
350 351 352 353 354
	buf->f_namelen = EFS_MAXNAMELEN;	/* max filename length */

	return 0;
}