super.c 12.2 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8
/*
 *  linux/fs/hfs/super.c
 *
 * Copyright (C) 1995-1997  Paul H. Hargrove
 * (C) 2003 Ardis Technologies <roman@ardistech.com>
 * This file may be distributed under the terms of the GNU General Public License.
 *
 * This file contains hfs_read_super(), some of the super_ops and
9
 * init_hfs_fs() and exit_hfs_fs().  The remaining super_ops are in
Linus Torvalds's avatar
Linus Torvalds committed
10 11 12 13 14 15 16
 * inode.c since they deal with inodes.
 *
 * Based on the minix file system code, (C) 1991, 1992 by Linus Torvalds
 */

#include <linux/module.h>
#include <linux/blkdev.h>
17
#include <linux/backing-dev.h>
18
#include <linux/mount.h>
Linus Torvalds's avatar
Linus Torvalds committed
19
#include <linux/init.h>
Roman Zippel's avatar
Roman Zippel committed
20
#include <linux/nls.h>
Linus Torvalds's avatar
Linus Torvalds committed
21
#include <linux/parser.h>
22
#include <linux/seq_file.h>
23
#include <linux/slab.h>
Linus Torvalds's avatar
Linus Torvalds committed
24 25 26 27 28
#include <linux/vfs.h>

#include "hfs_fs.h"
#include "btree.h"

29
static struct kmem_cache *hfs_inode_cachep;
Linus Torvalds's avatar
Linus Torvalds committed
30 31 32

MODULE_LICENSE("GPL");

Christoph Hellwig's avatar
Christoph Hellwig committed
33 34 35 36 37 38
static int hfs_sync_fs(struct super_block *sb, int wait)
{
	hfs_mdb_commit(sb);
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
39 40 41 42 43 44 45 46 47
/*
 * hfs_put_super()
 *
 * This is the put_super() entry in the super_operations structure for
 * HFS filesystems.  The purpose is to release the resources
 * associated with the superblock sb.
 */
static void hfs_put_super(struct super_block *sb)
{
48
	cancel_delayed_work_sync(&HFS_SB(sb)->mdb_work);
Linus Torvalds's avatar
Linus Torvalds committed
49 50 51 52 53
	hfs_mdb_close(sb);
	/* release the MDB's resources */
	hfs_mdb_put(sb);
}

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
static void flush_mdb(struct work_struct *work)
{
	struct hfs_sb_info *sbi;
	struct super_block *sb;

	sbi = container_of(work, struct hfs_sb_info, mdb_work.work);
	sb = sbi->sb;

	spin_lock(&sbi->work_lock);
	sbi->work_queued = 0;
	spin_unlock(&sbi->work_lock);

	hfs_mdb_commit(sb);
}

void hfs_mark_mdb_dirty(struct super_block *sb)
{
	struct hfs_sb_info *sbi = HFS_SB(sb);
	unsigned long delay;

	if (sb->s_flags & MS_RDONLY)
		return;

	spin_lock(&sbi->work_lock);
	if (!sbi->work_queued) {
		delay = msecs_to_jiffies(dirty_writeback_interval * 10);
		queue_delayed_work(system_long_wq, &sbi->mdb_work, delay);
		sbi->work_queued = 1;
	}
	spin_unlock(&sbi->work_lock);
}

Linus Torvalds's avatar
Linus Torvalds committed
86 87 88 89 90 91 92 93 94
/*
 * hfs_statfs()
 *
 * This is the statfs() entry in the super_operations structure for
 * HFS filesystems.  The purpose is to return various data about the
 * filesystem.
 *
 * changed f_files/f_ffree to reflect the fs_ablock/free_ablocks.
 */
95
static int hfs_statfs(struct dentry *dentry, struct kstatfs *buf)
Linus Torvalds's avatar
Linus Torvalds committed
96
{
97
	struct super_block *sb = dentry->d_sb;
Coly Li's avatar
Coly Li committed
98
	u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
99

Linus Torvalds's avatar
Linus Torvalds committed
100 101 102 103 104 105 106
	buf->f_type = HFS_SUPER_MAGIC;
	buf->f_bsize = sb->s_blocksize;
	buf->f_blocks = (u32)HFS_SB(sb)->fs_ablocks * HFS_SB(sb)->fs_div;
	buf->f_bfree = (u32)HFS_SB(sb)->free_ablocks * HFS_SB(sb)->fs_div;
	buf->f_bavail = buf->f_bfree;
	buf->f_files = HFS_SB(sb)->fs_ablocks;
	buf->f_ffree = HFS_SB(sb)->free_ablocks;
Coly Li's avatar
Coly Li committed
107 108
	buf->f_fsid.val[0] = (u32)id;
	buf->f_fsid.val[1] = (u32)(id >> 32);
Linus Torvalds's avatar
Linus Torvalds committed
109 110 111 112 113 114 115
	buf->f_namelen = HFS_NAMELEN;

	return 0;
}

static int hfs_remount(struct super_block *sb, int *flags, char *data)
{
116
	sync_filesystem(sb);
Linus Torvalds's avatar
Linus Torvalds committed
117 118 119 120 121
	*flags |= MS_NODIRATIME;
	if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY))
		return 0;
	if (!(*flags & MS_RDONLY)) {
		if (!(HFS_SB(sb)->mdb->drAtrb & cpu_to_be16(HFS_SB_ATTRIB_UNMNT))) {
122
			pr_warn("filesystem was not cleanly unmounted, running fsck.hfs is recommended.  leaving read-only.\n");
Linus Torvalds's avatar
Linus Torvalds committed
123 124 125
			sb->s_flags |= MS_RDONLY;
			*flags |= MS_RDONLY;
		} else if (HFS_SB(sb)->mdb->drAtrb & cpu_to_be16(HFS_SB_ATTRIB_SLOCK)) {
126
			pr_warn("filesystem is marked locked, leaving read-only.\n");
Linus Torvalds's avatar
Linus Torvalds committed
127 128 129 130 131 132 133
			sb->s_flags |= MS_RDONLY;
			*flags |= MS_RDONLY;
		}
	}
	return 0;
}

134
static int hfs_show_options(struct seq_file *seq, struct dentry *root)
135
{
136
	struct hfs_sb_info *sbi = HFS_SB(root->d_sb);
137 138

	if (sbi->s_creator != cpu_to_be32(0x3f3f3f3f))
139
		seq_show_option_n(seq, "creator", (char *)&sbi->s_creator, 4);
140
	if (sbi->s_type != cpu_to_be32(0x3f3f3f3f))
141
		seq_show_option_n(seq, "type", (char *)&sbi->s_type, 4);
142 143 144
	seq_printf(seq, ",uid=%u,gid=%u",
			from_kuid_munged(&init_user_ns, sbi->s_uid),
			from_kgid_munged(&init_user_ns, sbi->s_gid));
145 146 147 148 149 150 151 152
	if (sbi->s_file_umask != 0133)
		seq_printf(seq, ",file_umask=%o", sbi->s_file_umask);
	if (sbi->s_dir_umask != 0022)
		seq_printf(seq, ",dir_umask=%o", sbi->s_dir_umask);
	if (sbi->part >= 0)
		seq_printf(seq, ",part=%u", sbi->part);
	if (sbi->session >= 0)
		seq_printf(seq, ",session=%u", sbi->session);
Roman Zippel's avatar
Roman Zippel committed
153 154 155 156
	if (sbi->nls_disk)
		seq_printf(seq, ",codepage=%s", sbi->nls_disk->charset);
	if (sbi->nls_io)
		seq_printf(seq, ",iocharset=%s", sbi->nls_io->charset);
157 158 159 160 161
	if (sbi->s_quiet)
		seq_printf(seq, ",quiet");
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
162 163 164 165
static struct inode *hfs_alloc_inode(struct super_block *sb)
{
	struct hfs_inode_info *i;

166
	i = kmem_cache_alloc(hfs_inode_cachep, GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
167 168 169
	return i ? &i->vfs_inode : NULL;
}

Nick Piggin's avatar
Nick Piggin committed
170
static void hfs_i_callback(struct rcu_head *head)
Linus Torvalds's avatar
Linus Torvalds committed
171
{
Nick Piggin's avatar
Nick Piggin committed
172
	struct inode *inode = container_of(head, struct inode, i_rcu);
Linus Torvalds's avatar
Linus Torvalds committed
173 174 175
	kmem_cache_free(hfs_inode_cachep, HFS_I(inode));
}

Nick Piggin's avatar
Nick Piggin committed
176 177 178 179 180
static void hfs_destroy_inode(struct inode *inode)
{
	call_rcu(&inode->i_rcu, hfs_i_callback);
}

181
static const struct super_operations hfs_super_operations = {
Linus Torvalds's avatar
Linus Torvalds committed
182 183 184
	.alloc_inode	= hfs_alloc_inode,
	.destroy_inode	= hfs_destroy_inode,
	.write_inode	= hfs_write_inode,
185
	.evict_inode	= hfs_evict_inode,
Linus Torvalds's avatar
Linus Torvalds committed
186
	.put_super	= hfs_put_super,
Christoph Hellwig's avatar
Christoph Hellwig committed
187
	.sync_fs	= hfs_sync_fs,
Linus Torvalds's avatar
Linus Torvalds committed
188 189
	.statfs		= hfs_statfs,
	.remount_fs     = hfs_remount,
190
	.show_options	= hfs_show_options,
Linus Torvalds's avatar
Linus Torvalds committed
191 192 193 194 195
};

enum {
	opt_uid, opt_gid, opt_umask, opt_file_umask, opt_dir_umask,
	opt_part, opt_session, opt_type, opt_creator, opt_quiet,
Roman Zippel's avatar
Roman Zippel committed
196
	opt_codepage, opt_iocharset,
Linus Torvalds's avatar
Linus Torvalds committed
197 198 199
	opt_err
};

200
static const match_table_t tokens = {
Linus Torvalds's avatar
Linus Torvalds committed
201 202 203 204 205 206 207 208 209 210
	{ opt_uid, "uid=%u" },
	{ opt_gid, "gid=%u" },
	{ opt_umask, "umask=%o" },
	{ opt_file_umask, "file_umask=%o" },
	{ opt_dir_umask, "dir_umask=%o" },
	{ opt_part, "part=%u" },
	{ opt_session, "session=%u" },
	{ opt_type, "type=%s" },
	{ opt_creator, "creator=%s" },
	{ opt_quiet, "quiet" },
Roman Zippel's avatar
Roman Zippel committed
211 212
	{ opt_codepage, "codepage=%s" },
	{ opt_iocharset, "iocharset=%s" },
Linus Torvalds's avatar
Linus Torvalds committed
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
	{ opt_err, NULL }
};

static inline int match_fourchar(substring_t *arg, u32 *result)
{
	if (arg->to - arg->from != 4)
		return -EINVAL;
	memcpy(result, arg->from, 4);
	return 0;
}

/*
 * parse_options()
 *
 * adapted from linux/fs/msdos/inode.c written 1992,93 by Werner Almesberger
 * This function is called by hfs_read_super() to parse the mount options.
 */
static int parse_options(char *options, struct hfs_sb_info *hsb)
{
	char *p;
	substring_t args[MAX_OPT_ARGS];
	int tmp, token;

	/* initialize the sb with defaults */
237 238
	hsb->s_uid = current_uid();
	hsb->s_gid = current_gid();
Linus Torvalds's avatar
Linus Torvalds committed
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
	hsb->s_file_umask = 0133;
	hsb->s_dir_umask = 0022;
	hsb->s_type = hsb->s_creator = cpu_to_be32(0x3f3f3f3f);	/* == '????' */
	hsb->s_quiet = 0;
	hsb->part = -1;
	hsb->session = -1;

	if (!options)
		return 1;

	while ((p = strsep(&options, ",")) != NULL) {
		if (!*p)
			continue;

		token = match_token(p, tokens, args);
		switch (token) {
		case opt_uid:
			if (match_int(&args[0], &tmp)) {
257
				pr_err("uid requires an argument\n");
Linus Torvalds's avatar
Linus Torvalds committed
258 259
				return 0;
			}
260 261
			hsb->s_uid = make_kuid(current_user_ns(), (uid_t)tmp);
			if (!uid_valid(hsb->s_uid)) {
262
				pr_err("invalid uid %d\n", tmp);
263 264
				return 0;
			}
Linus Torvalds's avatar
Linus Torvalds committed
265 266 267
			break;
		case opt_gid:
			if (match_int(&args[0], &tmp)) {
268
				pr_err("gid requires an argument\n");
Linus Torvalds's avatar
Linus Torvalds committed
269 270
				return 0;
			}
271 272
			hsb->s_gid = make_kgid(current_user_ns(), (gid_t)tmp);
			if (!gid_valid(hsb->s_gid)) {
273
				pr_err("invalid gid %d\n", tmp);
274 275
				return 0;
			}
Linus Torvalds's avatar
Linus Torvalds committed
276 277 278
			break;
		case opt_umask:
			if (match_octal(&args[0], &tmp)) {
279
				pr_err("umask requires a value\n");
Linus Torvalds's avatar
Linus Torvalds committed
280 281 282 283 284 285 286
				return 0;
			}
			hsb->s_file_umask = (umode_t)tmp;
			hsb->s_dir_umask = (umode_t)tmp;
			break;
		case opt_file_umask:
			if (match_octal(&args[0], &tmp)) {
287
				pr_err("file_umask requires a value\n");
Linus Torvalds's avatar
Linus Torvalds committed
288 289 290 291 292 293
				return 0;
			}
			hsb->s_file_umask = (umode_t)tmp;
			break;
		case opt_dir_umask:
			if (match_octal(&args[0], &tmp)) {
294
				pr_err("dir_umask requires a value\n");
Linus Torvalds's avatar
Linus Torvalds committed
295 296 297 298 299 300
				return 0;
			}
			hsb->s_dir_umask = (umode_t)tmp;
			break;
		case opt_part:
			if (match_int(&args[0], &hsb->part)) {
301
				pr_err("part requires an argument\n");
Linus Torvalds's avatar
Linus Torvalds committed
302 303 304 305 306
				return 0;
			}
			break;
		case opt_session:
			if (match_int(&args[0], &hsb->session)) {
307
				pr_err("session requires an argument\n");
Linus Torvalds's avatar
Linus Torvalds committed
308 309 310 311 312
				return 0;
			}
			break;
		case opt_type:
			if (match_fourchar(&args[0], &hsb->s_type)) {
313
				pr_err("type requires a 4 character value\n");
Linus Torvalds's avatar
Linus Torvalds committed
314 315 316 317 318
				return 0;
			}
			break;
		case opt_creator:
			if (match_fourchar(&args[0], &hsb->s_creator)) {
319
				pr_err("creator requires a 4 character value\n");
Linus Torvalds's avatar
Linus Torvalds committed
320 321 322 323 324 325
				return 0;
			}
			break;
		case opt_quiet:
			hsb->s_quiet = 1;
			break;
Roman Zippel's avatar
Roman Zippel committed
326 327
		case opt_codepage:
			if (hsb->nls_disk) {
328
				pr_err("unable to change codepage\n");
Roman Zippel's avatar
Roman Zippel committed
329 330 331
				return 0;
			}
			p = match_strdup(&args[0]);
332 333
			if (p)
				hsb->nls_disk = load_nls(p);
Roman Zippel's avatar
Roman Zippel committed
334
			if (!hsb->nls_disk) {
335
				pr_err("unable to load codepage \"%s\"\n", p);
Roman Zippel's avatar
Roman Zippel committed
336 337 338 339 340 341 342
				kfree(p);
				return 0;
			}
			kfree(p);
			break;
		case opt_iocharset:
			if (hsb->nls_io) {
343
				pr_err("unable to change iocharset\n");
Roman Zippel's avatar
Roman Zippel committed
344 345 346
				return 0;
			}
			p = match_strdup(&args[0]);
347 348
			if (p)
				hsb->nls_io = load_nls(p);
Roman Zippel's avatar
Roman Zippel committed
349
			if (!hsb->nls_io) {
350
				pr_err("unable to load iocharset \"%s\"\n", p);
Roman Zippel's avatar
Roman Zippel committed
351 352 353 354 355
				kfree(p);
				return 0;
			}
			kfree(p);
			break;
Linus Torvalds's avatar
Linus Torvalds committed
356 357 358 359 360
		default:
			return 0;
		}
	}

Roman Zippel's avatar
Roman Zippel committed
361 362 363
	if (hsb->nls_disk && !hsb->nls_io) {
		hsb->nls_io = load_nls_default();
		if (!hsb->nls_io) {
364
			pr_err("unable to load default iocharset\n");
Roman Zippel's avatar
Roman Zippel committed
365 366 367
			return 0;
		}
	}
Linus Torvalds's avatar
Linus Torvalds committed
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
	hsb->s_dir_umask &= 0777;
	hsb->s_file_umask &= 0577;

	return 1;
}

/*
 * hfs_read_super()
 *
 * This is the function that is responsible for mounting an HFS
 * filesystem.	It performs all the tasks necessary to get enough data
 * from the disk to read the root inode.  This includes parsing the
 * mount options, dealing with Macintosh partitions, reading the
 * superblock and the allocation bitmap blocks, calling
 * hfs_btree_init() to get the necessary data about the extents and
 * catalog B-trees and, finally, reading the root inode into memory.
 */
static int hfs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct hfs_sb_info *sbi;
	struct hfs_find_data fd;
	hfs_cat_rec rec;
	struct inode *root_inode;
	int res;

393
	sbi = kzalloc(sizeof(struct hfs_sb_info), GFP_KERNEL);
Jan Blunck's avatar
Jan Blunck committed
394
	if (!sbi)
Linus Torvalds's avatar
Linus Torvalds committed
395
		return -ENOMEM;
Jan Blunck's avatar
Jan Blunck committed
396

397
	sbi->sb = sb;
Linus Torvalds's avatar
Linus Torvalds committed
398
	sb->s_fs_info = sbi;
399 400
	spin_lock_init(&sbi->work_lock);
	INIT_DELAYED_WORK(&sbi->mdb_work, flush_mdb);
Linus Torvalds's avatar
Linus Torvalds committed
401 402 403

	res = -EINVAL;
	if (!parse_options((char *)data, sbi)) {
404
		pr_err("unable to parse mount options\n");
405
		goto bail;
Linus Torvalds's avatar
Linus Torvalds committed
406 407 408 409
	}

	sb->s_op = &hfs_super_operations;
	sb->s_flags |= MS_NODIRATIME;
410
	mutex_init(&sbi->bitmap_lock);
Linus Torvalds's avatar
Linus Torvalds committed
411 412 413 414

	res = hfs_mdb_get(sb);
	if (res) {
		if (!silent)
415
			pr_warn("can't find a HFS filesystem on dev %s\n",
Linus Torvalds's avatar
Linus Torvalds committed
416 417
				hfs_mdb_name(sb));
		res = -EINVAL;
418
		goto bail;
Linus Torvalds's avatar
Linus Torvalds committed
419 420 421
	}

	/* try to get the root inode */
422 423 424
	res = hfs_find_init(HFS_SB(sb)->cat_tree, &fd);
	if (res)
		goto bail_no_root;
Linus Torvalds's avatar
Linus Torvalds committed
425
	res = hfs_cat_find_brec(sb, HFS_ROOT_CNID, &fd);
426 427 428 429 430
	if (!res) {
		if (fd.entrylength > sizeof(rec) || fd.entrylength < 0) {
			res =  -EIO;
			goto bail;
		}
Linus Torvalds's avatar
Linus Torvalds committed
431
		hfs_bnode_read(fd.bnode, &rec, fd.entryoffset, fd.entrylength);
432
	}
Linus Torvalds's avatar
Linus Torvalds committed
433 434 435 436
	if (res) {
		hfs_find_exit(&fd);
		goto bail_no_root;
	}
437
	res = -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
438 439 440 441 442
	root_inode = hfs_iget(sb, &fd.search_key->cat, &rec);
	hfs_find_exit(&fd);
	if (!root_inode)
		goto bail_no_root;

Al Viro's avatar
Al Viro committed
443
	sb->s_d_op = &hfs_dentry_operations;
444
	res = -ENOMEM;
445
	sb->s_root = d_make_root(root_inode);
Linus Torvalds's avatar
Linus Torvalds committed
446
	if (!sb->s_root)
447
		goto bail_no_root;
Linus Torvalds's avatar
Linus Torvalds committed
448 449 450 451 452

	/* everything's okay */
	return 0;

bail_no_root:
453
	pr_err("get root inode failed\n");
454
bail:
Linus Torvalds's avatar
Linus Torvalds committed
455 456 457 458
	hfs_mdb_put(sb);
	return res;
}

Al Viro's avatar
Al Viro committed
459 460
static struct dentry *hfs_mount(struct file_system_type *fs_type,
		      int flags, const char *dev_name, void *data)
Linus Torvalds's avatar
Linus Torvalds committed
461
{
Al Viro's avatar
Al Viro committed
462
	return mount_bdev(fs_type, flags, dev_name, data, hfs_fill_super);
Linus Torvalds's avatar
Linus Torvalds committed
463 464 465 466 467
}

static struct file_system_type hfs_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "hfs",
Al Viro's avatar
Al Viro committed
468
	.mount		= hfs_mount,
Linus Torvalds's avatar
Linus Torvalds committed
469 470 471
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};
472
MODULE_ALIAS_FS("hfs");
Linus Torvalds's avatar
Linus Torvalds committed
473

474
static void hfs_init_once(void *p)
Linus Torvalds's avatar
Linus Torvalds committed
475 476 477
{
	struct hfs_inode_info *i = p;

478
	inode_init_once(&i->vfs_inode);
Linus Torvalds's avatar
Linus Torvalds committed
479 480 481 482 483 484 485 486
}

static int __init init_hfs_fs(void)
{
	int err;

	hfs_inode_cachep = kmem_cache_create("hfs_inode_cache",
		sizeof(struct hfs_inode_info), 0, SLAB_HWCACHE_ALIGN,
487
		hfs_init_once);
Linus Torvalds's avatar
Linus Torvalds committed
488 489 490 491 492 493 494 495 496 497 498
	if (!hfs_inode_cachep)
		return -ENOMEM;
	err = register_filesystem(&hfs_fs_type);
	if (err)
		kmem_cache_destroy(hfs_inode_cachep);
	return err;
}

static void __exit exit_hfs_fs(void)
{
	unregister_filesystem(&hfs_fs_type);
499 500 501 502 503 504

	/*
	 * Make sure all delayed rcu free inodes are flushed before we
	 * destroy cache.
	 */
	rcu_barrier();
505
	kmem_cache_destroy(hfs_inode_cachep);
Linus Torvalds's avatar
Linus Torvalds committed
506 507 508 509
}

module_init(init_hfs_fs)
module_exit(exit_hfs_fs)