#include "spadfs.h"

#define BMAP_EXTEND	0
#define BMAP_MAP	1

__d_off SPADFS_SIZE_2_BLKS(SPADFS *fs, loff_t size)
{
	__d_off result;
	FILE_BLOCKS(fs->blocksize, 512 << fs->sectors_per_cluster_bits, fs->cluster_threshold, size, result);
	return result;
}

static void REALLY_DO_ANODE_CHECKSUM(struct anode *anode)
{
	anode->flags |= ANODE_CHECKSUM_VALID;
	anode->checksum ^= CHECKSUM_BASE ^ __byte_sum(anode, ANODE_SIZE);
}

static __finline__ void DO_ANODE_CHECKSUM(SPADFS *fs, struct anode *anode)
{
	if (__unlikely(make_checksums(fs))) REALLY_DO_ANODE_CHECKSUM(anode);
	else anode->flags &= ~ANODE_CHECKSUM_VALID;
}

static struct anode *ALLOC_ANODE(SPADFNODE *f, __d_off hint, struct buffer_head **bhp, __d_off *result)
{
	SPADFS *fs = f->fs;
	int r;
	struct alloc al;
	struct anode *anode;
	al.sector = hint;
	al.n_sectors = 1 << fs->sectors_per_block_bits;
	al.flags = ALLOC_METADATA;
	r = SPADFS_ALLOC_BLOCKS(f, &al);
	if (__unlikely(r)) return ERR_PTR(r);
	anode = SPADFS_GET_NEW_SECTOR(fs, *result = al.sector, bhp, "ALLOC_ANODE");
	if (__unlikely(IS_ERR(anode))) return anode;
	memset(anode, 0, fs->blocksize);
	anode->magic = ANODE_MAGIC;
	return anode;
}

static int SYNC_BMAP(SPADFNODE *f, __d_off lbn, __d_off *blk, __d_off *nblks, __d_off *nback, int flags, __d_off *aptr, char *msg)
{
	int depth_now, depth_total;
	__d_off ano;
	__d_off nb;
	struct anode *anode;
	struct buffer_head *bh;
	unsigned vx;
	int direct, off;
	*aptr = 0;
	if (__likely(lbn < f->blk1_n)) {
		*blk = f->blk1 + lbn;
		*nblks = f->blk1_n - lbn;
		*nback = lbn;
		if (__unlikely(flags == BMAP_EXTEND)) {
			f->blk1_n = lbn + 1;
			f->blk2_n = 0;
			f->root = 0;
		}
		return 0;
	}
	if (__likely(lbn < f->blk1_n + f->blk2_n)) {
		*blk = f->blk2 + lbn - f->blk1_n;
		*nblks = f->blk1_n + f->blk2_n - lbn;
		*nback = lbn - f->blk1_n;
		if (__unlikely(flags == BMAP_EXTEND)) {
			f->blk2_n = lbn - f->blk1_n + 1;
			f->root = 0;
		}
		return 0;
	}
	ano = f->root;
	depth_now = 0;
	depth_total = 0;
	subnode:
	anode = SPADFS_READ_ANODE(f->fs, ano, &bh, &vx, "SYNC_BMAP");
	if (__unlikely(IS_ERR(anode))) return PTR_ERR(anode);
	direct = find_direct(depth_now, depth_total);
	off = find_in_anode(anode, lbn, vx);
	if (__unlikely(lbn >= anode->x[off].end_off) || __unlikely(lbn < anode->x[off - 1].end_off)) {
		SPADFS_ERROR(f->fs, TXFLAGS_FS_ERROR, "BMAP(%s): OUT OF RANGE !(%LX <= %LX < %LX), ANODE(%LX -> %LX)", msg, anode->x[off - 1].end_off, (unsigned long long)lbn, (unsigned long long)anode->x[off].end_off, (unsigned long long)f->root, (unsigned long long)ano);
		brelse_err:
		__brelse(bh);
		return -EFSERROR;
	}
	if (__unlikely(off >= direct)) {
		ano = anode->x[off].blk;
		if (__unlikely(flags == BMAP_EXTEND)) {
			if (__unlikely(anode->x[off].end_off != -1) || __unlikely(anode->valid_extents != off + 1)) {
				start_atomic_buffer_modify(bh);
				anode->x[off].end_off = -1;
				anode->valid_extents = off + 1;
				DO_ANODE_CHECKSUM(f->fs, anode);
				end_atomic_buffer_modify(bh);
			}
		}
		update_depth(&depth_now, &depth_total, off);
		__brelse(bh);
		goto subnode;
	}
	if (__unlikely(!off) || (__unlikely(off == 2) && __unlikely(!depth_now))) *aptr = ano;
	if (__unlikely(anode->x[off].end_off <= anode->x[off - 1].end_off)) {
		SPADFS_ERROR(f->fs, TXFLAGS_FS_ERROR, "BMAP(%s): NON-MONOTONIC ANODE (%LX -> %LX), ENTRY %d", msg, (unsigned long long)f->root, (unsigned long long)ano, off);
		goto brelse_err;
	}
	nb = lbn - anode->x[off - 1].end_off;
	*nback = nb;
	*blk = anode->x[off].blk + nb;
	*nblks = anode->x[off].end_off - lbn;
	if (flags == BMAP_EXTEND) {
		if (__unlikely(anode->x[off].end_off != lbn + 1) || __unlikely(anode->valid_extents != off + 1)) {
			start_atomic_buffer_modify(bh);
			anode->x[off].end_off = lbn + 1;
			anode->valid_extents = off + 1;
			DO_ANODE_CHECKSUM(f->fs, anode);
			end_atomic_buffer_modify(bh);
		}
	}
	__brelse(bh);
	return 0;
}

static int SPADFS_ADD_EXTENT(SPADFNODE *f, __d_off blk, __d_off n_blks)
{
	SPADFS *fs = f->fs;
	struct anode *anode;
	__d_off ano;
	int depth_now, depth_total;
	int direct;
	__d_off ano_l;
	__d_off end_off;
	struct buffer_head *bh;
	unsigned vx;
	if (!f->blk1_n) {
		unsigned mdb;
		f->blk1 = blk;
		mdb = MAX_DIRECT_BLKS(1 << fs->sectors_per_block_bits);
		if (__unlikely(n_blks > mdb)) {
			f->blk1_n = mdb;
			blk += mdb;
			n_blks -= mdb;
			goto again1;
		}
		f->blk1_n = n_blks;
		return 0;
	}
	if (!f->blk2_n) {
		unsigned mdb;
		again1:
		f->blk2 = blk;
		mdb = MAX_DIRECT_BLKS(1 << fs->sectors_per_block_bits);
		if (__unlikely(n_blks > mdb)) {
			f->blk2_n = mdb;
			blk += mdb;
			n_blks -= mdb;
			goto again2;
		}
		f->blk2_n = n_blks;
		return 0;
	}
	if (!f->root) {
		again2:
		anode = ALLOC_ANODE(f, SPADFS_ALLOC_HINT(f, HINT_META), &bh, &f->root);
		if (__unlikely(IS_ERR(anode))) return PTR_ERR(anode);
		anode->flags |= ANODE_ROOT;
		anode->valid_extents = 3;
		anode->start_off = 0;
		anode->x[0].blk = f->blk1;
		anode->x[0].end_off = f->blk1_n;
		anode->x[1].blk = f->blk2;
		anode->x[1].end_off = f->blk1_n + f->blk2_n;
		anode->x[2].blk = blk;
		anode->x[2].end_off = anode->x[1].end_off + n_blks;
		DO_ANODE_CHECKSUM(fs, anode);
		mark_buffer_dirty(bh);
		__brelse(bh);
		return 0;
	}
	ano = f->root;
	ano_l = -1;
	depth_now = depth_total = 0;
	subnode:
	anode = SPADFS_READ_ANODE(fs, ano, &bh, &vx, "SPADFS_ADD_EXTENT 1");
	if (__unlikely(IS_ERR(anode))) return PTR_ERR(anode);
	direct = find_direct(depth_now, depth_total);
	if (__likely(vx < direct)) {
		start_atomic_buffer_modify(bh);
		anode->x[vx].blk = blk;
		anode->x[vx].end_off = anode->x[anode->valid_extents - 1].end_off + n_blks;
		anode->valid_extents = vx + 1;
		DO_ANODE_CHECKSUM(fs, anode);
		end_atomic_buffer_modify(bh);
		__brelse(bh);
		return 0;
	}
	if (vx < ANODE_N_EXTENTS) ano_l = ano;
	if (vx > direct) {
		ano = anode->x[vx - 1].blk;
		update_depth(&depth_now, &depth_total, vx - 1);
		__brelse(bh);
		goto subnode;
	}
	if (__unlikely(ano_l == -1)) {
		__brelse(bh);
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "ANODE %LX IS FULL", (unsigned long long)f->root);
		return -EFSERROR;
	}
	end_off = anode->x[vx - 1].end_off;
	__brelse(bh);
	anode = ALLOC_ANODE(f, SPADFS_ALLOC_HINT(f, HINT_META), &bh, &ano);
	if (__unlikely(IS_ERR(anode))) return PTR_ERR(anode);
	anode->valid_extents = 1;
	anode->start_off = end_off;
	anode->x[0].blk = blk;
	anode->x[0].end_off = end_off + n_blks;
	DO_ANODE_CHECKSUM(fs, anode);
	mark_buffer_dirty(bh);
	__brelse(bh);
	anode = SPADFS_READ_ANODE(fs, ano_l, &bh, &vx, "SPADFS_ADD_EXTENT 2");
	if (__unlikely(IS_ERR(anode))) return PTR_ERR(anode);
	if (__unlikely(vx == ANODE_N_EXTENTS)) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "ANODE %LX FILLED UNDER US", (unsigned long long)ano_l);
		__brelse(bh);
		return -EFSERROR;
	}
	start_atomic_buffer_modify(bh);
	anode->x[vx].blk = ano;
	anode->x[vx].end_off = -1;
	anode->x[vx - 1].end_off = end_off;
	anode->valid_extents = vx + 1;
	DO_ANODE_CHECKSUM(fs, anode);
	end_atomic_buffer_modify(bh);
	__brelse(bh);
	return 0;
}

static int SPADFS_EXTEND_LAST_EXTENT(SPADFNODE *f, __d_off n_blks)
{
	SPADFS *fs = f->fs;
	struct anode *anode;
	__d_off ano;
	int depth_now, depth_total;
	int direct;
	struct buffer_head *bh;
	unsigned vx;
	if (!f->blk2_n) {
		unsigned mdb = MAX_DIRECT_BLKS(1 << fs->sectors_per_block_bits);
		if (f->blk1_n + n_blks > mdb) {
			n_blks -= mdb - f->blk1_n;
			f->blk1_n = mdb;
			return SPADFS_ADD_EXTENT(f, f->blk1 + mdb, n_blks);
		}
		f->blk1_n += n_blks;
		return 0;
	}
	if (!f->root) {
		unsigned mdb = MAX_DIRECT_BLKS(1 << fs->sectors_per_block_bits);
		if (f->blk2_n + n_blks > mdb) {
			n_blks -= mdb - f->blk2_n;
			f->blk2_n = mdb;
			return SPADFS_ADD_EXTENT(f, f->blk2 + mdb, n_blks);
		}
		f->blk2_n += n_blks;
		return 0;
	}
	ano = f->root;
	depth_now = depth_total = 0;
	subnode:
	anode = SPADFS_READ_ANODE(fs, ano, &bh, &vx, "SPADFS_EXTEND_LAST_EXTENT");
	if (__unlikely(IS_ERR(anode))) return PTR_ERR(anode);
	direct = find_direct(depth_now, depth_total);
	if (vx <= direct) {
		start_atomic_buffer_modify(bh);
		anode->x[vx - 1].end_off += n_blks;
		DO_ANODE_CHECKSUM(fs, anode);
		end_atomic_buffer_modify(bh);
		__brelse(bh);
		return 0;
	}
	ano = anode->x[vx - 1].blk;
	update_depth(&depth_now, &depth_total, vx - 1);
	__brelse(bh);
	goto subnode;
}

static int SPADFS_DO_TRUNCATE_BLOCKS(SPADFNODE *f, __d_off blks, __d_off oldblks)
{
	SPADFS *fs = f->fs;
	__d_off d_off_sink;	/* write-only */
	int r;
	__d_off blk, bback, ano;
	if (!oldblks) return 0;
	f->extent_cache_size = 0;
	again:
	r = SYNC_BMAP(f, blks - 1, &blk, &d_off_sink, &bback, BMAP_MAP, &ano, "SPADFS_DO_TRUNCATE");
	if (__unlikely(r)) return r;
	blk++;
	bback++;
	if (oldblks >= bback && __unlikely(ano != 0)) {
		r = SPADFS_FREE_BLOCKS(f, ano, 1 << fs->sectors_per_block_bits);
		if (__unlikely(r)) return r;
	}
	if (oldblks < bback) bback = oldblks;
	r = SPADFS_FREE_BLOCKS(f, blk - bback, bback);
	if (__unlikely(r)) return r;
	blks -= bback;
	if ((oldblks -= bback)) goto again;
	return 0;
}

static int SPADFS_DO_TRUNCATE(SPADFNODE *f, loff_t newsize)
{
	SPADFS *fs = f->fs;
	__d_off blks, oldblks, nsb;
	blks = SPADFS_SIZE_2_BLKS(fs, f->disk_size);
	nsb = SPADFS_SIZE_2_BLKS(fs, newsize);
	inode(f)->i_blocks = nsb;
	oldblks = blks - nsb;
	f->disk_size = newsize;
	return SPADFS_DO_TRUNCATE_BLOCKS(f, blks, oldblks);
}

static int SPADFS_DO_EXTEND(SPADFNODE *f, loff_t size)
{
	SPADFS *fs = f->fs;
	struct alloc al;
	__d_off d_off_sink;	/* write-only */
	__d_off blks, newblks;
	__d_off blk;
	int flags;
	int r;
	blks = SPADFS_SIZE_2_BLKS(fs, f->disk_size);
	newblks = SPADFS_SIZE_2_BLKS(fs, size) - blks;
	/*printk("do extend: (%Lx) %Lx -> (%Lx) %Lx\n", f->disk_size, blks, size, blks + newblks);*/
	flags = size > fs->cluster_threshold ? ALLOC_BIG_FILE : ALLOC_SMALL_FILE;
	if (!newblks) goto set_size_return;
	f->extent_cache_size = 0;
	if (blks && (flags == ALLOC_SMALL_FILE || f->disk_size > fs->cluster_threshold)) {
		r = SYNC_BMAP(f, blks - 1, &blk, &d_off_sink, &d_off_sink, BMAP_EXTEND, &d_off_sink, "SPADFS_DO_EXTEND");
		if (__unlikely(r)) return r;
		blk++;
		new_run:
		al.n_sectors = newblks;
		al.flags = flags | ALLOC_PARTIAL_AT_GOAL;
		alloc_c:
		al.sector = blk;
		r = SPADFS_ALLOC_BLOCKS(f, &al);
		if (__unlikely(r)) {
			truncate_return_r:
			/*printk("truncate: %LX -> %LX\n", blks, blks - SPADFS_SIZE_2_BLKS(fs, f->disk_size));*/
			SPADFS_DO_TRUNCATE_BLOCKS(f, blks, blks - SPADFS_SIZE_2_BLKS(fs, f->disk_size));
			/*printk("done.\n");*/
			return r;
		}
		if (__likely(al.sector == blk)) r = SPADFS_EXTEND_LAST_EXTENT(f, al.n_sectors);
		else add_extent: r = SPADFS_ADD_EXTENT(f, al.sector, al.n_sectors);
		if (__unlikely(r)) {
			int rr = SPADFS_FREE_BLOCKS(f, al.sector, al.n_sectors);
			if (__unlikely(rr)) r = rr;
			goto truncate_return_r;
		}
		newblks -= al.n_sectors;
		blks += al.n_sectors;
	} else {
		if (blks) {
			r = SYNC_BMAP(f, blks - 1, &blk, &d_off_sink, &d_off_sink, BMAP_EXTEND, &d_off_sink, "SPADFS_DO_EXTEND 2");
			if (__unlikely(r)) return r;
			blk++;
			new_extent:
			if ((unsigned)newblks & ((1 << fs->sectors_per_cluster_bits) - 1)) {
				flags = 0;
				al.flags = ALLOC_SMALL_FILE | ALLOC_PARTIAL_AT_GOAL;
				al.n_sectors = (1 << fs->sectors_per_cluster_bits) - ((unsigned)newblks & ((1 << fs->sectors_per_cluster_bits) - 1));
				if (__unlikely(al.n_sectors > newblks)) al.n_sectors = newblks;
				goto alloc_c;
			}
		} else {
			f->blk1_n = 0;
			f->blk2_n = 0;
			f->root = 0;
		}
		if (flags & ALLOC_BIG_FILE) {
			al.sector = SPADFS_ALLOC_HINT(f, HINT_BIG);
		} else {
			al.sector = SPADFS_ALLOC_HINT(f, HINT_SMALL);
		}
		al.n_sectors = newblks;
		al.flags = flags;
		r = SPADFS_ALLOC_BLOCKS(f, &al);
		if (__unlikely(r)) goto truncate_return_r;
		if (__unlikely(al.flags & ALLOC_NEW_GROUP_HINT)) SPADFS_SET_NEW_HINT(f, &al);
		goto add_extent;
	}
	if (__unlikely(newblks != 0)) {
		blk = al.sector + al.n_sectors;
		if (__unlikely(!flags)) {
			flags = ALLOC_BIG_FILE;
			goto new_extent;
		} else {
			goto new_run;
		}
	}
	set_size_return:
	inode(f)->i_blocks = blks;
	f->disk_size = size;
	return 0;
}

static int SPADFS_GET_BLOCK(struct inode *i, sector_t lblock, struct buffer_head *bh_result, int create)
{
	__d_off n_fwd, n_back;
	__d_off d_off_sink;	/* write-only */
	int r;
	__d_off result;
	__d_off prealloc;
	int synced = 0;
	SPADFS *fs = spadfnode(i)->fs;
	__d_off blk = (__d_off)lblock << fs->sectors_per_block_bits;
	retry_getblk:
	if (__unlikely(blk >= (spadfnode(i)->mmu_private + 511) >> 9)) {
		if (!create) return 0;
		set_buffer_new(bh_result);
		if (__unlikely((loff_t)blk << 9 != spadfnode(i)->mmu_private)) {
			BUG();
			return -EIO;
		}
		down_read(&fs->sync_lock);
		down(&spadfnode(i)->lock);
		if (__likely(spadfnode(i)->prealloc != 0)) {
			spadfnode(i)->prealloc -= fs->blocksize;
			do_bmap_locked:
			spadfnode(i)->mmu_private += fs->blocksize;
			if (__likely(blk >= spadfnode(i)->extent_cache_lb) && __likely(blk < spadfnode(i)->extent_cache_lb + spadfnode(i)->extent_cache_size)) {
				result = spadfnode(i)->extent_cache_pb + (blk - spadfnode(i)->extent_cache_lb);
				up(&spadfnode(i)->lock);
				up_read(&fs->sync_lock);
				goto ret_result;
			}
			r = SYNC_BMAP(spadfnode(i), blk, &result, &n_fwd, &n_back, BMAP_MAP, &d_off_sink, "SPADFS_GET_BLOCK_2");
			up(&spadfnode(i)->lock);
			up_read(&fs->sync_lock);
			goto done_bmap;
		}
		goto new_blk;
	}
	if (__likely(blk >= spadfnode(i)->extent_cache_lb) && __likely(blk < spadfnode(i)->extent_cache_lb + spadfnode(i)->extent_cache_size)) {
		result = spadfnode(i)->extent_cache_pb + (blk - spadfnode(i)->extent_cache_lb);
		goto ret_result;
	}
	r = SYNC_BMAP(spadfnode(i), blk, &result, &n_fwd, &n_back, BMAP_MAP, &d_off_sink, "SPADFS_GET_BLOCK");
	done_bmap:
	if (__likely(!r)) {

		spadfnode(i)->extent_cache_pb = result - n_back;
		spadfnode(i)->extent_cache_lb = blk - n_back;
		spadfnode(i)->extent_cache_size = n_back + n_fwd;
		ret_result:
		map_bh(bh_result, i->i_sb, result >> fs->sectors_per_block_bits);
		return 0;
	}
	return r;

	new_blk:

	if (__likely(i->i_nlink)) {
		loff_t dsize;
		struct buffer_head *bh;
		struct fnode_block *fnode_block;
		struct fnode *fnode;
		fnode_block = SPADFS_READ_FNODE_BLOCK(fs, spadfnode(i)->fnode_block, &bh, SRFB_FNODE | SRFB_FIXED_FNODE, "SPADFS_GET_BLOCK");
		if (__unlikely(IS_ERR(fnode_block))) {
			r = PTR_ERR(fnode_block);
			goto unlock_return_r;
		}
		fnode = (struct fnode *)((char *)fnode_block + spadfnode(i)->fnode_pos);
		if (__likely(CC_CURRENT(fs, fnode->cc, fnode->txc))) {
			if (CC_VALID(fs, fnode->cc, fnode->txc)) {
				dsize = fnode->size1;
			} else {
				dsize = fnode->size0;
			}
			if (__unlikely(SPADFS_SIZE_2_BLKS(fs, dsize) > SPADFS_SIZE_2_BLKS(fs, spadfnode(i)->disk_size))) {
				brelse(bh);
				up(&spadfnode(i)->lock);
				up_read(&fs->sync_lock);
				if (__unlikely(r = SPADFS_COMMIT(fs))) return r;
				goto retry_getblk;
			}
		}
		brelse(bh);
	}

	if (__likely((unsigned)spadfnode(i)->disk_size == spadfnode(i)->disk_size))
		prealloc = (unsigned)spadfnode(i)->disk_size / fs->prealloc_part;
	else prealloc = (unsigned)-1 / fs->prealloc_part;
	if (prealloc < fs->min_prealloc) prealloc = fs->min_prealloc;
	if (prealloc > fs->max_prealloc) prealloc = fs->max_prealloc;
	prealloc &= ~(fs->blocksize - 1);
	again_without_prealloc:
	r = SPADFS_DO_EXTEND(spadfnode(i), spadfnode(i)->disk_size + fs->blocksize + prealloc);
	if (__unlikely(r)) {
		if (__likely(r == -ENOSPC)) {
			if (prealloc) {
				prealloc = 0;
				goto again_without_prealloc;
			}
			if (!synced) {
				synced = 1;
				up(&spadfnode(i)->lock);
				up_read(&fs->sync_lock);
				if (__unlikely(r = SPADFS_COMMIT(fs))) return r;
				goto retry_getblk;
			}
		}
		unlock_return_r:
		up(&spadfnode(i)->lock);
		up_read(&fs->sync_lock);
		return r;
	}
	spadfnode(i)->prealloc = prealloc;
	SPADFS_WRITE_FILE(spadfnode(i));
	goto do_bmap_locked;
}

static void SPADFS_DISCARD_PREALLOC(SPADFNODE *f)
{
	/*
	 * Here we are not under any VFS lock, so i_size can change under us.
	 * If this is the last instance of file, we must write correct i_size (and we know
	 * that it won't change and it is consistent with previous calls to get_block).
	 * If this is not the last instance, it doesn't matter what we write as size, just
	 * make sure that size is consistent with real allocated sectors.
	 */
	loff_t inode_size = i_size_read(inode(f));
	__d_off inode_blocks = SPADFS_SIZE_2_BLKS(f->fs, inode_size);
	loff_t prealloc_size = f->disk_size - f->prealloc;
	__d_off prealloc_blocks = SPADFS_SIZE_2_BLKS(f->fs, prealloc_size);
	SPADFS_DO_TRUNCATE(f, prealloc_blocks == inode_blocks ? inode_size : prealloc_size);
	f->prealloc = 0;
	SPADFS_WRITE_FILE(f);
}

static void SPADFS_TRUNCATE(struct inode *i)
{
	SPADFS *fs;
	if (__unlikely(i->i_size > spadfnode(i)->disk_size)) {
		i->i_size = spadfnode(i)->disk_size;
		return;
	}
	if (__unlikely(IS_APPEND(i)) || __unlikely(IS_IMMUTABLE(i))) return;
	fs = spadfnode(i)->fs;
	down_read(&fs->sync_lock);
	down(&spadfnode(i)->lock);
	SPADFS_DO_TRUNCATE(spadfnode(i), i->i_size);
	spadfnode(i)->mmu_private = spadfnode(i)->disk_size;
	spadfnode(i)->prealloc = 0;
	SPADFS_WRITE_FILE(spadfnode(i));
	up(&spadfnode(i)->lock);
	up_read(&fs->sync_lock);
}

static int SPADFS_RELEASE(struct inode *i, struct file *file)
{
	if (__unlikely(file->f_mode & FMODE_WRITE)) {
		SPADFS *fs = spadfnode(i)->fs;
		down_read(&fs->sync_lock);
		down(&spadfnode(i)->lock);
		SPADFS_DISCARD_PREALLOC(spadfnode(i));
		up(&spadfnode(i)->lock);
		up_read(&fs->sync_lock);
	}
	return 0;
}

void SPADFS_DELETE_FILE_CONTENT(SPADFNODE *f)
{
	SPADFS_DO_TRUNCATE(f, 0);
	f->prealloc = 0;
}

static int SPADFS_READPAGE(struct file *file, struct page *page)
{
	return block_read_full_page(page, SPADFS_GET_BLOCK);
}

static int SPADFS_WRITEPAGE(struct page *page, struct writeback_control *wbc)
{
	int r = block_write_full_page(page, SPADFS_GET_BLOCK, wbc);
	return r;
}

static int SPADFS_PREPARE_WRITE(struct file *file, struct page *page, unsigned from, unsigned to)
{
	SPADFNODE *f = spadfnode(page->mapping->host);
	int r = cont_prepare_write(page, from, to, SPADFS_GET_BLOCK, &f->mmu_private);
	/*
	down_read(&f->fs->sync_lock);
	down(&f->lock);
	SPADFS_WRITE_FILE(f);
	up(&f->lock);
	up_read(&f->fs->sync_lock);
	*/
	return r;
}

static sector_t SPADFS_BMAP(struct address_space *mapping, sector_t block)
{
	return generic_block_bmap(mapping, block, SPADFS_GET_BLOCK);
}

static ssize_t SPADFS_FILE_WRITE(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
	ssize_t r = do_sync_write(file, buf, count, ppos);
#else
	ssize_t r = generic_file_write(file, buf, count, ppos);
#endif
	return r;
}

static int SPADFS_FILE_FSYNC(struct file *file, struct dentry *dentry, int datasync)
{
	if (!datasync) {
		SPADFNODE *f = spadfnode(file->f_mapping->host);
		down_read(&f->fs->sync_lock);
		down(&f->lock);
		SPADFS_WRITE_FILE(f);
		up(&f->lock);
		up_read(&f->fs->sync_lock);
	}
	return SPADFS_FSYNC(file, dentry, datasync);
}

int SPADFS_FILE_SETATTR(struct dentry *dentry, struct iattr *iattr)
{
	struct inode *inode = dentry->d_inode;
	int r = SPADFS_SETATTR_COMMON(inode, iattr);
	if (__unlikely(r)) return r;
	down_read(&spadfnode(inode)->fs->sync_lock);
	down(&spadfnode(inode)->lock);
	SPADFS_UPDATE_EA(inode);
	r = SPADFS_WRITE_FILE(spadfnode(inode));
	up(&spadfnode(inode)->lock);
	up_read(&spadfnode(inode)->fs->sync_lock);
	return r;
}

struct inode_operations spadfs_file_iops = {
	.truncate = SPADFS_TRUNCATE,
	.setattr = SPADFS_FILE_SETATTR,
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
struct file_operations spadfs_file_fops = {
	.llseek = generic_file_llseek,
	.read = do_sync_read,
	.aio_read = generic_file_aio_read,
	.write = SPADFS_FILE_WRITE,
	.aio_write = generic_file_aio_write,
	.mmap = generic_file_mmap,
	.fsync = SPADFS_FILE_FSYNC,
	.sendfile = generic_file_sendfile,
	.release = SPADFS_RELEASE,
};
#else
struct file_operations spadfs_file_fops = {
	.llseek = generic_file_llseek,
	.read = generic_file_read,
	.write = SPADFS_FILE_WRITE,
	.mmap = generic_file_mmap,
	.fsync = SPADFS_FILE_FSYNC,
	.sendfile = generic_file_sendfile,
	.release = SPADFS_RELEASE,
};
#endif

struct address_space_operations spadfs_file_aops = {
	.readpage = SPADFS_READPAGE,
	.writepage = SPADFS_WRITEPAGE,
	.sync_page = block_sync_page,
	.prepare_write = SPADFS_PREPARE_WRITE,
	.commit_write = generic_commit_write,
	.bmap = SPADFS_BMAP,
};

