#include <linux/version.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pagemap.h>
#include <linux/smp_lock.h>
#include <linux/signal.h>

#include <asm/uaccess.h>
#include <asm/system.h>

#include "ftpfs.h"
#include "ftpfs_proc.h"

static int
ftp_file_readpage(struct file *f, struct page *p){
	struct dentry *dentry = f->f_dentry;
	char *buffer;
	unsigned long offset, count;
	int result;
	
	get_page(p);

	buffer = page_address(p);
	offset = p->index << PAGE_CACHE_SHIFT;
	count = PAGE_SIZE;

	do{
		result = ftp_read(dentry, offset, count, buffer);
		if(result < 0){
			VERBOSE(" IO error!\n");
			goto io_error;
		}
		count -= result;
		offset += result;
		buffer += result;
		dentry->d_inode->i_atime = CURRENT_TIME;
		if(!result) break;
	}while(count);

	memset(buffer, 0, count);
	flush_dcache_page(p);
	SetPageUptodate(p);
	result = 0;
io_error:
	UnlockPage(p);	
	put_page(p);
	switch(result){
	    case -ERESTARTSYS:	if(sigismember(&current->pending.signal, SIGPIPE)){
					sigdelset(&current->pending.signal, SIGPIPE);
					current->sigpending--;
				}
	    
				result = -EINTR;
				break;
	}
	return result;
}

static int
ftp_file_writepage(struct page *p){
	DEBUG(" This should't happen!!\n");
	return -EFAULT;
}

static int
ftp_file_preparewrite(struct file *f, struct page *p, unsigned offset, unsigned to){
	DEBUG("\n");
	kmap(p);
	return 0;
}

static int
ftp_file_commitwrite(struct file *f, struct page *p, unsigned offset, unsigned to){
	struct dentry *dentry = f->f_dentry;
	char *buffer = page_address(p) + offset;
	int written = 0, result;
	unsigned count = to - offset;
	
	offset += p->index << PAGE_CACHE_SHIFT;
		
	DEBUG("\n");
	lock_kernel();

	do{
		result = ftp_write(dentry, offset, count, buffer);
		if(result < 0){
			VERBOSE(" IO error!\n");
			goto error;
		}

		count -= result;
		offset += result;
		buffer += result;
		written += result;
		dentry->d_inode->i_mtime = dentry->d_inode->i_atime = CURRENT_TIME;
		if(!result)
			break;

	}while(count);

	memset(buffer, 0, count);
	result = 0;
error:
	unlock_kernel();
	kunmap(p);

	switch(result){
	    case -ERESTARTSYS:	if(sigismember(&current->pending.signal, SIGPIPE)){
					sigdelset(&current->pending.signal, SIGPIPE);
					current->sigpending--;
				}
	    
				result = -EINTR;
				break;
	}

	return (result == 0) ? written : result;
}

static int
ftp_file_permission(struct inode *inode, int mask){
	int mode = inode->i_mode;

	mode >>= 6;
	if((mode & 7 & mask) != mask)
		return -EACCES;
	return 0;
}

static int
ftp_file_open(struct inode *inode, struct file *f){
	return 0;
}

static int
ftp_file_release(struct inode *inode, struct file *f){
//	struct dentry *dentry = f->f_dentry;
//	if(dentry->d_count == 1){
		VERBOSE(" FIXME: should do some stuff here!\n");
//	}
	return 0;
}

struct file_operations ftp_file_operations = {
	read:		generic_file_read,
	write:		generic_file_write,
	mmap:		generic_file_mmap,
	open:		ftp_file_open,
	release:	ftp_file_release,
};

struct inode_operations ftp_file_inode_operations = {
	permission:	ftp_file_permission,
};

struct address_space_operations ftp_file_aops = {
	readpage:		ftp_file_readpage,
	writepage:		ftp_file_writepage,
	prepare_write:	ftp_file_preparewrite,
	commit_write:	ftp_file_commitwrite
};

