/*
 *  linux/fs/affs/file.c
 *
 *  (c) 1996  Hans-Joachim Widmaier - Rewritten
 *
 *  (C) 1993  Ray Burr - Modified for Amiga FFS filesystem.
 *
 *  (C) 1992  Eric Youngdale Modified for ISO 9660 filesystem.
 *
 *  (C) 1991  Linus Torvalds - minix filesystem
 *
 *  affs regular file handling primitives
 */

#define DEBUG 0
#include <asm/uaccess.h>
#include <asm/system.h>
#include <linux/sched.h>
#include <linux/affs_fs.h>
#include <linux/fcntl.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/stat.h>
#include <linux/locks.h>
#include <linux/dirent.h>
#include <linux/fs.h>
#include <linux/amigaffs.h>
#include <linux/mm.h>
#include <linux/pagemap.h>

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

#if PAGE_SIZE < 4096
#error PAGE_SIZE must be at least 4096
#endif

static int affs_bmap(struct inode *inode, int block);
static struct buffer_head *affs_getblock(struct inode *inode, s32 block);
static ssize_t affs_file_read_ofs(struct file *filp, char *buf, size_t count, loff_t *ppos);
static ssize_t affs_file_write(struct file *filp, const char *buf, size_t count, loff_t *ppos);
static ssize_t affs_file_write_ofs(struct file *filp, const char *buf, size_t cnt, loff_t *ppos);
static int alloc_ext_cache(struct inode *inode);

static struct file_operations affs_file_operations = {
	NULL,			/* lseek - default */
	generic_file_read,	/* read */
	affs_file_write,	/* write */
	NULL,			/* readdir - bad */
	NULL,			/* poll - default */
	NULL,			/* ioctl - default */
	generic_file_mmap,	/* mmap */
	NULL,			/* no special open */
	NULL,			/* flush */
	NULL,			/* release */
	file_fsync,		/* brute force, but works */
	NULL,			/* fasync */
	NULL			/* lock */
};

struct inode_operations affs_file_inode_operations = {
	&affs_file_operations,	/* default file operations */
	NULL,			/* create */
	NULL,			/* lookup */
	NULL,			/* link */
	NULL,			/* unlink */
	NULL,			/* symlink */
	NULL,			/* mkdir */
	NULL,			/* rmdir */
	NULL,			/* mknod */
	NULL,			/* rename */
	NULL,			/* readlink */
	NULL,			/* follow_link */
	affs_bmap,		/* get_block */
	block_read_full_page,	/* readpage */
	NULL,			/* writepage */
	affs_truncate,		/* truncate */
	NULL,			/* permission */
	NULL			/* revalidate */
};

static struct file_operations affs_file_operations_ofs = {
	NULL,			/* lseek - default */
	affs_file_read_ofs,	/* read */
	affs_file_write_ofs,	/* write */
	NULL,			/* readdir - bad */
	NULL,			/* poll - default */
	NULL,			/* ioctl - default */
	NULL,			/* mmap */
	NULL,			/* no special open */
	NULL,			/* flush */
	NULL,			/* release */
	file_fsync,		/* brute force, but works */
	NULL,			/* fasync */
	NULL			/* lock */
};

struct inode_operations affs_file_inode_operations_ofs = {
	&affs_file_operations_ofs,	/* default file operations */
	NULL,			/* create */
	NULL,			/* lookup */
	NULL,			/* link */
	NULL,			/* unlink */
	NULL,			/* symlink */
	NULL,			/* mkdir */
	NULL,			/* rmdir */
	NULL,			/* mknod */
	NULL,			/* rename */
	NULL,			/* readlink */
	NULL,			/* follow_link */
	NULL,			/* get_block */
	NULL,			/* readpage */
	NULL,			/* writepage */
	affs_truncate,		/* truncate */
	NULL,			/* permission */
	NULL			/* revalidate */
};

#define AFFS_ISINDEX(x)	((x < 129) ||				\
			 (x < 512 && (x & 1) == 0) ||		\
			 (x < 1024 && (x & 3) == 0) ||		\
			 (x < 2048 && (x & 15) == 0) ||		\
			 (x < 4096 && (x & 63) == 0) ||		\
			 (x < 20480 && (x & 255) == 0) ||	\
			 (x < 36864 && (x & 511) == 0))

/* The keys of the extension blocks are stored in a 512-entry
 * deep cache. In order to save memory, not every key of later
 * extension blocks is stored - the larger the file gets, the
 * bigger the holes in between.
 */

static int
seqnum_to_index(int seqnum)
{
	/* All of the first 127 keys are stored */
	if (seqnum < 128)
		return seqnum;
	seqnum -= 128;

	/* Of the next 384 keys, every 2nd is kept */
	if (seqnum < (192 * 2))
		return 128 + (seqnum >> 1);
	seqnum -= 192 * 2;
	
	/* Every 4th of the next 512 */
	if (seqnum < (128 * 4))
		return 128 + 192 + (seqnum >> 2);
	seqnum -= 128 * 4;

	/* Every 16th of the next 1024 */
	if (seqnum < (64 * 16))
		return 128 + 192 + 128 + (seqnum >> 4);
	seqnum -= 64 * 16;

	/* Every 64th of the next 2048 */
	if (seqnum < (32 * 64))
		return 128 + 192 + 128 + 64 + (seqnum >> 6);
	seqnum -= 32 * 64;

	/* Every 256th of the next 16384 */
	if (seqnum < (64 * 256))
		return 128 + 192 + 128 + 64 + 32 + (seqnum >> 8);
	seqnum -= 64 * 256;

	/* Every 512th upto 36479 (1.3 GB with 512 byte blocks).
	 * Seeking to positions behind this will get slower
	 * than dead snails nailed to the ground. But if
	 * someone uses files that large with 512-byte blocks,
	 * he or she deserves no better.
	 */
	
	if (seqnum > (31 * 512))
		seqnum = 31 * 512;
	return 128 + 192 + 128 + 64 + 32 + 64 + (seqnum >> 9);
}

/* Now the other way round: Calculate the sequence
 * number of an extension block of a key at the
 * given index in the cache.
 */

static int
index_to_seqnum(int index)
{
	if (index < 128)
		return index;
	index -= 128;
	if (index < 192)
		return 128 + (index << 1);
	index -= 192;
	if (index < 128)
		return 128 + 192 * 2 + (index << 2);
	index -= 128;
	if (index < 64)
		return 128 + 192 * 2 + 128 * 4 + (index << 4);
	index -= 64;
	if (index < 32)
		return 128 + 192 * 2 + 128 * 4 + 64 * 16 + (index << 6);
	index -= 32;
	if (index < 64)
		return 128 + 192 * 2 + 128 * 4 + 64 * 16 + 32 * 64 + (index << 8);
	index -= 64;
	return 128 + 192 * 2 + 128 * 4 + 64 * 16 + 32 * 64 + 64 * 256 + (index << 9);
}

static s32 __inline__
calc_key(struct inode *inode, int *ext)
{
	int		  index;
	struct key_cache *kc;

	for (index = 0; index < 4; index++) {
		kc = &inode->u.affs_i.i_ec->kc[index];
		if (*ext == kc->kc_this_seq) {
			return kc->kc_this_key;
		} else if (*ext == kc->kc_this_seq + 1) {
			if (kc->kc_next_key)
				return kc->kc_next_key;
			else {
				(*ext)--;
				return kc->kc_this_key;
			}
		}
	}
	index = seqnum_to_index(*ext);
	if (index > inode->u.affs_i.i_ec->max_ext)
		index = inode->u.affs_i.i_ec->max_ext;
	*ext = index_to_seqnum(index);
	return inode->u.affs_i.i_ec->ec[index];
}

static int
affs_bmap(struct inode *inode, int block)
{
	struct buffer_head	*bh;
	s32			 key, nkey;
	s32			 ptype, stype;
	int			 ext;
	int			 index;
	int			 keycount;
	struct key_cache	*kc;
	struct key_cache	*tkc;
	struct timeval		 tv;
	s32			*keyp;
	int			 i;

	pr_debug("AFFS: bmap(%lu,%d)\n",inode->i_ino,block);

	if (block < 0) {
		affs_error(inode->i_sb,"bmap","Block < 0");
		return 0;
	}
	if (!inode->u.affs_i.i_ec) {
		if (alloc_ext_cache(inode)) {
			return 0;
		}
	}

	/* Try to find the requested key in the cache.
	 * In order to speed this up as much as possible,
	 * the cache line lookup is done in a separate
	 * step.
	 */

	for (i = 0; i < 4; i++) {
		tkc = &inode->u.affs_i.i_ec->kc[i];
		/* Look in any cache if the key is there */
		if (block <= tkc->kc_last && block >= tkc->kc_first) {
			return tkc->kc_keys[block - tkc->kc_first];
		}
	}
	kc = NULL;
	tv = xtime;
	for (i = 0; i < 4; i++) {
		tkc = &inode->u.affs_i.i_ec->kc[i];
		if (tkc->kc_lru_time.tv_sec > tv.tv_sec)
			continue;
		if (tkc->kc_lru_time.tv_sec < tv.tv_sec ||
		    tkc->kc_lru_time.tv_usec < tv.tv_usec) {
			kc = tkc;
			tv = tkc->kc_lru_time;
		}
	}
	if (!kc)	/* Really shouldn't happen */
		kc = tkc;
	kc->kc_lru_time = xtime;
	keyp            = kc->kc_keys;
	kc->kc_first    = block;
	kc->kc_last     = -1;
	keycount        = AFFS_KCSIZE;

	/* Calculate sequence number of the extension block where the
	 * number of the requested block is stored. 0 means it's in
	 * the file header.
	 */

	ext    = block / AFFS_I2HSIZE(inode);
	key    = calc_key(inode,&ext);
	block -= ext * AFFS_I2HSIZE(inode);

	for (;;) {
		bh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode));
		if (!bh) 
			return 0;
		index = seqnum_to_index(ext);
		if (index > inode->u.affs_i.i_ec->max_ext &&
		    (affs_checksum_block(AFFS_I2BSIZE(inode),bh->b_data,&ptype,&stype) ||
		     (ptype != T_SHORT && ptype != T_LIST) || stype != ST_FILE)) {
			affs_brelse(bh);
			return 0;
		}
		nkey = be32_to_cpu(FILE_END(bh->b_data,inode)->extension);
		if (block < AFFS_I2HSIZE(inode)) {
			/* Fill cache as much as possible */
			if (keycount) {
				kc->kc_first = ext * AFFS_I2HSIZE(inode) + block;
				keycount     = keycount < AFFS_I2HSIZE(inode) - block ? keycount :
						AFFS_I2HSIZE(inode) - block;
				for (i = 0; i < keycount; i++)
					kc->kc_keys[i] = be32_to_cpu(AFFS_BLOCK(bh->b_data,inode,block + i));
				kc->kc_last = kc->kc_first + i - 1;
			}
			break;
		}
		block -= AFFS_I2HSIZE(inode);
		affs_brelse(bh);
		ext++;
		if (index > inode->u.affs_i.i_ec->max_ext && AFFS_ISINDEX(ext)) {
			inode->u.affs_i.i_ec->ec[index] = nkey;
			inode->u.affs_i.i_ec->max_ext   = index;
		}
		key = nkey;
	}
	kc->kc_this_key = key;
	kc->kc_this_seq = ext;
	kc->kc_next_key = nkey;
	key = be32_to_cpu(AFFS_BLOCK(bh->b_data,inode,block));
	affs_brelse(bh);
	return key;
}

/* With the affs, getting a random block from a file is not
 * a simple business. Since this fs does not allow holes,
 * it may be necessary to allocate all the missing blocks
 * in between, as well as some new extension blocks. The OFS
 * is even worse: All data blocks contain pointers to the
 * next ones, so you have to fix [n-1] after allocating [n].
 * What a mess.
 */

static struct buffer_head *
affs_getblock(struct inode *inode, s32 block)
{
	struct super_block	*sb = inode->i_sb;
	int			 ofs = sb->u.affs_sb.s_flags & SF_OFS;
	int			 ext = block / AFFS_I2HSIZE(inode);
	struct buffer_head	*bh, *ebh, *pbh = NULL;
	struct key_cache	*kc;
	s32			 key, nkey;
	int			 cf, j, pt;
	int			 index;
	int			 err;

	pr_debug("AFFS: getblock(%lu,%d)\n",inode->i_ino,block);

	if (block < 0)
		goto out_fail;

	key    = calc_key(inode,&ext);
	block -= ext * AFFS_I2HSIZE(inode);
	pt     = ext ? T_LIST : T_SHORT;

	/* Key refers now to the last known extension block,
	 * ext is its sequence number (if 0, key refers to the
	 * header block), and block is the block number relative
	 * to the first block stored in that extension block.
	 */
	for (;;) {	/* Loop over header block and extension blocks */
		struct file_front *fdp;

		bh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode));
		if (!bh)
			goto out_fail;
		fdp = (struct file_front *) bh->b_data;
		err = affs_checksum_block(AFFS_I2BSIZE(inode),bh->b_data,&cf,&j);
		if (err || cf != pt || j != ST_FILE) {
		    	affs_error(sb, "getblock",
				"Block %d is not a valid %s", key,
				pt == T_SHORT ? "file header" : "ext block");
			goto out_free_bh;
		}
		j  = be32_to_cpu(((struct file_front *)bh->b_data)->block_count);
		for (cf = 0; j < AFFS_I2HSIZE(inode) && j <= block; j++) {
			if (ofs && !pbh && inode->u.affs_i.i_lastblock >= 0) {
				if (j > 0) {
					s32 k = AFFS_BLOCK(bh->b_data, inode,
								j - 1);
					pbh = affs_bread(inode->i_dev,
							be32_to_cpu(k),
							AFFS_I2BSIZE(inode));
				} else
					pbh = affs_getblock(inode,inode->u.affs_i.i_lastblock);
				if (!pbh) {
					affs_error(sb,"getblock",
						"Cannot get last block in file");
					break;
				}
			}
			nkey = affs_new_data(inode);
			if (!nkey)
				break;
			inode->u.affs_i.i_lastblock++;
			if (AFFS_BLOCK(bh->b_data,inode,j)) {
				affs_warning(sb,"getblock","Block already allocated");
				affs_free_block(sb,nkey);
				continue;
			}
			AFFS_BLOCK(bh->b_data,inode,j) = cpu_to_be32(nkey);
			if (ofs) {
				ebh = affs_bread(inode->i_dev,nkey,AFFS_I2BSIZE(inode));
				if (!ebh) {
					affs_error(sb,"getblock",
						   "Cannot get block %d",nkey);
					affs_free_block(sb,nkey);
					AFFS_BLOCK(bh->b_data,inode,j) = 0;
					break;
				}
				DATA_FRONT(ebh)->primary_type    = cpu_to_be32(T_DATA);
				DATA_FRONT(ebh)->header_key      = cpu_to_be32(inode->i_ino);
				DATA_FRONT(ebh)->sequence_number = cpu_to_be32(inode->u.affs_i.i_lastblock + 1);
				affs_fix_checksum(AFFS_I2BSIZE(inode),
							ebh->b_data, 5);
				mark_buffer_dirty(ebh, 0);
				if (pbh) {
					DATA_FRONT(pbh)->data_size = cpu_to_be32(AFFS_I2BSIZE(inode) - 24);
					DATA_FRONT(pbh)->next_data = cpu_to_be32(nkey);
					affs_fix_checksum(AFFS_I2BSIZE(inode),pbh->b_data,5);
					mark_buffer_dirty(pbh,0);
					affs_brelse(pbh);
				}
				pbh = ebh;
			}
			cf = 1;
		}
		/* N.B. May need to release pbh after here */

		if (cf) {
			if (pt == T_SHORT)
				fdp->first_data = AFFS_BLOCK(bh->b_data,inode,0);
			fdp->block_count = cpu_to_be32(j);
			affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5);
			mark_buffer_dirty(bh,1);
		}

		if (block < j) {
			if (pbh)
				affs_brelse(pbh);
			break;
		}
		if (j < AFFS_I2HSIZE(inode)) {
			/* N.B. What about pbh here? */
			goto out_free_bh;
		}

		block -= AFFS_I2HSIZE(inode);
		key    = be32_to_cpu(FILE_END(bh->b_data,inode)->extension);
		if (!key) {
			key = affs_new_header(inode);
			if (!key)
				goto out_free_bh;
			ebh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode));
			if (!ebh) {
				/* N.B. must free bh here */
				goto out_free_block;
			}
			((struct file_front *)ebh->b_data)->primary_type = cpu_to_be32(T_LIST);
			((struct file_front *)ebh->b_data)->own_key      = cpu_to_be32(key);
			FILE_END(ebh->b_data,inode)->secondary_type      = cpu_to_be32(ST_FILE);
			FILE_END(ebh->b_data,inode)->parent              = cpu_to_be32(inode->i_ino);
			affs_fix_checksum(AFFS_I2BSIZE(inode),ebh->b_data,5);
			mark_buffer_dirty(ebh, 1);
			FILE_END(bh->b_data,inode)->extension = cpu_to_be32(key);
			affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5);
			mark_buffer_dirty(bh,1);
			affs_brelse(bh);
			bh = ebh;
		}
		pt = T_LIST;
		ext++;
		index = seqnum_to_index(ext);
		if (index > inode->u.affs_i.i_ec->max_ext &&
		    AFFS_ISINDEX(ext)) {
			inode->u.affs_i.i_ec->ec[index] = key;
			inode->u.affs_i.i_ec->max_ext   = index;
		}
		affs_brelse(bh);
	}

	/* Invalidate key cache */
	for (j = 0; j < 4; j++) {
		kc = &inode->u.affs_i.i_ec->kc[j];
		kc->kc_last = -1;
	}
	key = be32_to_cpu(AFFS_BLOCK(bh->b_data,inode,block));
	affs_brelse(bh);
	if (!key)
		goto out_fail;

	bh = affs_bread(inode->i_dev, key, AFFS_I2BSIZE(inode));
	return bh;

out_free_block:
	affs_free_block(sb, key);
out_free_bh:
	affs_brelse(bh);
out_fail:
	return NULL;
}

static ssize_t
affs_file_read_ofs(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
	struct inode		*inode = filp->f_dentry->d_inode;
	char			*start;
	ssize_t			 left, offset, size, sector;
	ssize_t			 blocksize;
	struct buffer_head	*bh;
	void			*data;

	pr_debug("AFFS: file_read_ofs(ino=%lu,pos=%lu,%d)\n",inode->i_ino,
		 (unsigned long)*ppos,count);

	if (!inode) {
		affs_error(inode->i_sb,"file_read_ofs","Inode = NULL");
		return -EINVAL;
	}
	blocksize = AFFS_I2BSIZE(inode) - 24;
	if (!(S_ISREG(inode->i_mode))) {
		pr_debug("AFFS: file_read: mode = %07o",inode->i_mode);
		return -EINVAL;
	}
	if (*ppos >= inode->i_size || count <= 0)
		return 0;

	start = buf;
	for (;;) {
		left = MIN (inode->i_size - *ppos,count - (buf - start));
		if (!left)
			break;
		sector = affs_bmap(inode,(u32)*ppos / blocksize);
		if (!sector)
			break;
		offset = (u32)*ppos % blocksize;
		bh = affs_bread(inode->i_dev,sector,AFFS_I2BSIZE(inode));
		if (!bh)
			break;
		data = bh->b_data + 24;
		size = MIN(blocksize - offset,left);
		*ppos += size;
		copy_to_user(buf,data + offset,size);
		buf += size;
		affs_brelse(bh);
	}
	if (start == buf)
		return -EIO;
	return buf - start;
}

static ssize_t
affs_file_write(struct file *filp, const char *buf, size_t count, loff_t *ppos)
{
	struct inode		*inode = filp->f_dentry->d_inode;
	off_t			 pos;
	ssize_t			 written;
	ssize_t			 c;
	ssize_t			 blocksize;
	struct buffer_head	*bh;
	char			*p;

	if (!count)
		return 0;
	pr_debug("AFFS: file_write(ino=%lu,pos=%lu,count=%d)\n",inode->i_ino,
		 (unsigned long)*ppos,count);

	if (!inode) {
		affs_error(inode->i_sb,"file_write","Inode = NULL");
		return -EINVAL;
	}
	if (!S_ISREG(inode->i_mode)) {
		affs_error(inode->i_sb,"file_write",
			   "Trying to write to non-regular file (mode=%07o)",
			   inode->i_mode);
		return -EINVAL;
	}
	if (!inode->u.affs_i.i_ec && alloc_ext_cache(inode))
		return -ENOMEM;
	if (filp->f_flags & O_APPEND)
		pos = inode->i_size;
	else
		pos = *ppos;
	written   = 0;
	blocksize = AFFS_I2BSIZE(inode);

	while (written < count) {
		bh = affs_getblock(inode,pos / blocksize);
		if (!bh) {
			if (!written)
				written = -ENOSPC;
			break;
		}
		c = blocksize - (pos % blocksize);
		if (c > count - written)
			c = count - written;
		if (c != blocksize && !buffer_uptodate(bh)) {
			ll_rw_block(READ,1,&bh);
			wait_on_buffer(bh);
			if (!buffer_uptodate(bh)) {
				affs_brelse(bh);
				if (!written)
					written = -EIO;
				break;
			}
		}
		p  = (pos % blocksize) + bh->b_data;
		c -= copy_from_user(p,buf,c);
		if (!c) {
			affs_brelse(bh);
			if (!written)
				written = -EFAULT;
			break;
		}
		update_vm_cache(inode,pos,p,c);
		mark_buffer_uptodate(bh,1);
		mark_buffer_dirty(bh,0);
		affs_brelse(bh);
		pos     += c;
		written += c;
		buf     += c;
	}
	if (pos > inode->i_size)
		inode->i_size = pos;
	inode->i_mtime = inode->i_ctime = CURRENT_TIME;
	*ppos          = pos;
	mark_inode_dirty(inode);
	return written;
}

static ssize_t
affs_file_write_ofs(struct file *filp, const char *buf, size_t count, loff_t *ppos)
{
	struct inode		*inode = filp->f_dentry->d_inode;
	off_t			 pos;
	ssize_t			 written;
	ssize_t			 c;
	ssize_t			 blocksize;
	struct buffer_head	*bh;
	char			*p;

	pr_debug("AFFS: file_write_ofs(ino=%lu,pos=%lu,count=%d)\n",inode->i_ino,
		(unsigned long)*ppos,count);

	if (!count)
		return 0;
	if (!inode) {
		affs_error(inode->i_sb,"file_write_ofs","Inode = NULL");
		return -EINVAL;
	}
	if (!S_ISREG(inode->i_mode)) {
		affs_error(inode->i_sb,"file_write_ofs",
			   "Trying to write to non-regular file (mode=%07o)",
			   inode->i_mode);
		return -EINVAL;
	}
	if (!inode->u.affs_i.i_ec && alloc_ext_cache(inode))
		return -ENOMEM;
	if (filp->f_flags & O_APPEND)
		pos = inode->i_size;
	else
		pos = *ppos;

	bh        = NULL;
	blocksize = AFFS_I2BSIZE(inode) - 24;
	written   = 0;
	while (written < count) {
		bh = affs_getblock(inode,pos / blocksize);
		if (!bh) {
			if (!written)
				written = -ENOSPC;
			break;
		}
		c = blocksize - (pos % blocksize);
		if (c > count - written)
			c = count - written;
		if (c != blocksize && !buffer_uptodate(bh)) {
			ll_rw_block(READ,1,&bh);
			wait_on_buffer(bh);
			if (!buffer_uptodate(bh)) {
				affs_brelse(bh);
				if (!written)
					written = -EIO;
				break;
			}
		}
		p  = (pos % blocksize) + bh->b_data + 24;
		c -= copy_from_user(p,buf,c);
		if (!c) {
			affs_brelse(bh);
			if (!written)
				written = -EFAULT;
			break;
		}
		update_vm_cache(inode,pos,p,c);

		pos     += c;
		buf     += c;
		written += c;
		DATA_FRONT(bh)->data_size = cpu_to_be32(be32_to_cpu(DATA_FRONT(bh)->data_size) + c);
		affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5);
		mark_buffer_uptodate(bh,1);
		mark_buffer_dirty(bh,0);
		affs_brelse(bh);
	}
	if (pos > inode->i_size)
		inode->i_size = pos;
	*ppos = pos;
	inode->i_mtime = inode->i_ctime = CURRENT_TIME;
	mark_inode_dirty(inode);
	return written;
}

/* Free any preallocated blocks. */

void
affs_free_prealloc(struct inode *inode)
{
	struct super_block	*sb = inode->i_sb;
	struct affs_zone	*zone;
	int block;

	pr_debug("AFFS: free_prealloc(ino=%lu)\n", inode->i_ino);

	while (inode->u.affs_i.i_pa_cnt) {	
		block = inode->u.affs_i.i_data[inode->u.affs_i.i_pa_next++];
		inode->u.affs_i.i_pa_next &= AFFS_MAX_PREALLOC - 1;
		inode->u.affs_i.i_pa_cnt--;
		affs_free_block(sb, block);
	}
	if (inode->u.affs_i.i_zone) {
		zone = &sb->u.affs_sb.s_zones[inode->u.affs_i.i_zone];
		if (zone->z_ino == inode->i_ino)
			zone->z_ino = 0;
	}
}

/* Truncate (or enlarge) a file to the requested size. */

void
affs_truncate(struct inode *inode)
{
	struct buffer_head	*bh = NULL;
	int	 first;			/* First block to be thrown away	*/
	int	 block;
	s32	 key;
	s32	*keyp;
	s32	 ekey;
	s32	 ptype, stype;
	int	 freethis;
	int	 net_blocksize;
	int	 blocksize = AFFS_I2BSIZE(inode);
	int	 rem;
	int	 ext;

	pr_debug("AFFS: truncate(inode=%ld,size=%lu)\n",inode->i_ino,inode->i_size);

	net_blocksize = blocksize - ((inode->i_sb->u.affs_sb.s_flags & SF_OFS) ? 24 : 0);
	first = (inode->i_size + net_blocksize - 1) / net_blocksize;
	if (inode->u.affs_i.i_lastblock < first - 1) {
		/* There has to be at least one new block to be allocated */
		if (!inode->u.affs_i.i_ec && alloc_ext_cache(inode)) {
			/* XXX Fine! No way to indicate an error. */
			return /* -ENOSPC */;
		}
		bh = affs_getblock(inode,first - 1);
		if (!bh) {
			affs_warning(inode->i_sb,"truncate","Cannot extend file");
			inode->i_size = net_blocksize * (inode->u.affs_i.i_lastblock + 1);
		} else if (inode->i_sb->u.affs_sb.s_flags & SF_OFS) {
			rem = inode->i_size % net_blocksize;
			DATA_FRONT(bh)->data_size = cpu_to_be32(rem ? rem : net_blocksize);
			affs_fix_checksum(blocksize,bh->b_data,5);
			mark_buffer_dirty(bh,0);
		}
		goto out_truncate;
	}
	ekey = inode->i_ino;
	ext  = 0;

	/* Free all blocks starting at 'first' and all then-empty
	 * extension blocks. Do not free the header block, though.
	 */
	while (ekey) {
		if (!(bh = affs_bread(inode->i_dev,ekey,blocksize))) {
			affs_error(inode->i_sb,"truncate","Cannot read block %d",ekey);
			goto out_truncate;
		}
		if (affs_checksum_block(blocksize,bh->b_data,&ptype,&stype)) {
			affs_error(inode->i_sb,"truncate","Checksum error in header/ext block %d",
				   ekey);
			goto out_truncate;
		}
		if (stype != ST_FILE || (ptype != T_SHORT && ptype != T_LIST)) {
			affs_error(inode->i_sb,"truncate",
				   "Bad block (key=%d, ptype=%d, stype=%d)",ekey,ptype,stype);
			goto out_truncate;
		}
		/* Do we have to free this extension block after
		 * freeing the data blocks pointed to?
		 */
		freethis = first == 0 && ekey != inode->i_ino;

		/* Free the data blocks. 'first' is relative to this
		 * extension block and may well lie behind this block.
		 */
		for (block = first; block < AFFS_I2HSIZE(inode); block++) {
			keyp = &AFFS_BLOCK(bh->b_data,inode,block);
			key  = be32_to_cpu(*keyp);
			if (key) {
				*keyp = 0;
				affs_free_block(inode->i_sb,key);
			} else
				break;
		}
		keyp = &GET_END_PTR(struct file_end,bh->b_data,blocksize)->extension;
		key  = be32_to_cpu(*keyp);

		/* If 'first' is in this block or is the first
		 * in the next one, this will be the last in
		 * the list, thus we have to adjust the count
		 * and zero the pointer to the next ext block.
		 */
		if (first <= AFFS_I2HSIZE(inode)) {
			((struct file_front *)bh->b_data)->block_count = cpu_to_be32(first);
			first = 0;
			*keyp = 0;
			affs_fix_checksum(blocksize,bh->b_data,5);
			mark_buffer_dirty(bh,1);
		} else
			first -= AFFS_I2HSIZE(inode);
		affs_brelse(bh);
		bh = NULL;
		if (freethis)			/* Don't bother fixing checksum */
			affs_free_block(inode->i_sb,ekey);
		ekey = key;
	}
	block = ((inode->i_size + net_blocksize - 1) / net_blocksize) - 1;
	inode->u.affs_i.i_lastblock = block;

	/* If the file is not truncated to a block boundary,
	 * the partial block after the EOF must be zeroed
	 * so it cannot become accessible again.
	 */

	rem = inode->i_size % net_blocksize;
	if (rem) {
		if ((inode->i_sb->u.affs_sb.s_flags & SF_OFS)) 
			rem += 24;
		pr_debug("AFFS: Zeroing from offset %d in block %d\n",rem,block);
		bh = affs_getblock(inode,block);
		if (bh) {
			memset(bh->b_data + rem,0,blocksize - rem);
			if ((inode->i_sb->u.affs_sb.s_flags & SF_OFS)) {
				((struct data_front *)bh->b_data)->data_size = cpu_to_be32(rem);
				((struct data_front *)bh->b_data)->next_data = 0;
				affs_fix_checksum(blocksize,bh->b_data,5);
			}
			mark_buffer_dirty(bh,1);
		} else 
			affs_error(inode->i_sb,"truncate","Cannot read block %d",block);
	}

out_truncate:
	affs_brelse(bh);
	/* Invalidate cache */
	if (inode->u.affs_i.i_ec) {
		inode->u.affs_i.i_ec->max_ext = 0;
		for (key = 0; key < 3; key++) {
			inode->u.affs_i.i_ec->kc[key].kc_next_key = 0;
			inode->u.affs_i.i_ec->kc[key].kc_last     = -1;
		}
	}
	mark_inode_dirty(inode);
}

/*
 * Called only when we need to allocate the extension cache.
 */

static int
alloc_ext_cache(struct inode *inode)
{
	s32	 key;
	int	 i;
	unsigned long cache_page;
	int      error = 0;

	pr_debug("AFFS: alloc_ext_cache(ino=%lu)\n",inode->i_ino);

	cache_page = get_free_page(GFP_KERNEL);
	/*
	 * Check whether somebody else allocated it for us ...
	 */
	if (inode->u.affs_i.i_ec)
		goto out_free;
	if (!cache_page)
		goto out_error;

	inode->u.affs_i.i_ec = (struct ext_cache *) cache_page;
	/* We only have to initialize non-zero values.
	 * get_free_page() zeroed the page already.
	 */
	key = inode->i_ino;
	inode->u.affs_i.i_ec->ec[0] = key;
	for (i = 0; i < 4; i++) {
		inode->u.affs_i.i_ec->kc[i].kc_this_key = key;
		inode->u.affs_i.i_ec->kc[i].kc_last     = -1;
	}
out:
	return error;

out_free:
	if (cache_page)
		free_page(cache_page);
	goto out;

out_error:
	affs_error(inode->i_sb,"alloc_ext_cache","Cache allocation failed");
	error = -ENOMEM;
	goto out;
}
