/*
 *  ntfs_fs.c
 *  NTFS driver for Linux
 *
 *  Copyright (C) 1995,1996 Martin von Löwis
 *  Copyright (C) 1996 Richard Russon
 */

#ifdef MODULE
#ifdef CONFIG_MODVERSIONS
#warning Remove -DCONFIG_MODVERSIONS if your kernel is compiled without.
#define MODVERSIONS
#include <linux/modversions.h>
#else
#warning Add -DCONFIG_MODVERSIONS if your kernel is compiled with this option.
#endif

#include <linux/module.h>
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#endif

#include <linux/version.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/locks.h>
#include <linux/stat.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <errno.h>
#include <asm/segment.h>
#include "config.h"
#include "ntfs.h"

#if (LINUX_VERSION_CODE>(1*65536+3*256)) && (LINUX_VERSION_CODE<(1*65536+3*256+20))
#error Please upgrade to a newer subrelease of Linux 1.3
#endif

#if LINUX_VERSION_CODE>(1*65536+3*256)
#define NEW_READDIR
#define NEW_STATFS
#endif

extern struct inode_operations ntfs_inode_operations;
extern struct inode_operations ntfs_inode_operations_nobmap;
extern struct inode_operations ntfs_dir_inode_operations;
extern struct super_operations ntfs_super_operations;

void ntfs_free(void*);

#ifdef DEBUG
#define PRINTK(x,args...)	printk(x, ##args)
#else
#define PRINTK(x,args...)
#endif

/* memcpy_tofs is a macro, so we define a wrapper here */
static void *my_memcpy_touser(void* dest,const void* src,size_t len)
{
	memcpy_tofs(dest,src,len);
	return dest;
}

void *ntfs_memcpy(void*dest,const void*src,size_t len)
{
	return memcpy(dest,src,len);
}

void *ntfs_memset(void *dest,int c,size_t n)
{
	memset(dest,c,n);
	return dest;
}

int ntfs_strlen(char*s)
{
	return strlen(s);
}

static int ntfs_read(struct inode* inode, struct file * filp, char *buf, int count)
{
	int ret;
	PRINTK(KERN_DEBUG "ntfs_read %x,%x,%x ->",
		(unsigned)inode->i_ino,(unsigned)filp->f_pos,(unsigned)count);
	/* inode is not properly initialized */
	if(!inode || !inode->u.generic_ip)return -EINVAL;
	/* inode has no unnamed data attribute */
	if(!ntfs_find_attr((ntfs_inode*)inode->u.generic_ip,AT_DATA,NULL))
		return -EINVAL;

	/* read the data */
	ret=ntfs_read_attr((ntfs_inode*)inode->u.generic_ip,
		AT_DATA,NULL,filp->f_pos,buf,count,my_memcpy_touser);
	PRINTK("%x\n",ret);
	if(ret<0)return -EINVAL;

	filp->f_pos+=ret;
	return ret;
}

static int ntfs_write(struct inode* inode, struct file *filp,
	const char* buf, int count)
{
	struct super_block* sb;
	int ret;
	PRINTK(KERN_DEBUG "ntfs_write %x,%x,%x ->",
		(unsigned)inode->i_ino,(unsigned)filp->f_pos,(unsigned)count);
	if(!inode || !inode->u.generic_ip)
		return -EINVAL;
	sb = inode->i_sb;
	/* Allows to lock fs ro at any time */
	if(sb->s_flags & MS_RDONLY)
		return -ENOSPC;
	if(!ntfs_find_attr(inode->u.generic_ip,AT_DATA,NULL))
		return -EINVAL;

	ret = ntfs_write_attr((ntfs_inode*)inode->u.generic_ip,
		AT_DATA,NULL,filp->f_pos,(char*)buf,count,my_memcpy_touser);
	PRINTK("%x\n",ret);
	if(ret<0)return -EINVAL;

	filp->f_pos+=ret;
	return ret;
}

static void ntfs_read_inode(struct inode* inode)
{
	ntfs_volume *vol;
	int can_mmap=0;
	ntfs_inode *ino;
	ntfs_attribute *data;
	ntfs_attribute *si;

	vol=NTFS_INO2VOL(inode);
	inode->i_op=NULL;
	inode->i_mode=0;
	inode->u.generic_ip=NULL;
	PRINTK(KERN_DEBUG "ntfs_read_inode %x\n",(unsigned)inode->i_ino);

	switch(inode->i_ino)
	{
		/* those are loaded special files */
		case FILE_MFT:
			ino=NTFS_INO2VOL(inode)->mft_ino;break;
		default:
			ino=(ntfs_inode*)ntfs_malloc(sizeof(ntfs_inode));
			if(!ino || ntfs_init_inode(ino,
				NTFS_INO2VOL(inode),inode->i_ino)==-1)
			{
				printk("NTFS:Error loading inode %x\n",
					(unsigned int)inode->i_ino);
				return;
			}
	}
	inode->u.generic_ip=ino;
	/* Set uid/gid from mount options */
	inode->i_uid=vol->uid;
	inode->i_gid=vol->gid;
	inode->i_nlink=1;
	/* Use the size of the data attribute as file size */
	data = ntfs_find_attr(ino,AT_DATA,NULL);
	if(!data)
	{
		inode->i_size=0;
		can_mmap=0;
	}
	else
	{
		inode->i_size=DATASIZE(&data->header);
		can_mmap=!RESIDENT(&data->header) && !COMPRESSED(&data->header);
	}
	/* get the file modification times from the standard information */
	si=ntfs_find_attr(ino,AT_STANDARD_INFORMATION,NULL);
	if(si){
		char *attr=si->d.data;
		inode->i_atime=ntfs_ntutc2unixutc(*(long long*)(attr+0x18));
		inode->i_ctime=ntfs_ntutc2unixutc(*(long long*)(attr));
		inode->i_mtime=ntfs_ntutc2unixutc(*(long long*)(attr+8));
	}
	/* if it has an index root, it's a directory */
	if(ntfs_find_attr(ino,AT_INDEX_ROOT,"$I30"))
	{
		inode->i_op=&ntfs_dir_inode_operations;
		inode->i_mode=S_IFDIR|S_IRUGO|S_IXUGO;
	}
	else
	{
		inode->i_op=can_mmap ? &ntfs_inode_operations : 
							   &ntfs_inode_operations_nobmap;
		inode->i_mode=S_IFREG|S_IRUGO|S_IMMUTABLE;
		inode->i_mode=S_IFREG|S_IRUGO;
	}
	inode->i_mode &= ~vol->umask;
}

/* readdir returns '..', then '.', then the directory entries in sequence
   As the root directory contains a entry for itself, '.' is not emulated
   for the root directory */
#ifdef NEW_READDIR
static int ntfs_readdir(struct inode* dir, struct file *filp, 
	void *dirent, filldir_t filldir)
#else
static int ntfs_readdir(struct inode* dir, struct file *filp, 
	struct dirent *dirent, int count)
#endif
{
#define ITEM_SIZE 2040
#define NAME_SIZE 300
	char *name=0;
	char *item=0;
	int length;
	PRINTK(KERN_DEBUG "ntfs_readdir ino %x mode %x\n",
		(unsigned)dir->i_ino,(unsigned int)dir->i_mode);
	if(!dir || (dir->i_ino==0) || !S_ISDIR(dir->i_mode))return -EBADF;

	PRINTK(KERN_DEBUG "readdir: Looking for file %x dircount %d\n",
		(unsigned)filp->f_pos,dir->i_count);
	item=ntfs_malloc(ITEM_SIZE);
	name=ntfs_malloc(NAME_SIZE);
	if(filp->f_pos==0)
	{	/* Start from the beginning. Return '..' */
		length=2;
		strcpy(name,"..");
		filp->f_pos=-2;	/*remember to continue with . */
	}else if(filp->f_pos==-2 && dir->i_ino!=FILE_ROOT){
		/* Position on first file, return '.', unless it is the root dir */
		filp->f_pos=0;
		ntfs_getdir_byposition((ntfs_inode*)dir->u.generic_ip,
			&filp->f_pos,item);
		length=1;
		strcpy(name,".");
	}else{
		if(filp->f_pos==-2) /* we didn't fake '.' for the root */
		{
			filp->f_pos=0;
			ntfs_getdir_byposition((ntfs_inode*)dir->u.generic_ip,
				&filp->f_pos,item);
		}
		/* proceed to the next entry, find the current entry */
		if(!ntfs_getdir_byposition((ntfs_inode*)dir->u.generic_ip,
			&filp->f_pos,item))
		{
			ntfs_free(item);
			ntfs_free(name);
			return 0;
		}
		/* get the name out of the entry. We have all kinds of information,
		   but readdir doesn't want it. What a waste */
		length=min((unsigned char)*(item+0x50),NAME_SIZE);
		ntfs_uni2ascii(name,item+0x52,length);
		/* at least, remember the inode number, so the we don't have to 
		   search at the next lookup */
		dcache_add(dir,name,length,*(int*)item);
	}
	PRINTK(KERN_DEBUG "new position:%x\n",filp->f_pos);
	PRINTK(KERN_DEBUG "Name is %s,%d\n",name,length);
	/* Return the name to user space */
#ifdef NEW_READDIR
	filldir(dirent,name,length,filp->f_pos,*(int*)item);
	ntfs_free(item);
	ntfs_free(name);
	return 0;
#else
	put_user_long(*(int*)item,(int*)&dirent->d_ino);
	memcpy_tofs(dirent->d_name,name,length+1);
	put_user_word(length,&dirent->d_reclen);
	ntfs_free(item);
	ntfs_free(name);
	/* What are we supposed to return here, anyways? */
	return length;
#endif
}

static void ntfs_put_inode(struct inode *ino)
{
	PRINTK(KERN_DEBUG "ntfs_put_inode %x\n",ino->i_ino);
	if(ino->i_ino!=FILE_MFT && ino->u.generic_ip)
	{
		ntfs_clear_inode(ino->u.generic_ip);
		ntfs_free(ino->u.generic_ip);
		ino->u.generic_ip=0;
	}
	clear_inode(ino);
}

#ifdef NEW_STATFS
static void ntfs_statfs(struct super_block *sb, struct statfs *sf, int bufsize)
#else
static void ntfs_statfs(struct super_block *sb, struct statfs *sf)
#endif
{
	struct statfs fs;
	struct inode *bitmap;
	struct inode *mft;
	ntfs_volume *vol;
	PRINTK(KERN_DEBUG "ntfs_statfs\n");
	vol=NTFS_SB2VOL(sb);
	memset(&fs,0,sizeof(fs));
	fs.f_type=NTFS_SUPER_MAGIC;
	fs.f_bsize=vol->clustersize;

	fs.f_blocks=ntfs_get_volumesize(NTFS_SB2VOL(sb));
	bitmap=iget(sb,FILE_BITMAP);
	fs.f_bfree=ntfs_get_free_cluster_count((ntfs_inode*)bitmap->u.generic_ip);
	iput(bitmap);
	fs.f_bavail=fs.f_bfree;

	/* Number of files is limited by free space only, so we lie here */
	fs.f_ffree=0;
	mft=iget(sb,FILE_MFT);
	fs.f_files=mft->i_size/vol->mft_recordsize;
	iput(mft);

	/* should be read from volume */
	fs.f_namelen=255;
#ifdef NEW_STATFS
	memcpy_tofs(sf,&fs,bufsize);
#else
	memcpy_tofs(sf,&fs,sizeof(fs));
#endif
}

static int parse_options(ntfs_volume* vol,char *opt)
{
	char *value;

	vol->uid=vol->gid=0;
	vol->umask=0777;
	if(!opt)return 1;

	for(opt = strtok(opt,",");opt;opt=strtok(NULL,","))
	{
		if ((value = strchr(opt, '=')) != NULL)
			*value++='\0';
		if(strcmp(opt,"uid")==0)
		{
			if(!value || !*value){
				printk(KERN_ERR "NTFS: uid needs an argument\n");
				return 0;
			}
			vol->uid=simple_strtoul(value,&value,0);
			if(*value){
				printk(KERN_ERR "NTFS: uid invalid argument\n");
				return 0;
			}
		}else if(strcmp(opt, "gid") == 0)
		{
			if(!value || !*value){
				printk(KERN_ERR "NTFS: gid needs an argument\n");
				return 0;
			}
			vol->gid=simple_strtoul(value,&value,0);
			if(*value){
				printk(KERN_ERR "gid invalid argument\n");
				return 0;
			}
		}else if(strcmp(opt, "umask") == 0)
		{
			if(!value || !*value){
				printk(KERN_ERR "NTFS: umask needs an argument\n");
				return 0;
			}
			vol->umask=simple_strtoul(value,&value,0);
			if(*value){
				printk(KERN_ERR "umask invalid argument\n");
				return 0;
			}
		}else{
			printk(KERN_ERR "NTFS: unkown option '%s'\n", opt);
			return 0;
		}
	}
	return 1;
}
			

struct super_block * ntfs_read_super(struct super_block *sb, void *data, int silent)
{
	struct buffer_head *bh;
	int dev=sb->s_dev;
	int i;
	ntfs_volume *vol;
	ntfs_volume myvol;
#if defined(DEBUG) && !defined(MODULE)
	extern int console_loglevel;
	console_loglevel=15;
#endif
	PRINTK(KERN_DEBUG "ntfs_read_super\n");
	MOD_INC_USE_COUNT;
	lock_super(sb);
	PRINTK(KERN_DEBUG "lock_super\n");


	if(!parse_options(&myvol,(char*)data)){
		MOD_DEC_USE_COUNT;
		return 0;
	}

	/* Read the superblock assuming 512 byte blocks for now */
	set_blocksize(dev,512);
	/* Read the boot block to get the location of the MFT */
	if(!(bh=bread(dev,0,512)))
	{
		printk("Reading super block failed\n");
		sb->s_dev=0;
		unlock_super(sb);
		MOD_DEC_USE_COUNT;
		return NULL;
	}
	PRINTK(KERN_DEBUG "Done reading boot block\n");

	/* Check for 'NTFS' magic at offset 2 */
	if(!IS_NTFS_VOLUME(bh->b_data)){
		printk(KERN_DEBUG "Not a NTFS volume\n");
		sb->s_dev=0;
		brelse(bh);
		unlock_super(sb);
		MOD_DEC_USE_COUNT;
		return NULL;
	}

	PRINTK(KERN_DEBUG "Going to init volume\n");
	vol=ntfs_malloc(sizeof(ntfs_volume));
	/*copy uid, ... */
	*vol=myvol;
	ntfs_init_volume(vol,bh->b_data);
	NTFS_SB(vol)=sb;
	NTFS_SB2VOL(sb)=vol;
	PRINTK(KERN_DEBUG "Done to init volume\n");

	sb->s_blocksize=vol->clustersize;

	for(i=sb->s_blocksize,sb->s_blocksize_bits=0;i;i>>=1)
		sb->s_blocksize_bits++;

	set_blocksize(dev,sb->s_blocksize);
	PRINTK(KERN_DEBUG "set_blocksize\n");

	PRINTK(KERN_DEBUG "MFT record at cluster 0x%X\n",vol->mft_cluster);
	brelse(bh);

	/* Allocate MFT record 0 */
	sb->u.generic_sbp=vol;
	vol->mft=ntfs_malloc(vol->mft_recordsize);

	/* read the MFT record 0 */
	for(i=0;i<vol->mft_clusters_per_record;i++){
		if(!(bh=bread(dev,vol->mft_cluster+i,vol->clustersize)))
		{
			printk(KERN_DEBUG "Could not read MFT block 0\n");
			sb->s_dev=0;
			unlock_super(sb);
			MOD_DEC_USE_COUNT;
			return NULL;
		}
		memcpy(vol->mft+i*vol->clustersize,bh->b_data,vol->clustersize);
		brelse(bh);
		PRINTK(KERN_DEBUG "Read cluster %x\n",vol->mft_cluster+i);
	}

	/* fixup MFT record 0 */
	if(!ntfs_check_mft_record(vol,vol->mft)){
		printk(KERN_DEBUG "Invalid MFT record 0\n");
		sb->s_dev=0;
		unlock_super(sb);
		MOD_DEC_USE_COUNT;
		return NULL;
	}
	sb->s_op = &ntfs_super_operations;
	sb->s_magic = NTFS_SUPER_MAGIC;
	sb->s_flags |= MS_RDONLY;
	
	PRINTK(KERN_DEBUG "Reading master file\n");
	ntfs_load_special_files(vol);

	PRINTK(KERN_DEBUG "Getting Upcase\n");
	/* get $UpCase */
	{
		struct inode * upcase;
		upcase = iget(sb, FILE_UPCASE);
		ntfs_init_upcase((ntfs_inode*)upcase->u.generic_ip);
		iput(upcase);
	}
	unlock_super(sb);

	PRINTK(KERN_DEBUG "Getting RootDir\n");
	/* get the root directory */
	if(!(sb->s_mounted=iget(sb,FILE_ROOT))){
		sb->s_dev=0;
		printk(KERN_DEBUG "Could not get root dir inode\n");
		MOD_DEC_USE_COUNT;
		return NULL;
	}
	PRINTK(KERN_DEBUG "read_super: done\n");
	return sb;
}

static void ntfs_put_super(struct super_block *sb)
{
	ntfs_volume *vol=NTFS_SB2VOL(sb);
	PRINTK(KERN_DEBUG "ntfs_put_super\n");
	lock_super(sb);
	if(vol->mft_ino){
		ntfs_clear_inode(vol->mft_ino);
		ntfs_free(vol->mft_ino);
		vol->mft_ino=0;
	}
	sb->s_dev=0;
	ntfs_free(vol->mft);
	ntfs_free(vol->upcase);
	ntfs_free(sb->u.generic_sbp);
	sb->u.generic_sbp=0;
	unlock_super(sb);
	MOD_DEC_USE_COUNT;
}

static int ntfs_remount_fs(struct super_block *sb, int* flags, char* options)
{
	if(parse_options((ntfs_volume*)sb->u.generic_sbp, options))
		return 0;
	else
		return -EINVAL;
}

static int ntfs_lookup(struct inode *dir, const char *name, int len,
	struct inode **result)
{
	struct inode *res=0;
	unsigned long ino;
	char *item=0;
	PRINTK((KERN_DEBUG "Looking up %s in %x\n",name,(unsigned)dir->i_ino));
	if(strncmp(name,".",len)==0)
	{	*result=dir;
		return 0;
	}
	if(dcache_lookup(dir,name,len,&ino))
	{
		if(!(*result = iget(dir->i_sb,ino)))
		{
			iput(dir);
			return -EACCES;
		}
		iput(dir);
		return 0;
	}
	if(strncmp(name,"..",len)==0)
	{
		/*printk(KERN_DEBUG ".. not supported\n");*/
		/* What if a directory has more than one name? */
		/* FIXME: support for segments */
		ntfs_attribute *attr=ntfs_find_attr((ntfs_inode*)dir->u.generic_ip,
			AT_FILE_NAME,NULL);
		if(!attr){
			printk(KERN_DEBUG ".. not found\n");
			iput(dir);
			return -ENOENT;
		}
		if(!(*result = iget(dir->i_sb,*(int*)(attr->d.data))))
		{
			iput(dir);
			return -EACCES;
		}
		iput(dir);
		return 0;
	}
	item=ntfs_malloc(ITEM_SIZE);
	/* ntfs_getdir will place the directory entry into item,
	   and the first long long is the MFT record number */
	if(ntfs_getdir_byname((ntfs_inode*)dir->u.generic_ip,name,len,item))
	{
		res=iget(dir->i_sb,*(int*)item);
		dcache_add(dir, name, len, *(int*)item);
	}
	iput(dir);
	*result=res;
	ntfs_free(item);
	return res?0:-ENOENT;
}

static int ntfs_bmap(struct inode *ino,int block)
{
	int ret=ntfs_vcn_to_lcn((ntfs_inode*)ino->u.generic_ip,block);
	PRINTK((KERN_DEBUG "bmap of %x,block %x is %x\n",
			ino->i_ino,block,ret));
	return (ret==-1) ? 0:ret;
}

struct file_operations ntfs_file_operations = {
	NULL, /* lseek */
	ntfs_read,
	ntfs_write,
	NULL, /* readdir */
	NULL, /* select */
	NULL, /* ioctl */
#if LINUX_VERSION_CODE > (1*65536 + 3*256 + 49)
	generic_file_mmap,
#else
	generic_mmap,
#endif
	NULL, /* open */
	NULL, /* release */
	NULL, /* fsync */
	NULL, /* fasync */
	NULL, /* check_media_change */
	NULL  /* revalidate */
};

struct inode_operations ntfs_inode_operations = {
	&ntfs_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 */
#if LINUX_VERSION_CODE>(1*65536+3*256+49)
	NULL, /* readpage */
	NULL, /* writepage */
#endif
	ntfs_bmap,
	NULL, /* truncate */
	NULL, /* permission */
	NULL, /* smap */
};

struct file_operations ntfs_file_operations_nommap = {
	NULL, /* lseek */
	ntfs_read,
	ntfs_write,
	NULL, /* readdir */
	NULL, /* select */
	NULL, /* ioctl */
	NULL, /* mmap */
	NULL, /* open */
	NULL, /* release */
	NULL, /* fsync */
	NULL, /* fasync */
	NULL, /* check_media_change */
	NULL  /* revalidate */
};

struct inode_operations ntfs_inode_operations_nobmap = {
	&ntfs_file_operations_nommap,
	NULL, /* create */
	NULL, /* lookup */
	NULL, /* link */
	NULL, /* unlink */
	NULL, /* symlink */
	NULL, /* mkdir */
	NULL, /* rmdir */
	NULL, /* mknod */
	NULL, /* rename */
	NULL, /* readlink */
	NULL, /* follow_link */
#if LINUX_VERSION_CODE>(1*65536+3*256+49)
	NULL, /* readpage */
	NULL, /* writepage */
#endif
	NULL, /* bmap */
	NULL, /* truncate */
	NULL, /* permission */
	NULL, /* smap */
};

struct file_operations ntfs_dir_operations = {
	NULL, /* lseek */
	NULL, /* read */
	NULL, /* write */
	ntfs_readdir, /* readdir */
	NULL, /* select */
	NULL, /* ioctl */
	NULL, /* mmap */
	NULL, /* open */
	NULL, /* release */
	NULL, /* fsync */
	NULL, /* fasync */
	NULL, /* check_media_change */
	NULL  /* revalidate */
};

struct inode_operations ntfs_dir_inode_operations = {
	&ntfs_dir_operations,
	NULL, /* create */
	ntfs_lookup, /* lookup */
	NULL, /* link */
	NULL, /* unlink */
	NULL, /* symlink */
	NULL, /* mkdir */
	NULL, /* rmdir */
	NULL, /* mknod */
	NULL, /* rename */
	NULL, /* readlink */
	NULL, /* follow_link */
#if LINUX_VERSION_CODE>(1*65536+3*256+49)
	NULL, /* readpage */
	NULL, /* writepage */
#endif
	NULL, /* bmap */
	NULL, /* truncate */
	NULL, /* permission */
	NULL, /* smap */
};

struct super_operations ntfs_super_operations = {
	ntfs_read_inode,
	NULL, /* notify_change */
	NULL, /* write_inode */
	ntfs_put_inode,
	ntfs_put_super,
	NULL, /* write_super */
	ntfs_statfs,
	ntfs_remount_fs, /* remount */
};

/* Define SECOND if you cannot unload ntfs, and want to avoid rebooting
   for just one more test */
struct file_system_type ntfs_fs_type = {
	ntfs_read_super,
#ifndef SECOND
	"ntfs",
#else
	"ntfs2",
#endif
	1,
	NULL
};

#ifdef MODULE

/* In 1.3.38, this became part of module.h */
#if LINUX_VERSION_CODE<(1*65536+3*256+38)
char kernel_version[] = UTS_RELEASE;
#endif

int init_module(void)
{
	/* Uncomment this if you don't trust klogd. There are reasons
	   not to trust it. */
	/*extern int console_loglevel;
	console_loglevel=15;*/
	PRINTK((KERN_DEBUG "registering %s\n",ntfs_fs_type.name));
	register_filesystem(&ntfs_fs_type);
	return 0;
}

void cleanup_module(void)
{
	PRINTK((KERN_DEBUG "unregistering %s\n",ntfs_fs_type.name));
	unregister_filesystem(&ntfs_fs_type);
}
#endif

/* These functions allow the rest of the ntfs code some generic interface
   to the operating system */
void ntfs_error(char *s,int a1,int a2,int a3,int a4,int a5)
{
	char buf[210];
	strcpy(buf,KERN_ERR);
	strncat(buf,s,200);
	printk(buf,a1,a2,a3,a4,a5);
}

void ntfs_debug(char *s,int a1,int a2,int a3,int a4,int a5)
{
#ifdef DEBUG
	char buf[210];
	strcpy(buf,KERN_DEBUG);
	strncat(buf,s,200);
	printk(buf,a1,a2,a3,a4,a5);
#endif
}

void *ntfs_malloc(int size)
{
	void *ret=kmalloc(size,GFP_KERNEL);
	PRINTK((KERN_DEBUG "Allocating %x at %x\n",size,(unsigned)ret));
	return ret;
}

void ntfs_free(void *block)
{
	PRINTK((KERN_DEBUG "Releasing memory at %x\n",(unsigned)block));
	kfree(block);
}

int ntfs_getput_clusters(ntfs_volume *vol,int cluster,size_t start_offs,
	size_t length,void *buf, copyfunction pmemcpy, int do_read)
{
	struct super_block *sb=NTFS_SB(vol);
	struct buffer_head *bh;
	size_t to_copy;
	if(do_read)
		PRINTK((KERN_DEBUG "get_clusters %d %d %d\n",cluster,start_offs,length));
	else
		PRINTK((KERN_DEBUG "put_clusters %d %d %d\n",cluster,start_offs,length));
	while(length)
	{
		if(!(bh=bread(sb->s_dev,cluster,vol->clustersize)))
		{
			printk(KERN_DEBUG "%s failed\n", do_read?"Reading":"Writing");
			return 0;
		}
		to_copy=min(vol->clustersize-start_offs,length);
		lock_buffer(bh);
		if(do_read)
			pmemcpy(buf,bh->b_data+start_offs,to_copy);
		else
		{
			pmemcpy(bh->b_data+start_offs,buf,to_copy);
			mark_buffer_dirty(bh,1);
		}
		unlock_buffer(bh);
		length-=to_copy;
		buf+=to_copy;
		start_offs=0;
		cluster++;
		brelse(bh);
	}
	return 1;
}

int ntfs_read_mft_record(ntfs_volume *vol,int mftno,char *buf)
{
	PRINTK((KERN_DEBUG "read_mft_record %x\n",mftno));
	if(mftno==FILE_MFT)
	{
		memcpy(buf,vol->mft,vol->mft_recordsize);
		return 0;
	}
	if(!vol->mft_ino)
	{
		printk("ntfs:something is terribly wrong here\n");
		return -1;
	}
	PRINTK((KERN_DEBUG "read_mft_record: finished init %x\n",mftno));
	if(ntfs_read_attr(vol->mft_ino,AT_DATA,NULL,mftno*vol->mft_recordsize,
		buf,vol->mft_recordsize,ntfs_memcpy)!=vol->mft_recordsize)
	{
		PRINTK((KERN_DEBUG "read_mft_record: read %x failed\n",mftno));
		return -1;
	}
	PRINTK((KERN_DEBUG "read_mft_record: finished read %x\n",mftno));
	if(!ntfs_check_mft_record(vol,buf))
	{
		printk("Invalid MFT record for %x\n",mftno);
		return -1;
	}
	PRINTK((KERN_DEBUG "read_mft_record: Done %x\n",mftno));
	return 0;
}
