#include "spadfs.h"

static void SPADFS_FREE_SUPER(struct super_block *s);

static struct super_operations spadfs_sops;
static kmem_cache_t *spadfs_inode_cachep;

static void fnode_ctor(void *fn_, kmem_cache_t *cachep, unsigned long flags)
{
	SPADFNODE *fn = fn_;
	if (__likely((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == SLAB_CTOR_CONSTRUCTOR)) {
		inode_init_once(inode(fn));
		init_MUTEX(&fn->lock);
	}
}

static struct inode *SPADFS_ALLOC_INODE(struct super_block *sb)
{
	SPADFNODE *fn = kmem_cache_alloc(spadfs_inode_cachep, SLAB_KERNEL);
	SPADFS *fs = spadfs(sb);
	fn->fs = fs;
	fn->parent_fnode_block = 0;
	fn->parent_fnode_pos = 0;
	down(&fs->inode_list_lock);
	hlist_add_head(&fn->inode_list, &fs->inode_list);
	up(&fs->inode_list_lock);
	inode(fn)->i_version = 1;
	return inode(fn);
}

static void SPADFS_DESTROY_INODE(struct inode *inode)
{
	SPADFNODE *fn = spadfnode(inode);
	SPADFS *fs = fn->fs;
	down(&fs->inode_list_lock);
	hlist_del(&fn->inode_list);
	up(&fs->inode_list_lock);
	kmem_cache_free(spadfs_inode_cachep, fn);
}

enum {
	Opt_help, Opt_uid, Opt_gid, Opt_umask,
	Opt_prealloc_part, Opt_min_prealloc, Opt_max_prealloc,
	Opt_xfer_size, Opt_sync_time,
	Opt_no_checksums, Opt_checksums,
	Opt_err,
};

static match_table_t tokens = {
	{Opt_help, "help"},
	{Opt_uid, "uid=%u"},
	{Opt_gid, "gid=%u"},
	{Opt_umask, "umask=%o"},
	{Opt_prealloc_part, "prealloc_part=%u"},
	{Opt_min_prealloc, "prealloc_min=%u"},
	{Opt_max_prealloc, "prealloc_max=%u"},
	{Opt_xfer_size, "xfer_size=%u"},
	{Opt_sync_time, "sync_time=%u"},
	{Opt_no_checksums, "no_checksums"},
	{Opt_checksums, "checksums"},
	{Opt_err, NULL},
};

static int parse_opts(char *opts, SPADFS *fs)
{
	char *p;
	while ((p = strsep(&opts, ","))) {
		substring_t args[MAX_OPT_ARGS];
		int token, option;
		if (!*p) continue;
		token = match_token(p, tokens, args);
		switch (token) {
			case Opt_help:
				return 2;
			case Opt_uid:
				if (match_int(args, &option))
					return 0;
				fs->uid = option;
				break;
			case Opt_gid:
				if (match_int(args, &option))
					return 0;
				fs->gid = option;
				break;
			case Opt_umask:
				if (match_octal(args, &option))
					return 0;
				if (option < 0 || option > 0777)
					return 0;
				fs->mode = 0777 & ~option;
				break;
			case Opt_prealloc_part:
				if (match_int(args, &option))
					return 0;
				if (option <= 0) return 0;
				fs->prealloc_part = option;
				break;
			case Opt_min_prealloc:
				if (match_int(args, &option))
					return 0;
				if (option < 0) return 0;
				fs->min_prealloc = option;
				break;
			case Opt_max_prealloc:
				if (match_int(args, &option))
					return 0;
				if (option < 0) return 0;
				fs->max_prealloc = option;
				break;
			case Opt_xfer_size:
				if (match_int(args, &option))
					return 0;
				if (option <= 0) return 0;
				fs->xfer_size = option;
				break;
			case Opt_sync_time:
				if (match_int(args, &option))
					return 0;
				if (option <= 0 || option >= (1UL << (BITS_PER_LONG - 1)) / HZ) return 0;
				fs->spadfs_sync_time = option * HZ;
				break;
			case Opt_checksums:
				fs->mount_flags |= MOUNT_FLAGS_CHECKSUMS_OVERRIDE;
				fs->mount_flags |= MOUNT_FLAGS_CHECKSUMS;
				break;
			case Opt_no_checksums:
				fs->mount_flags |= MOUNT_FLAGS_CHECKSUMS_OVERRIDE;
				fs->mount_flags &= ~MOUNT_FLAGS_CHECKSUMS;
				break;
			default:
				return 0;
		}
	}
	return 1;
}

static void spadfs_help(void)
{
	printk("\n\
SPADFS filesystem options:\n\
        help                    display this text\n\
        uid=xxx                 set default uid of files\n\
        gid=xxx                 set default gid of files\n\
        umask=xxx               set default mode of files\n\
        prealloc_part=xxx       prealloc this part of existing file size\n\
                                (i.e. 8 means prealloc 1/8 of file size)\n\
        prealloc_min=xxx        minimum preallocation in bytes\n\
        prealloc_max=xxx        maximum preallocation in bytes\n\
        xfer_size=xxx           optimal request size reported in st_blksize\n\
        sync_time=xxx           sync after this interval in seconds\n\
	no_checksums		do not check and make checksums\n\
	checksums		do check and make checksums\n\
\n");
}

void SPADFS_ERROR(SPADFS *fs, unsigned flags, char *msg, ...)
{
	if (msg) {
		va_list va;
		va_start(va, msg);
		printk("SPADFS: ");
		vprintk(msg, va);
		printk("\n");
		va_end(va);
	}
	fs->txflags_new |= flags;
}

int SPADFS_STOP_CYCLES(SPADFS *fs, __d_off key, __d_off (*c)[2], char *msg)
{
	if (__unlikely((*c)[0] == key && __unlikely((*c)[1] != 0))) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "CYCLE DETECTED ON KEY %LX IN %s", (unsigned long long)key, msg);
		return 1;
	}
	(*c)[1]++;
	if (__likely(!(((*c)[1] - 1) & (*c)[1]))) (*c)[0] = key;
	return 0;
}

void SPADFS_TX_BLOCK_CHECKSUM(struct txblock *tb)
{
	tb->checksum ^= CHECKSUM_BASE ^ __byte_sum(tb, 512);
}

static int SPADFS_INCREASE_CC(SPADFS *fs, int inc)
{
	struct txblock *tb;
	struct buffer_head *bh;
	int r, rr = 0;
	if (__unlikely(fs->s->s_flags & MS_RDONLY)) return 0;
	tb = SPADFS_READ_TX_BLOCK(fs, &bh, "SPADFS_INCREASE_CC");
	if (__unlikely(IS_ERR(tb))) return PTR_ERR(tb);
	start_atomic_buffer_modify(bh);
	if (__likely(inc)) {
		tb->cc += inc;
		/* !!! FIXME: process wrap-around */
		if (inc == 1 && __unlikely(fs->cct[tb->cc])) {
			SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "CRASH COUNT TABLE IS DAMAGED (NON-ZERO AT %04X: %08X)", (unsigned)tb->cc, (unsigned)fs->cct[tb->cc]);
			rr = -EFSERROR;
		}
		fs->cc = tb->cc - 1;
		fs->txc = fs->cct[fs->cc];
	}
	tb->txflags |= fs->txflags_new;
	fs->txflags |= fs->txflags_new;
	SPADFS_TX_BLOCK_CHECKSUM(tb);
	end_atomic_buffer_modify(bh);
	r = sync_dirty_buffer(bh);
	if (__unlikely(r)) SPADFS_ERROR(fs, TXFLAGS_IO_WRITE_ERROR, "WRITE ERROR AT TX BLOCK");
	brelse(bh);
	return r ? r : rr;
}

static __finline__ int SPADFS_UPDATE_ERROR_FLAGS(SPADFS *fs)
{
	int r = 0;
	if (__unlikely(fs->txflags_new & ~fs->txflags)) {
		r = SPADFS_INCREASE_CC(fs, 0);
	}
	return r;
}

static int SPADFS_START_NEW_TX(SPADFS *fs)
{
	int r;
	if (__unlikely(fs->cct[fs->cc] == 0x7fffffff)) {
		if (__unlikely(r = SPADFS_INCREASE_CC(fs, 1))) return r;
	} else {
		if (__unlikely(r = SPADFS_UPDATE_ERROR_FLAGS(fs))) return r;
	}
	fs->txc = ++fs->cct[fs->cc];
	return 0;
}

int SPADFS_COMMIT(SPADFS *fs)
{
	int r, rr;
	unsigned sector;
	void *cct_data;
	struct buffer_head *bh;
	if (__unlikely(fs->s->s_flags & MS_RDONLY)) return 0;
	down_write(&fs->sync_lock);
	r = sync_blockdev(fs->s->s_bdev);
	if (__unlikely(r)) SPADFS_ERROR(fs, TXFLAGS_IO_WRITE_ERROR, "WRITE ERROR");
	sector = fs->cc / (512 / 4);
	sector &= ~((1 << fs->sectors_per_block_bits) - 1);
	cct_data = SPADFS_GET_NEW_SECTOR(fs, fs->cct_sec + sector, &bh, "SPADFS_COMMIT");
	if (__unlikely(IS_ERR(cct_data))) {
		if (!r) r = PTR_ERR(cct_data);
		goto unlock_ret;
	}
	start_atomic_buffer_modify(bh);
	memcpy(cct_data, (char *)fs->cct + (sector << 9), fs->blocksize);
	end_atomic_buffer_modify(bh);
	if (__unlikely(rr = sync_dirty_buffer(bh))) {
		if (!r) r = rr;
		brelse(bh);
		goto unlock_ret;
	}
	brelse(bh);
	if (__unlikely(rr = SPADFS_START_NEW_TX(fs))) {
		if (!r) r = rr;
	}
	unlock_ret:
	up_write(&fs->sync_lock);
	return r;
}

static int SPADFS_SYNC_FS(struct super_block *s, int wait)
{
	if (!wait) return filemap_fdatawrite(s->s_bdev->bd_inode->i_mapping);
	return SPADFS_COMMIT(spadfs(s));
}

static void SPADFS_SYNC_WORK(void *fs_)
{
	SPADFS *fs = fs_;
	sync_blockdev(fs->s->s_bdev);
	SPADFS_COMMIT(fs);
	queue_delayed_work(fs->spadfs_syncer, &fs->spadfs_sync_work, fs->spadfs_sync_time);
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
static int SPADFS_STATFS(struct dentry *dentry, struct kstatfs *ks)
{
	struct super_block *s = dentry->d_sb;
#else
static int SPADFS_STATFS(struct super_block *s, struct kstatfs *ks)
{
#endif
	SPADFS *fs = spadfs(s);
	down(&fs->alloc_lock);
	ks->f_type = s->s_magic;
	ks->f_bsize = fs->blocksize;
	ks->f_blocks = fs->size >> fs->sectors_per_block_bits;
	ks->f_bfree = fs->freespace >> fs->sectors_per_block_bits;
	ks->f_bavail = fs->freespace >> fs->sectors_per_block_bits;
	ks->f_files = 0;
	ks->f_ffree = 0;
	ks->f_namelen = 255;
	up(&fs->alloc_lock);
	return 0;
}

static int SPADFS_FILL_SUPER(struct super_block *s, void *options, int silent)
{
	SPADFS *fs;
	int blocksize;
	int r;
	int o;
	unsigned i, j, n;
	__d_off sec;
	struct buffer_head *bh;
	struct superblock *sbp;
	__u64 sbx[64];
#define sb	((struct superblock *)(&sbx))
	struct txblock *tb;
	char *msg;
	int cycle;
	struct inode *root;

	if (__unlikely(!(fs = kmalloc(sizeof(SPADFS), GFP_KERNEL)))) return -ENOMEM;
	s->s_fs_info = fs;
	s->s_flags |= MS_NOATIME;
	s->s_magic = MEMORY_SUPER_MAGIC;
	s->s_op = &spadfs_sops;
	s->s_maxbytes = ((__u64)512 << 48) - 512;
	memset(fs, 0, sizeof(SPADFS));
	fs->s = s;
	init_MUTEX(&fs->alloc_lock);
	init_rwsem(&fs->sync_lock);
	init_MUTEX(&fs->inode_list_lock);
	INIT_HLIST_HEAD(&fs->inode_list);

	fs->uid = current->uid;
	fs->gid = current->gid;
	fs->mode = 0777 & ~current->fs->umask;
	fs->prealloc_part = SPADFS_DEFAULT_PREALLOC_PART;
	fs->min_prealloc = SPADFS_DEFAULT_MIN_PREALLOC;
	fs->max_prealloc = SPADFS_DEFAULT_MAX_PREALLOC;
	fs->xfer_size = PAGE_SIZE;
	fs->spadfs_sync_time = SPADFS_DEFAULT_SYNC_TIME;

	o = parse_opts(options, fs);
	if (__unlikely(!o)) {
		printk("SPADFS: BAD MOUNT OPTIONS\n");
		r = -EINVAL;
		goto err0;
	}
	if (__unlikely(o == 2)) {
		spadfs_help();
		r = -EAGAIN;
		goto err0;
	}

	blocksize = sb_min_blocksize(s, 512);
	if (__unlikely(!blocksize)) {
		printk("SPADFS: UNABLE TO SET BLOCKSIZE\n");
		r = -EIO;
		goto err0;
	}
	cycle = 0;
	read_again:
	if (__unlikely(blocksize & (blocksize - 1)) || __unlikely(blocksize < 512))
		panic("SPADFS: WRONG BLOCKSIZE: %d", blocksize);
	fs->sectors_per_block_bits = ffs(blocksize) - 1 - 9;
	fs->blocksize = blocksize;

	sbp = SPADFS_READ_SECTOR(fs, SUPERBLOCK_SECTOR, &bh, "SPADFS_FILL_SUPER 1");
	if (__unlikely(IS_ERR(sbp))) {
		printk("SPADFS: UNABLE TO READ SUPER BLOCK\n");
		r = PTR_ERR(sbp);
		goto err0;
	}
	memcpy(sb, sbp, 512);
	__brelse(bh);
	if (__unlikely(memcmp(sb->signature, SUPERBLOCK_SIGNATURE, sizeof sb->signature))) {
		printk("SPADFS: SUPERBLOCK NOT FOUND\n");
		r = -EINVAL;
		goto err0;
	}
	if ((__u32)(sb->byte_sex & 0xFFFFFFFF) != 0x89ABCDEF || (__u32)(sb->byte_sex >> 32) != 0x01234567) {
		printk("SPADFS: BYTE SEX DOES NOT MATCH: %08X%08X\n", (unsigned)(sb->byte_sex >> 32), (unsigned)(sb->byte_sex & 0xFFFFFFFF));
		r = -EINVAL;
		goto err0;
	}
	if (__unlikely(sb->version_major != SPADFS_VERSION_MAJOR)) {
		printk("SPADFS: BAD MAJOR VERSION NUMBER (DISK %d.%d.%d, DRIVER %d.%d.%d)\n", sb->version_major, sb->version_middle, sb->version_minor, SPADFS_VERSION_MAJOR, SPADFS_VERSION_MIDDLE, SPADFS_VERSION_MINOR);
		r = -EINVAL;
		goto err0;
	}
	/* feature flags are used instead
	if (__unlikely(sb->version_middle > SPADFS_VERSION_MIDDLE)) {
		printk("SPADFS: BAD MIDDLE VERSION NUMBER (DISK %d.%d.%d, DRIVER %d.%d.%d)\n", sb->version_major, sb->version_middle, sb->version_minor, SPADFS_VERSION_MAJOR, SPADFS_VERSION_MIDDLE, SPADFS_VERSION_MINOR);
		r = -EINVAL;
		goto err0;
	}*/
	if (__unlikely(sb->flags_compat_ro & ~(0)) || __unlikely(sb->flags_compat_none & ~(FLAG_COMPAT_NONE_UNIX_NAMES))) {
		printk("SPADFS: INCOMPATIBLE FEATURE FLAGS IN SUPERBLOCK: %08X/%08X/%08X/%08X", (unsigned)sb->flags_compat_fsck, (unsigned)sb->flags_compat_rw, (unsigned)sb->flags_compat_ro, (unsigned)sb->flags_compat_none);
		r = -EINVAL;
		goto err0;
	}
	if (__unlikely(__byte_sum(sb, 512) != CHECKSUM_BASE)) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "BAD CHECKSUM ON SUPERBLOCK: %02X", __byte_sum(sb, 512) ^ CHECKSUM_BASE);
		r = -EFSERROR;
		goto err0;
	}
	if (__unlikely((__u64)(__d_off)(sb->size * 2) != (sb->size * 2))) {
		printk("SPADFS: 48-BIT FILESYSTEM NOT SUPPORTED BY VFS LAYER --- ENABLE IT IN KERNEL CONFIGURATION\n");
		r = -EINVAL;
		goto err0;
	}
	if (__unlikely((msg = validate_super(sb)) != NULL)) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "INVALID SUPERBLOCK: %s", msg);
		r = -EFSERROR;
		goto err0;
	}
	if (__unlikely(sb->sectors_per_block_bits != fs->sectors_per_block_bits)) {
		if (__unlikely(cycle)) {
			printk("SPADFS: SUPERBLOCK CHANGED WHILE SETTING DEVICE SIZE\n");
			r = -EIO;
			goto err0;
		}
		blocksize = 512 << sb->sectors_per_block_bits;
		if (__unlikely(!sb_set_blocksize(s, blocksize))) {
			printk("SPADFS: CAN'T SET BLOCKSIZE %d\n", blocksize);
			r = -EINVAL;
			goto err0;
		}
		cycle = 1;
		goto read_again;
	}
	
	fs->sectors_per_fnodepage_bits = sb->sectors_per_fnodepage_bits;
	fs->sectors_per_page_bits = sb->sectors_per_page_bits;
	fs->pagesize_bits = fs->sectors_per_page_bits + 9;
	fs->apage_size = 1 << (fs->pagesize_bits - 1);
	fs->dnode_hash_bits = fs->pagesize_bits - DNODE_PAGE_ENTRY_BITS - 1;
	fs->dnode_data_size = DNODE_PAGE_ENTRY_SIZE << fs->dnode_hash_bits;
	fs->dnode_page_sectors = DNODE_ENTRY_OFFSET + (fs->dnode_data_size << 1);
	fs->dnode_page_sectors = (fs->dnode_page_sectors + (fs->blocksize - 1)) & ~(fs->blocksize - 1);
	fs->dnode_page_sectors >>= 9;
	fs->sectors_per_cluster_bits = sb->sectors_per_cluster_bits;
	fs->cluster_threshold = (512 << fs->sectors_per_cluster_bits) * sb->cluster_threshold;
	fs->sectors_per_group_bits = sb->sectors_per_group_bits;
	fs->group_mask = ((__d_off)1 << fs->sectors_per_group_bits) - 1;
	fs->n_apage_mappings = fs->apage_size / fs->blocksize;
	if (__unlikely(!fs->n_apage_mappings)) fs->n_apage_mappings = 1;

	fs->txb_sec = sb->txblock;
	fs->apage_index0_sec = sb->apage_index[0];
	fs->apage_index1_sec = sb->apage_index[1];
	fs->cct_sec = sb->cct;
	fs->root_sec = sb->root;
	fs->size = sb->size;

	fs->flags_compat_fsck = sb->flags_compat_fsck;
	fs->flags_compat_rw = sb->flags_compat_rw;
	fs->flags_compat_ro = sb->flags_compat_ro;
	fs->flags_compat_none = sb->flags_compat_none;

	if (fs->mount_flags & MOUNT_FLAGS_CHECKSUMS_OVERRIDE) {
		if (fs->mount_flags & MOUNT_FLAGS_CHECKSUMS) fs->flags_compat_fsck &= ~FLAG_COMPAT_FSCK_NO_CHECKSUMS;
		else fs->flags_compat_fsck |= FLAG_COMPAT_FSCK_NO_CHECKSUMS;
	}

	if (__unlikely(IS_ERR(tb = SPADFS_READ_TX_BLOCK(fs, &bh, "SPADFS_FILL_SUPER 3")))) {
		r = PTR_ERR(tb);
		goto err0;
	}
	fs->cc = tb->cc;
	fs->a_cc = tb->a_cc;
	fs->a_txc = tb->a_txc;
	fs->txflags = tb->txflags;
	__brelse(bh);

	if (__unlikely(fs->txflags & TXFLAGS_DIRTY)) SPADFS_ERROR(fs, 0, "FILESYSTEM WAS NOT CLEANLY UNMOUNTED");
	if (__unlikely(fs->txflags & TXFLAGS_IO_READ_ERROR)) SPADFS_ERROR(fs, 0, "FILESYSTEM HAS ENCOUNTERED READ I/O ERROR");
	if (__unlikely(fs->txflags & TXFLAGS_IO_WRITE_ERROR)) SPADFS_ERROR(fs, 0, "FILESYSTEM HAS ENCOUNTERED WRITE I/O ERROR");
	if (__unlikely(fs->txflags & TXFLAGS_FS_ERROR)) SPADFS_ERROR(fs, 0, "FILESYSTEM HAS ERRORS IN STRUCTURES");
	if (__unlikely(fs->txflags & TXFLAGS_CHECKSUM_ERROR)) SPADFS_ERROR(fs, 0, "FILESYSTEM STRUCTURES HAVE BAD CHECKSUMS");
	if (__unlikely(fs->txflags & TXFLAGS_EA_ERROR)) SPADFS_ERROR(fs, 0, "FILESYSTEM STRUCTURES HAS ERRORS IN EXTENDED ATTRIBUTES");
	if (__unlikely(fs->txflags & ~(TXFLAGS_DIRTY | TXFLAGS_IO_READ_ERROR | TXFLAGS_IO_WRITE_ERROR | TXFLAGS_FS_ERROR | TXFLAGS_CHECKSUM_ERROR | TXFLAGS_EA_ERROR))) SPADFS_ERROR(fs, 0, "FILESYSTEM HAS UNKNOWN ERROR FLAGS %08X", fs->txflags);
	if (__unlikely(fs->txflags) && !(s->s_flags & MS_RDONLY)) SPADFS_ERROR(fs, 0, "RUNNING SPADFSCK IS RECOMMENDED");

	if (__unlikely(!(fs->cct = vmalloc(CCT_SIZE)))) {
		r = -ENOMEM;
		goto err1;
	}
	for (i = 0; i < CCT_SIZE / 512; i++) {
		void *p = SPADFS_READ_SECTOR(fs, fs->cct_sec + i, &bh, "SPADFS_FILL_SUPER 2");
		if (__unlikely(IS_ERR(p))) {
			r = PTR_ERR(p);
			goto err1;
		}
		memcpy((__u8 *)fs->cct + (i << 9), p, 512);
		__brelse(bh);
	}
	fs->txc = fs->cct[fs->cc];

	fs->n_apages = N_APAGES(fs->size, fs->pagesize_bits, fs->sectors_per_block_bits + 9);
	n = APAGE_INDEX_SECTORS(fs->n_apages, fs->blocksize);
	if (__unlikely(!(fs->apage_index = vmalloc((n << 9) + sizeof(struct apage_index_entry))))) {
		r = -ENOMEM;
		goto err1;
	}
	memset(fs->apage_index, 0, sizeof(struct apage_index_entry));
	fs->apage_index++;

	sec = CC_VALID(fs, fs->a_cc, fs->a_txc) ? fs->apage_index0_sec : fs->apage_index1_sec;
	for (i = 0; i < n; i++, sec++) {
		void *p = SPADFS_READ_SECTOR(fs, sec, &bh, "SPADFS_FILL_SUPER 4");
		if (__unlikely(IS_ERR(p))) {
			r = PTR_ERR(p);
			goto err1;
		}
		memcpy((__u8 *)fs->apage_index + (i << 9), p, 512);
		__brelse(bh);
	}
	if (__unlikely(!(fs->apage_info = vmalloc(fs->n_apages * sizeof(struct apage_info))))) {
		r = -ENOMEM;
		goto err1;
	}
	memset(fs->apage_info, 0, fs->n_apages * sizeof(struct apage_info));
	for (i = 0; i < fs->n_apages; i++) for (j = 0; j < 2; j++) {
		if (__unlikely(!(fs->apage_info[i].map[j] = kmalloc(fs->n_apage_mappings * sizeof(APAGE_MAP), GFP_KERNEL)))) {
			r = -ENOMEM;
			goto err1;
		}
	}

	fs->n_groups = (fs->size + (1 << fs->sectors_per_group_bits) - 1) >> fs->sectors_per_group_bits;
	fs->zones[0].grp_start = 0;
	fs->zones[0].grp_n = sb->small_file_group;
	fs->zones[1].grp_start = sb->small_file_group;
	fs->zones[1].grp_n = sb->large_file_group - sb->small_file_group;
	fs->zones[2].grp_start = sb->large_file_group;
	fs->zones[2].grp_n = fs->n_groups - sb->large_file_group;
	if (__unlikely(!(fs->group_info = vmalloc(fs->n_groups * sizeof(struct group_info))))) {
		r = -ENOMEM;
		goto err1;
	}
	memset(fs->group_info, 0, fs->n_groups * sizeof(struct group_info));
	for (i = 0; i < fs->n_groups; i++) {
		if (i < fs->zones[1].grp_start) fs->group_info[i].zone = &fs->zones[0];
		else if (i < fs->zones[2].grp_start) fs->group_info[i].zone = &fs->zones[1];
		else fs->group_info[i].zone = &fs->zones[2];
	}
	if (__unlikely(!(fs->tmp_map = kmalloc(fs->n_apage_mappings * sizeof(APAGE_MAP), GFP_KERNEL)))) {
		r = -ENOMEM;
		goto err1;
	}
	memset(fs->tmp_map, 0, fs->n_apage_mappings * sizeof(APAGE_MAP));
	for (i = 0; i < fs->n_apage_mappings; i++) {
		if (__unlikely(!(fs->tmp_map[i].entry = kmalloc(fs->blocksize, GFP_KERNEL)))) {
			r = -ENOMEM;
			goto err1;
		}
	}

	for (i = 0; i < fs->n_apages; i++) if (!fs->apage_index[i].end_block) break;
	fs->n_active_apages = i;

	if (__unlikely(r = SPADFS_COUNT_FREE_SPACE(fs))) goto err1;

	fs->spadfs_syncer = create_singlethread_workqueue("spadfs_syncer");
	if (__unlikely(!fs->spadfs_syncer)) goto err1;
	INIT_WORK(&fs->spadfs_sync_work, SPADFS_SYNC_WORK, fs);
	queue_delayed_work(fs->spadfs_syncer, &fs->spadfs_sync_work, fs->spadfs_sync_time);

	if (__unlikely(r = SPADFS_INCREASE_CC(fs, 1))) goto err2;
	if (__unlikely(r = SPADFS_START_NEW_TX(fs))) goto err2;

	root = SPADFS_IGET(s, make_fixed_spadfs_ino_t(fs->root_sec), 0);
	if (__unlikely(IS_ERR(root))) {
		r = PTR_ERR(root);
		goto err2;
	}
	unlock_new_inode(root);
	s->s_root = d_alloc_root(root);
	if (__unlikely(!s->s_root)) {
		iput(root);
		printk("SPADFS: UNABLE TO ALLOCATE ROOT DENTRY\n");
		r = -ENOMEM;
		goto err2;
	}
	SPADFS_SET_DENTRY_OPERATIONS(fs, s->s_root);

	return 0;

	err2:
	SPADFS_INCREASE_CC(fs, -1);
	err1:
	SPADFS_UPDATE_ERROR_FLAGS(fs);
	err0:
	SPADFS_FREE_SUPER(s);
	return r;
}

static void SPADFS_FREE_SUPER(struct super_block *s)
{
	SPADFS *fs = spadfs(s);
	unsigned i, j;
	if (fs->spadfs_syncer) {
		cancel_rearming_delayed_workqueue(fs->spadfs_syncer, &fs->spadfs_sync_work);
		destroy_workqueue(fs->spadfs_syncer);
	}
	if (__unlikely(!hlist_empty(&fs->inode_list)))
		panic("SPADFS_FREE_SUPER: INODES LEAKED");
	if (fs->tmp_map) {
		for (i = 0; i < fs->n_apage_mappings; i++) kfree(fs->tmp_map[i].entry);
		kfree(fs->tmp_map);
	}
	if (fs->apage_info) {
		for (i = 0; i < fs->n_apages; i++) for (j = 0; j < 2; j++) kfree(fs->apage_info[i].map[j]);
		vfree(fs->apage_info);
	}
	vfree(fs->group_info);
	if (fs->apage_index) {
		vfree(fs->apage_index - 1);
	}
	vfree(fs->cct);
	kfree(fs);
	s->s_fs_info = NULL;
}

static void SPADFS_PUT_SUPER(struct super_block *s)
{
	SPADFS_COMMIT(spadfs(s));
	SPADFS_INCREASE_CC(spadfs(s), -1);
	/*{
		int i;
		printk("groups: ");
		for (i = 0; i < spadfs(s)->n_groups; i++) printk(" %Ld", (long long)spadfs(s)->group_info[i].freespace);
		printk("\nzones: %Ld %Ld %Ld\n", (long long)spadfs(s)->zones[0].freespace, (long long)spadfs(s)->zones[1].freespace, (long long)spadfs(s)->zones[2].freespace);
	}*/
	SPADFS_FREE_SUPER(s);
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
static int SPADFS_GET_SB(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
	return get_sb_bdev(fs_type, flags, dev_name, data, SPADFS_FILL_SUPER, mnt);
}
#else
static struct super_block *SPADFS_GET_SB(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{
	return get_sb_bdev(fs_type, flags, dev_name, data, SPADFS_FILL_SUPER);
}
#endif

static struct super_operations spadfs_sops = {
	.alloc_inode = SPADFS_ALLOC_INODE,
	.destroy_inode = SPADFS_DESTROY_INODE,
	.delete_inode = SPADFS_DELETE_INODE,
	.put_super = SPADFS_PUT_SUPER,
	.sync_fs = SPADFS_SYNC_FS,
	.statfs = SPADFS_STATFS,
};

static struct file_system_type spadfs_fs_type = {
	.owner = THIS_MODULE,
	.name = "spadfs",
	.get_sb = SPADFS_GET_SB,
	.kill_sb = kill_block_super,
	.fs_flags = FS_REQUIRES_DEV,
};

static int __init SPADFS_INIT(void)
{
	int err;
	if (__unlikely(!(spadfs_inode_cachep = kmem_cache_create("spadfs_inode_cache", sizeof(SPADFNODE), 0, SLAB_RECLAIM_ACCOUNT, fnode_ctor, NULL)))) return -ENOMEM;
	if (__unlikely(err = register_filesystem(&spadfs_fs_type))) {
		kmem_cache_destroy(spadfs_inode_cachep);
		return err;
	}
	return 0;
}

static void __exit SPADFS_EXIT(void)
{
	unregister_filesystem(&spadfs_fs_type);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
	kmem_cache_destroy(spadfs_inode_cachep);
#else
	if (__unlikely(kmem_cache_destroy(spadfs_inode_cachep)))
		printk(KERN_INFO "SPADFS_INODE_CACHE: NOT ALL STRUCTURES WERE FREED\n");
#endif
}

module_init(SPADFS_INIT)
module_exit(SPADFS_EXIT)
MODULE_LICENSE("GPL");

#if __GNUC__ == 2 || 1
/*
 * GCC 2.95.* has a bug --- it generates ".globl __udivdi3" into assembler
 * although it doesn't ever call this function. This causes module linking
 * problems.
 *
 * GCC 3.3.6 does it too. I have no idea why.
 */
void __udivdi3(void)
{
	printk("__udivdi3 called!\n");
	BUG();
}
#endif
