/*
 * Copyright (C) 2005-2013 Junjiro R. Okajima
 *
 * This program, aufs is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * module global variables and operations
 */

#include <linux/module.h>
#include <linux/seq_file.h>
#include "aufs.h"

void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp)
{
	if (new_sz <= nused)
		return p;

	p = krealloc(p, new_sz, gfp);
	if (p)
		memset(p + nused, 0, new_sz - nused);
	return p;
}

/* ---------------------------------------------------------------------- */

/*
 * aufs caches
 */
struct kmem_cache *au_cachep[AuCache_Last];
static int __init au_cache_init(void)
{
	au_cachep[AuCache_DINFO] = AuCacheCtor(au_dinfo, au_di_init_once);
	if (au_cachep[AuCache_DINFO])
		/* SLAB_DESTROY_BY_RCU */
		au_cachep[AuCache_ICNTNR] = AuCacheCtor(au_icntnr,
							au_icntnr_init_once);
	if (au_cachep[AuCache_ICNTNR])
		au_cachep[AuCache_FINFO] = AuCacheCtor(au_finfo,
						       au_fi_init_once);
	if (au_cachep[AuCache_FINFO])
		au_cachep[AuCache_VDIR] = AuCache(au_vdir);
	if (au_cachep[AuCache_VDIR])
		au_cachep[AuCache_DEHSTR] = AuCache(au_vdir_dehstr);
	if (au_cachep[AuCache_DEHSTR])
		return 0;

	return -ENOMEM;
}

static void au_cache_fin(void)
{
	int i;

	/*
	 * Make sure all delayed rcu free inodes are flushed before we
	 * destroy cache.
	 */
	rcu_barrier();

	/* excluding AuCache_HNOTIFY */
	BUILD_BUG_ON(AuCache_HNOTIFY + 1 != AuCache_Last);
	for (i = 0; i < AuCache_HNOTIFY; i++)
		if (au_cachep[i]) {
			kmem_cache_destroy(au_cachep[i]);
			au_cachep[i] = NULL;
		}
}

/* ---------------------------------------------------------------------- */

int au_dir_roflags;

#ifdef CONFIG_AUFS_SBILIST
/*
 * iterate_supers_type() doesn't protect us from
 * remounting (branch management)
 */
struct au_splhead au_sbilist;
#endif

struct lock_class_key au_lc_key[AuLcKey_Last];

/*
 * functions for module interface.
 */
MODULE_LICENSE("GPL");
/* MODULE_LICENSE("GPL v2"); */
MODULE_AUTHOR("Junjiro R. Okajima <aufs-users@lists.sourceforge.net>");
MODULE_DESCRIPTION(AUFS_NAME
	" -- Advanced multi layered unification filesystem");
MODULE_VERSION(AUFS_VERSION);

/* this module parameter has no meaning when SYSFS is disabled */
int sysaufs_brs = 1;
MODULE_PARM_DESC(brs, "use <sysfs>/fs/aufs/si_*/brN");
module_param_named(brs, sysaufs_brs, int, S_IRUGO);

/* ---------------------------------------------------------------------- */

static char au_esc_chars[0x20 + 3]; /* 0x01-0x20, backslash, del, and NULL */

int au_seq_path(struct seq_file *seq, struct path *path)
{
	return seq_path(seq, path, au_esc_chars);
}

/* ---------------------------------------------------------------------- */

static int __init aufs_init(void)
{
	int err, i;
	char *p;

	p = au_esc_chars;
	for (i = 1; i <= ' '; i++)
		*p++ = i;
	*p++ = '\\';
	*p++ = '\x7f';
	*p = 0;

	au_dir_roflags = au_file_roflags(O_DIRECTORY | O_LARGEFILE);

	au_sbilist_init();
	sysaufs_brs_init();
	au_debug_init();
	au_dy_init();
	err = sysaufs_init();
	if (unlikely(err))
		goto out;
	err = au_procfs_init();
	if (unlikely(err))
		goto out_sysaufs;
	err = au_wkq_init();
	if (unlikely(err))
		goto out_procfs;
	err = au_loopback_init();
	if (unlikely(err))
		goto out_wkq;
	err = au_hnotify_init();
	if (unlikely(err))
		goto out_loopback;
	err = au_sysrq_init();
	if (unlikely(err))
		goto out_hin;
	err = au_cache_init();
	if (unlikely(err))
		goto out_sysrq;
	err = register_filesystem(&aufs_fs_type);
	if (unlikely(err))
		goto out_cache;
	/* since we define pr_fmt, call printk directly */
	printk(KERN_INFO AUFS_NAME " " AUFS_VERSION "\n");
	goto out; /* success */

out_cache:
	au_cache_fin();
out_sysrq:
	au_sysrq_fin();
out_hin:
	au_hnotify_fin();
out_loopback:
	au_loopback_fin();
out_wkq:
	au_wkq_fin();
out_procfs:
	au_procfs_fin();
out_sysaufs:
	sysaufs_fin();
	au_dy_fin();
out:
	return err;
}

static void __exit aufs_exit(void)
{
	unregister_filesystem(&aufs_fs_type);
	au_cache_fin();
	au_sysrq_fin();
	au_hnotify_fin();
	au_loopback_fin();
	au_wkq_fin();
	au_procfs_fin();
	sysaufs_fin();
	au_dy_fin();
}

module_init(aufs_init);
module_exit(aufs_exit);
