/*
 * fs/sdcardfs/main.c
 *
 * Copyright (c) 2013 Samsung Electronics Co. Ltd
 *   Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun,
 *               Sunghwan Yun, Sungjong Seo
 *
 * This program has been developed as a stackable file system based on
 * the WrapFS which written by
 *
 * Copyright (c) 1998-2011 Erez Zadok
 * Copyright (c) 2009     Shrikar Archak
 * Copyright (c) 2003-2011 Stony Brook University
 * Copyright (c) 2003-2011 The Research Foundation of SUNY
 *
 * This file is dual licensed.  It may be redistributed and/or modified
 * under the terms of the Apache 2.0 License OR version 2 of the GNU
 * General Public License.
 */

#include "sdcardfs.h"
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
#include <linux/fscrypt.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/parser.h>

enum sdcardfs_param {
	Opt_fsuid,
	Opt_fsgid,
	Opt_gid,
	Opt_debug,
	Opt_mask,
	Opt_multiuser,
	Opt_userid,
	Opt_reserved_mb,
	Opt_gid_derivation,
	Opt_default_normal,
	Opt_nocache,
	Opt_unshared_obb,
	Opt_err,
};

static const struct fs_parameter_spec sdcardfs_param_specs[] = {
	fsparam_u32("fsuid", Opt_fsuid),
	fsparam_u32("fsgid", Opt_fsgid),
	fsparam_u32("gid", Opt_gid),
	fsparam_bool("debug", Opt_debug),
	fsparam_u32("mask", Opt_mask),
	fsparam_u32("userid", Opt_userid),
	fsparam_bool("multiuser", Opt_multiuser),
	fsparam_bool("derive_gid", Opt_gid_derivation),
	fsparam_bool("default_normal", Opt_default_normal),
	fsparam_bool("unshared_obb", Opt_unshared_obb),
	fsparam_u32("reserved_mb", Opt_reserved_mb),
	fsparam_bool("nocache", Opt_nocache),
	{}
};

static const struct fs_parameter_description sdcardfs_parameters = {
	.name	= "sdcardfs",
	.specs	= sdcardfs_param_specs,
};

static int sdcardfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
	struct sdcardfs_context_options *fc_opts = fc->fs_private;
	struct sdcardfs_mount_options *opts = &fc_opts->opts;
	struct sdcardfs_vfsmount_options *vfsopts = &fc_opts->vfsopts;
	struct fs_parse_result result;
	int opt;

	opt = fs_parse(fc, &sdcardfs_parameters, param, &result);
	if (opt < 0)
		return opt;

	switch (opt) {
	case Opt_debug:
		opts->debug = true;
		break;
	case Opt_fsuid:
		opts->fs_low_uid = result.uint_32;
		break;
	case Opt_fsgid:
		opts->fs_low_gid = result.uint_32;
		break;
	case Opt_gid:
		vfsopts->gid = result.uint_32;
		break;
	case Opt_userid:
		opts->fs_user_id = result.uint_32;
		break;
	case Opt_mask:
		vfsopts->mask = result.uint_32;
		break;
	case Opt_multiuser:
		opts->multiuser = true;
		break;
	case Opt_reserved_mb:
		opts->reserved_mb = result.uint_32;
		break;
	case Opt_gid_derivation:
		opts->gid_derivation = true;
		break;
	case Opt_default_normal:
		opts->default_normal = true;
		break;
	case Opt_nocache:
		opts->nocache = true;
		break;
	case Opt_unshared_obb:
		opts->unshared_obb = true;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static void copy_sb_opts(struct sdcardfs_mount_options *opts,
		struct fs_context *fc)
{
	struct sdcardfs_context_options *fcopts = fc->fs_private;

	opts->debug = fcopts->opts.debug;
	opts->default_normal = fcopts->opts.default_normal;
	opts->fs_low_gid = fcopts->opts.fs_low_gid;
	opts->fs_low_uid = fcopts->opts.fs_low_uid;
	opts->fs_user_id = fcopts->opts.fs_user_id;
	opts->gid_derivation = fcopts->opts.gid_derivation;
	opts->multiuser = fcopts->opts.multiuser;
	opts->nocache = fcopts->opts.nocache;
	opts->reserved_mb = fcopts->opts.reserved_mb;
	opts->unshared_obb = fcopts->opts.unshared_obb;
}

#if 0
/*
 * our custom d_alloc_root work-alike
 *
 * we can't use d_alloc_root if we want to use our own interpose function
 * unchanged, so we simply call our own "fake" d_alloc_root
 */
static struct dentry *sdcardfs_d_alloc_root(struct super_block *sb)
{
	struct dentry *ret = NULL;

	if (sb) {
		static const struct qstr name = {
			.name = "/",
			.len = 1
		};

		ret = d_alloc(NULL, &name);
		if (ret) {
			d_set_d_op(ret, &sdcardfs_ci_dops);
			ret->d_sb = sb;
			ret->d_parent = ret;
		}
	}
	return ret;
}
#endif

DEFINE_MUTEX(sdcardfs_super_list_lock);
EXPORT_SYMBOL_GPL(sdcardfs_super_list_lock);
LIST_HEAD(sdcardfs_super_list);
EXPORT_SYMBOL_GPL(sdcardfs_super_list);

struct sdcardfs_mount_private {
	struct vfsmount *mnt;
	const char *dev_name;
	void *raw_data;
};

static int __sdcardfs_fill_super(
	struct super_block *sb,
	struct fs_context *fc)
{
	int err = 0;
	struct super_block *lower_sb;
	struct path lower_path;
	struct sdcardfs_sb_info *sb_info;
	struct inode *inode;
	const char *dev_name = fc->source;
	struct sdcardfs_context_options *fcopts = fc->fs_private;
	struct sdcardfs_mount_options *opts = &fcopts->opts;
	struct sdcardfs_vfsmount_options *mntopts = &fcopts->vfsopts;

	pr_info("sdcardfs version 2.0\n");

	if (!dev_name) {
		pr_err("sdcardfs: read_super: missing dev_name argument\n");
		err = -EINVAL;
		goto out;
	}

	pr_info("sdcardfs: dev_name -> %s\n", dev_name);
	pr_info("sdcardfs: gid=%d,mask=%x\n", mntopts->gid, mntopts->mask);

	/* parse lower path */
	err = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
			&lower_path);
	if (err) {
		pr_err("sdcardfs: error accessing lower directory '%s'\n", dev_name);
		goto out;
	}

	/* allocate superblock private data */
	sb->s_fs_info = kzalloc(sizeof(struct sdcardfs_sb_info), GFP_KERNEL);
	if (!SDCARDFS_SB(sb)) {
		pr_crit("sdcardfs: read_super: out of memory\n");
		err = -ENOMEM;
		goto out_free;
	}

	sb_info = sb->s_fs_info;
	copy_sb_opts(&sb_info->options, fc);
	if (opts->debug) {
		pr_info("sdcardfs : options - debug:%d\n", opts->debug);
		pr_info("sdcardfs : options - gid:%d\n", mntopts->gid);
		pr_info("sdcardfs : options - mask:%d\n", mntopts->mask);
	}

	/* set the lower superblock field of upper superblock */
	lower_sb = lower_path.dentry->d_sb;
	atomic_inc(&lower_sb->s_active);
	sdcardfs_set_lower_super(sb, lower_sb);

	sb->s_stack_depth = lower_sb->s_stack_depth + 1;
	if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) {
		pr_err("sdcardfs: maximum fs stacking depth exceeded\n");
		err = -EINVAL;
		goto out_sput;
	}

	/* inherit maxbytes from lower file system */
	sb->s_maxbytes = lower_sb->s_maxbytes;

	/*
	 * Our c/m/atime granularity is 1 ns because we may stack on file
	 * systems whose granularity is as good.
	 */
	sb->s_time_gran = 1;

	sb->s_magic = SDCARDFS_SUPER_MAGIC;
	sb->s_op = &sdcardfs_sops;

	/* get a new inode and allocate our root dentry */
	inode = sdcardfs_iget(sb, d_inode(lower_path.dentry), 0);
	if (IS_ERR(inode)) {
		err = PTR_ERR(inode);
		goto out_sput;
	}
	sb->s_root = d_make_root(inode);
	if (!sb->s_root) {
		err = -ENOMEM;
		goto out_sput;
	}
	d_set_d_op(sb->s_root, &sdcardfs_ci_dops);

	/* link the upper and lower dentries */
	sb->s_root->d_fsdata = NULL;
	err = new_dentry_private_data(sb->s_root);
	if (err)
		goto out_freeroot;

	/* set the lower dentries for s_root */
	sdcardfs_set_lower_path(sb->s_root, &lower_path);

	/*
	 * No need to call interpose because we already have a positive
	 * dentry, which was instantiated by d_make_root.  Just need to
	 * d_rehash it.
	 */
	d_rehash(sb->s_root);

	/* setup permission policy */
	sb_info->obbpath_s = kzalloc(PATH_MAX, GFP_KERNEL);
	mutex_lock(&sdcardfs_super_list_lock);
	if (sb_info->options.multiuser) {
		setup_derived_state(d_inode(sb->s_root), PERM_PRE_ROOT,
				sb_info->options.fs_user_id, AID_ROOT);
		snprintf(sb_info->obbpath_s, PATH_MAX, "%s/obb", dev_name);
	} else {
		setup_derived_state(d_inode(sb->s_root), PERM_ROOT,
				sb_info->options.fs_user_id, AID_ROOT);
		snprintf(sb_info->obbpath_s, PATH_MAX, "%s/Android/obb", dev_name);
	}
	fixup_tmp_permissions(d_inode(sb->s_root));
	sb_info->sb = sb;
	list_add(&sb_info->list, &sdcardfs_super_list);
	mutex_unlock(&sdcardfs_super_list_lock);

	sb_info->fscrypt_nb.notifier_call = sdcardfs_on_fscrypt_key_removed;
	fscrypt_register_key_removal_notifier(&sb_info->fscrypt_nb);

	if (!(fc->sb_flags & SB_SILENT))
		pr_info("sdcardfs: mounted on top of %s type %s\n",
				dev_name, lower_sb->s_type->name);
	goto out; /* all is well */

	/* no longer needed: free_dentry_private_data(sb->s_root); */
out_freeroot:
	dput(sb->s_root);
	sb->s_root = NULL;
out_sput:
	/* drop refs we took earlier */
	atomic_dec(&lower_sb->s_active);
	kfree(SDCARDFS_SB(sb));
	sb->s_fs_info = NULL;
out_free:
	path_put(&lower_path);

out:
	return err;
}

static int sdcardfs_get_tree(struct fs_context *fc)
{
	return vfs_get_super(fc, vfs_get_independent_super,
			__sdcardfs_fill_super);
}

void *sdcardfs_alloc_mnt_data(void)
{
	return kmalloc(sizeof(struct sdcardfs_vfsmount_options), GFP_KERNEL);
}

void sdcardfs_kill_sb(struct super_block *sb)
{
	struct sdcardfs_sb_info *sbi;

	if (sb->s_magic == SDCARDFS_SUPER_MAGIC && sb->s_fs_info) {
		sbi = SDCARDFS_SB(sb);

		fscrypt_unregister_key_removal_notifier(&sbi->fscrypt_nb);

		mutex_lock(&sdcardfs_super_list_lock);
		list_del(&sbi->list);
		mutex_unlock(&sdcardfs_super_list_lock);
	}
	kill_anon_super(sb);
}

static void sdcardfs_free_fs_context(struct fs_context *fc)
{
	struct sdcardfs_context_options *fc_opts = fc->fs_private;

	kfree(fc_opts);
}

/* Most of the remount happens in sdcardfs_update_mnt_data */
static int sdcardfs_reconfigure_context(struct fs_context *fc)
{
	struct sdcardfs_context_options *fc_opts = fc->fs_private;
	struct sdcardfs_sb_info *sbi = SDCARDFS_SB(fc->root->d_sb);

	sbi->options.debug = fc_opts->opts.debug;
	if (sbi->options.debug) {
		pr_info("sdcardfs : options - debug:%d\n", sbi->options.debug);
		pr_info("sdcardfs : options - gid:%d\n", fc_opts->vfsopts.gid);
		pr_info("sdcardfs : options - mask:%d\n",
						fc_opts->vfsopts.mask);
	}
	return 0;
}

/* reconfigure is handled by sdcardfs_update_mnt_data */
static const struct fs_context_operations sdcardfs_context_options_ops = {

    .parse_param    = sdcardfs_parse_param,
    .get_tree   = sdcardfs_get_tree,
    .free       = sdcardfs_free_fs_context,
    .reconfigure = sdcardfs_reconfigure_context,
};

static int sdcardfs_init_fs_context(struct fs_context *fc)
{
	struct sdcardfs_context_options *fc_opts =
		kmalloc(sizeof(struct sdcardfs_context_options), GFP_KERNEL);

	/* by default, we use AID_MEDIA_RW as uid, gid */
	fc_opts->opts.fs_low_uid = AID_MEDIA_RW;
	fc_opts->opts.fs_low_gid = AID_MEDIA_RW;
	fc_opts->opts.fs_user_id = 0;
	fc_opts->vfsopts.gid = 0;
	fc_opts->vfsopts.mask = 0;

	/* by default, 0MB is reserved */
	fc_opts->opts.reserved_mb = 0;
	/* by default, gid derivation is off */
	fc_opts->opts.gid_derivation = false;
	fc_opts->opts.default_normal = false;
	fc_opts->opts.nocache = false;
	fc_opts->opts.multiuser = false;
	fc_opts->opts.debug = false;

	fc->fs_private = fc_opts;
	fc->ops = &sdcardfs_context_options_ops;
	return 0;
}

static struct file_system_type sdcardfs_fs_type = {
	.owner		= THIS_MODULE,
	.name		= SDCARDFS_NAME,
	.alloc_mnt_data = sdcardfs_alloc_mnt_data,
	.kill_sb	= sdcardfs_kill_sb,
	.init_fs_context = sdcardfs_init_fs_context,
	.fs_flags	= 0,
};
MODULE_ALIAS_FS(SDCARDFS_NAME);

static int __init init_sdcardfs_fs(void)
{
	int err;

	pr_info("Registering sdcardfs " SDCARDFS_VERSION "\n");

	err = sdcardfs_init_inode_cache();
	if (err)
		goto out;
	err = sdcardfs_init_dentry_cache();
	if (err)
		goto out;
	err = packagelist_init();
	if (err)
		goto out;
	err = register_filesystem(&sdcardfs_fs_type);
out:
	if (err) {
		sdcardfs_destroy_inode_cache();
		sdcardfs_destroy_dentry_cache();
		packagelist_exit();
	}
	return err;
}

static void __exit exit_sdcardfs_fs(void)
{
	sdcardfs_destroy_inode_cache();
	sdcardfs_destroy_dentry_cache();
	packagelist_exit();
	unregister_filesystem(&sdcardfs_fs_type);
	pr_info("Completed sdcardfs module unload\n");
}

/* Original wrapfs authors */
MODULE_AUTHOR("Erez Zadok, Filesystems and Storage Lab, Stony Brook University (http://www.fsl.cs.sunysb.edu/)");

/* Original sdcardfs authors */
MODULE_AUTHOR("Woojoong Lee, Daeho Jeong, Kitae Lee, Yeongjin Gil System Memory Lab., Samsung Electronics");

/* Current maintainer */
MODULE_AUTHOR("Daniel Rosenberg, Google");
MODULE_DESCRIPTION("Sdcardfs " SDCARDFS_VERSION);
MODULE_LICENSE("GPL");

module_init(init_sdcardfs_fs);
module_exit(exit_sdcardfs_fs);