/* vim: set sw=4 :*/
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 1999  Free Software Foundation, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Samuel Leo <samuel@_.remove.me._szonline.net>
 * Limitations:
 * 1. Only 32 bit size support
 * 2. don't support >1k MFT record size, >16k INDEX record size
 * 3. don't support recursive at_attribute_list
 * 4. don't support compressed attribute other than Datastream
 * 5. all MFT's at_attribute_list must resident at first run list
 * 6. don't support journaling
 * 7. don't support EFS encryption
 * 8. don't support mount point and junction
 */
#ifdef FSYS_NTFS

//#define DEBUG_NTFS 1

/*
#define NO_ATTRIBUTE_LIST 1
   totally disable at_attribute_list support,
   if no compressed/fragment file and MFT,
   not recommended
#define NO_NON_RESIDENT_ATTRIBUTE_LIST 1
   disable non-resident at_attribute_list support,
   if no huge compressed/fragment file and MFT
#define NO_NTFS_DECOMPRESSION 1
   disable ntfs compressed file support
#define NO_ALTERNATE_DATASTREAM 1
   disable ntfs alternate datastream support
*/
/*#ifdef STAGE1_5*/

#include <shared.h>
#include <filesys.h>
#include "ntfs.h"

/* safe turn off non-resident attribute list if MFT fragments < 4000 */
//#define NO_NON_RESIDENT_ATTRIBUTE_LIST 1
#define NO_NTFS_DECOMPRESSION 1
#endif


#define MAX_MFT_RECORD_SIZE 1024
#define MAX_INDEX_RECORD_SIZE 16384
#define MAX_INDEX_BITMAP_SIZE 4096
#define DECOMP_DEST_BUFFER_SIZE 16384
#define DECOMP_SOURCE_BUFFER_SIZE (8192+2)
#define MAX_DIR_DEPTH 64

/* sizes are always in bytes, BLOCK values are always in DEV_BSIZE (sectors) */
#define DEV_BSIZE 512

/* include/linux/fs.h */
#define BLOCK_SIZE 	512

#define WHICH_SUPER 1
#define SBLOCK (WHICH_SUPER * BLOCK_SIZE / DEV_BSIZE)	/* = 2 */

/* include/asm-i386/type.h */
//typedef __signed__ char __s8;
//typedef unsigned char __u8;
//typedef __signed__ short __s16;
//typedef unsigned short __u16;
//typedef __signed__ int __s32;
//typedef unsigned int __u32;
//typedef __signed__ long long __s64;
//typedef unsigned long long __u64;

#define FILE_MFT      0
#define FILE_MFTMIRR  1
#define FILE_LOGFILE  2
#define FILE_VOLUME   3
#define FILE_ATTRDEF  4
#define FILE_ROOT     5
#define FILE_BITMAP   6
#define FILE_BOOT     7
#define FILE_BADCLUS  8
#define FILE_QUOTA    9
#define FILE_UPCASE  10

#define at_standard_information 0x10
#define at_attribute_list 	0x20
#define at_filename		0x30
#define at_security_descriptor	0x50
#define at_data			0x80
#define at_index_root		0x90
#define at_index_allocation	0xa0
#define at_bitmap		0xb0
#define at_symlink		0xc0

#define NONAME	""
#define ATTR_NORMAL	0
#define ATTR_COMPRESSED	1
#define ATTR_RESIDENT	2
#define ATTR_ENCRYPTED	16384
#define ATTR_SPARSE	32768



#define index_data	((char *)FSYS_BUF)
#define bitmap_data	((unsigned char *)(FSYS_BUF+MAX_INDEX_RECORD_SIZE))
#define dcdbuf	((unsigned char *)index_data)
#define dcsbuf	(bitmap_data)
#define dcend	(dcsbuf+DECOMP_SOURCE_BUFFER_SIZE)
#define fnbuf ((char *)(bitmap_data+MAX_INDEX_BITMAP_SIZE))
#define mmft	((MFTR *)dcend)
#define cmft	((MFTR *)(dcend+sizeof(MFTR)))
#define mft_run	((RUNL *)(dcend+2*sizeof(MFTR)))
#define path_ino ((unsigned long *)(dcend+2*sizeof(MFTR)+sizeof(RUNL)))
#define cluster16 (path_ino+MAX_DIR_DEPTH)
#define index16 cluster16[16]
#define blocksize cluster16[17]
#define clustersize cluster16[18]
#define mft_record_size cluster16[19]
#define index_record_size cluster16[20]
#define dcvcn cluster16[21]
#define dcoff cluster16[22]
#define dclen cluster16[23]
#define dcrem cluster16[24]
#define dcslen cluster16[25]
#define dcsptr ((unsigned char *)cluster16[26])
#define is_ads_completion cluster16[27]

typedef struct run_list {
	char *start;
	char *ptr;
	unsigned long svcn;
	unsigned long evcn;
	unsigned long vcn;
	unsigned long cnum0;
	unsigned long cnum;
	unsigned long clen;
} RUNL;

typedef struct ntfs_mft_record {
	char mft[MAX_MFT_RECORD_SIZE];
	char mft2[MAX_MFT_RECORD_SIZE];
	unsigned long attr_type;
	char *attr_name;
	unsigned long attr_flag;
	unsigned long attr_size;
	char *attr;
	unsigned long attr_len;
	RUNL runl;
	char *attr_list;
	unsigned long attr_list_len;
	unsigned long attr_list_size;
	unsigned long attr_list_off;
	unsigned long attr_inited;
	char attr_list_buf[2*BLOCK_SIZE];
	RUNL attr_list_runl;
} MFTR;

static int read_mft_record(unsigned long mftno, char *mft, unsigned long self);
static unsigned long read_attribute(MFTR *mftr, unsigned long offset, char *buf, unsigned long len, RUNL *from_rl);
static int get_next_run(RUNL *runl);



#if 0
static inline int
nsubstring (char *s1, char *s2)
{
    while (tolower(*s1) == tolower(*s2))
    {
	/* The strings match exactly. */
	if (! *(s1++))
	    return 0;
	s2 ++;
    }

    /* S1 is a substring of S2. */
    if (*s1 == 0)
	return -1;

    /* S1 isn't a substring. */
    return 1;
}
#endif

static int
fixup_record (char *record, char *magic, unsigned long size)
{
    unsigned long start, count, offset;
    unsigned short fixup;

    if (*(int *)record != *(int *)magic)
	return 0;
    
    start = *(unsigned short *)(record + 4);
    count = *(unsigned short *)(record + 6);
    count--;
    
    if (size && blocksize * count != size)
	return 0;
    
    fixup = *(unsigned short *)(record + start);
    start += 2;
    offset = blocksize - 2;
    while (count--)
    {
	if(*(unsigned short *)(record + offset) != fixup)
	    return 0;
	
	*(unsigned short *)(record + offset) = *(unsigned short *)(record + start);
	
	start += 2;
	offset += blocksize;
    }
    return 1;
}

static void
rewind_run_list( RUNL *runl)
{
    runl->vcn = runl->svcn;
    runl->ptr = runl->start;
    runl->cnum0 = 0;
    runl->cnum = 0;
    runl->clen = 0;
}

static int
get_next_run (RUNL *runl)
{
    unsigned long t, n, v;

#ifdef DEBUG_NTFS
    printf("get_next_run: s=%d e=%d c=%d start=%x ptr=%x\n",
	   runl->svcn, runl->evcn, runl->vcn, runl->start, runl->ptr);
#endif

    runl->vcn += runl->clen;
    if (runl->vcn > runl->evcn)
    	return 0;

    t = *((runl->ptr)++);
    n = t & 0xf;
    runl->clen = 0;
    v = 1;
    
    while (n--)
    {
	runl->clen += v * (*(unsigned char *)((runl->ptr)++));
	v <<= 8;
    }
    
    n = (t >> 4) & 0xf;
    
    if (n == 0)
	runl->cnum = 0;
    else
    {
	unsigned long c = 0;
	v = 1;
	while(n--)
       	{
	    c += v * (*(unsigned char *)((runl->ptr)++));
	    v <<= 8;
	}
	
	if (c & (v>>1))
	    c -= v;
	runl->cnum0 += c;
	runl->cnum = runl->cnum0;
    }
#ifdef DEBUG_NTFS
    printf ("got_next_run: t=%x cluster %x len %x vcn=%x ecn=%x\n",
    	t, runl->cnum, runl->clen, runl->vcn, runl->evcn);
#endif
    return 1;
}

#ifndef NO_ATTRIBUTE_LIST
static void
init_run_list (char *attr, unsigned long len, RUNL *runl, unsigned long *initp)
{
    unsigned long allocated;
    /* int inited; */

    runl->svcn = *(unsigned long *)(attr + 0x10); /* only support 32 bit */
    runl->evcn = *(unsigned long *)(attr + 0x18); /* only support 32 bit */
    runl->start = attr + (*(unsigned short *)(attr + 0x20));
    allocated = *(unsigned long *)(attr + 0x28);
    
    if (initp)
       	*initp = *(unsigned long *)(attr + 0x38);
    
    if (! runl->evcn)
       	runl->evcn = (allocated - 1) / clustersize;
    
#ifdef DEBUG_NTFS
    printf("size %d allocated=%d inited=%d cegin=%x csize=%d vcn=%d-%d\n",
	    /*attr_size*/ *(unsigned long *)(attr+0x30),
	    /*allocated*/ *(unsigned long *)(attr+0x28),
	    /*attr_inited*/ *(unsigned long *)(attr+0x38),
	    /*cengin*/ *(unsigned short *)(attr+0x22),
	    /*csize*/ *(unsigned short *)(attr+0x40),
	    runl->svcn, runl->evcn);
#endif
    rewind_run_list(runl);
}
#endif


static int
find_attribute (char *mft, unsigned long type, char *name, char **attr, unsigned long *size, unsigned long *len, unsigned long *flag)
{
    unsigned long t, l, r, n, i, namelen;
    unsigned short *attr_name;

    n = strlen (name);
    r = mft_record_size - *(unsigned short *)(mft + 0x14);
    mft += *(unsigned short *)(mft + 0x14);
    while ((t = *(unsigned long *)mft) != -1)
    {
	l = *(unsigned long *)(mft+4);
	if (l > r)
	    break;
#ifdef DEBUG_NTFS
	printf("type = %x len = %d namelen=%d resident=%d compresed=%d attrno=%d\n",
		t, l,
		/*namelen*/ *(mft+9),
		//name = (unsigned short *)(mft + *(unsigned short *)(mft+10)),
		/*resident */ (*(mft+8) == 0),
		/*compressed*/ *(unsigned short *)(mft+12),
		/*attrno*/ *(unsigned short *)(mft+14));
#endif
	namelen = (*(mft + 9));
	if (t == type)
       	{
#ifndef STAGE1_5
#ifndef NO_ALTERNATE_DATASTREAM
	    if(is_ads_completion && type == at_data)
	    {
		if(namelen && namelen >= n &&
		   (! *(mft + 8)/*resident*/ || ! *(unsigned long *)(attr + 0x10)/*svcn==0*/))
		{
		    for (i = 0, attr_name = (unsigned short *)(mft + *(unsigned short *)(mft + 10)); i < n; i++)
			if (tolower(name[i]) != tolower(attr_name[i]))
			    break;
		    
		    if (i >= n)
		    {
			for(; i < namelen; i++)
			    name[i] = attr_name[i];
			
			name[i] = '\0';
			
			if (print_possibilities > 0)
			    print_possibilities = -print_possibilities;
			
			print_a_completion(fnbuf);
			
			name[n] = '\0';
		    }
		}
	    } else
#endif
#endif
	    if (namelen == n)
	    {
		for (i = 0, attr_name = (unsigned short *)(mft + *(unsigned short *)(mft + 10)); i < n; i++)
		    if(tolower(name[i]) != tolower(attr_name[i]))
			break;
		
		if (i >= n)
	       	{
		    if (flag)
			*flag = *(unsigned short *)(mft + 12);
		    
		    if (*(mft + 8) == 0)
		    {
			if (flag)
			    *flag |= ATTR_RESIDENT;
#ifdef DEBUG_NTFS
			printf("resident data at %x size %x indexed=%d\n",
			       /*data*/ *(unsigned short *)(mft+0x14),
			       /*attr_size*/ *(unsigned short *)(mft+0x10),
			       /*indexed*/ *(unsigned short *)(mft+0x16));
#endif
			if (attr)
			    *attr = mft + (*(unsigned short *)(mft + 0x14));
			
			if (size)
  			    *size = (*(unsigned short *)(mft + 0x10));
			
			if (len)
  			    *len = (*(unsigned short *)(mft + 0x10));
			
		    } else {
			
			if (attr)
  			    *attr = mft;
			
			if (size)
  			    *size = *(unsigned long *)(mft+0x30);
			
			if (len)
  			    *len = l;
		    }
		    
		    return 1;
		}
	    }
	}
	
	mft += l;
	r -= l;
    }
    return 0;
}

#ifndef NO_ATTRIBUTE_LIST
static unsigned long
get_next_attribute_list (MFTR *mftr, unsigned long *size)
{
    unsigned long l, t, mftno;
#ifdef DEBUG_NTFS
    printf("get_next_attribute_list: type=%x\n",mftr->attr_type);
#endif
again:
    while (mftr->attr_list_len > 0x14)
    {
	t = *(unsigned long *)(mftr->attr_list + 0);
	l = *(unsigned short *)(mftr->attr_list + 4);
#ifdef DEBUG_NTFS
	printf("attr_list type=%x len=%x remain=%x\n", t, l, mftr->attr_list_len);
#endif
	if(l == 0 || l > mftr->attr_list_len)
	    return 0;
	
	mftno = *(unsigned long *)(mftr->attr_list + 0x10);
	mftr->attr_list_len -= l;
	mftr->attr_list += l;
	
	if(t == mftr->attr_type)
	{
#ifdef DEBUG_NTFS
	printf("attr_list mftno=%x\n", mftno);
#endif
	    if (read_mft_record (mftno, mftr->mft2, (mftr == mmft)) == 0)
		break;
	    
	    if (find_attribute(mftr->mft2, mftr->attr_type, mftr->attr_name,
			&mftr->attr, size, &mftr->attr_len, &mftr->attr_flag))
		return 1;
	}
    }
#ifndef NO_NON_RESIDENT_ATTRIBUTE_LIST
    if (mftr->attr_list_off < mftr->attr_list_size)
    {
	unsigned long len = mftr->attr_list_size - mftr->attr_list_off;
	
	if (len > BLOCK_SIZE)
	    len = BLOCK_SIZE;

	if (mftr->attr_list_len)
	    memmove (mftr->attr_list_buf, mftr->attr_list, mftr->attr_list_len);
	
	mftr->attr_list = mftr->attr_list_buf;

	if (read_attribute (NULL, mftr->attr_list_off,
			mftr->attr_list_buf + mftr->attr_list_len,
			len, &mftr->attr_list_runl) != len)
	{
#ifdef DEBUG_NTFS
	    printf("CORRUPT NON-RESIDENT ATTRIBUTE_LIST\n");
#endif
	    /* corrupt */
	    errnum = ERR_FSYS_CORRUPT;
	    mftr->attr_list_size = 0;
	    mftr->attr_len = 0;
	    mftr->attr_list = NULL;
	    return 0;
	}

	mftr->attr_list_len += len;
	mftr->attr_list_off += len;
	goto again;
    }
#endif
    mftr->attr_list = NULL;
    return 0;
}
#endif

static int
search_attribute (MFTR *mftr, unsigned long type, char *name)
{
#ifdef DEBUG_NTFS
    printf("searching attribute %x <%s>\n", type, name);
#endif 

    mftr->attr_type = type;
    mftr->attr_name = name;
    mftr->attr_list = NULL;
    mftr->attr_list_len = 0;
    mftr->attr_list_size = 0;
    mftr->attr_list_off = 0;
    dcrem = dclen = 0;

#ifndef NO_ATTRIBUTE_LIST
    if (find_attribute (mftr->mft, at_attribute_list, NONAME,
		      &mftr->attr_list, &mftr->attr_list_size,
		      &mftr->attr_list_len, &mftr->attr_list_off))
    {
	if (mftr->attr_list_off&ATTR_RESIDENT)
       	{
	    /* resident at_attribute_list */
	    mftr->attr_list_size = 0;
#ifdef DEBUG_NTFS
	    printf("resident attribute_list len=%x\n", mftr->attr_list_len);
#endif
	} else {
#ifdef DEBUG_NTFS
	    printf("non-resident attribute_list len=%x size=%x\n",
		   mftr->attr_list_len, mftr->attr_list_size);
#endif
#ifndef NO_NON_RESIDENT_ATTRIBUTE_LIST
	    init_run_list(mftr->attr_list, mftr->attr_list_len, &mftr->attr_list_runl, NULL);
	    if (get_next_run (&mftr->attr_list_runl) == 0 || mftr->attr_list_runl.cnum == 0)
		mftr->attr_list_size = 0;
#endif
	    mftr->attr_list = NULL;
	    mftr->attr_list_len = 0;
	}
    }
#endif

    if (find_attribute (mftr->mft, type, name, &mftr->attr, &mftr->attr_size, &mftr->attr_len, &mftr->attr_flag)
#ifndef NO_ATTRIBUTE_LIST
       || get_next_attribute_list (mftr, &mftr->attr_size)
#endif
       )
    {
#ifndef NO_ATTRIBUTE_LIST
	if (! (mftr->attr_flag & ATTR_RESIDENT))
	{
	    init_run_list (mftr->attr, mftr->attr_len, &mftr->runl, (unsigned long *)&mftr->attr_inited);
	    
	    if (mftr->attr_inited > mftr->attr_size)
	    	mftr->attr_inited = mftr->attr_size;
	    
	    if (get_next_run (&mftr->runl) == 0)
	    {
		mftr->attr_flag |= ATTR_RESIDENT;
		mftr->attr_len = 0;
	    }
	} else
	    mftr->attr_inited = mftr->attr_size;
#endif

	return 1;
    }

    mftr->attr_type = 0;
    return 0;
}

static int
get_run (RUNL *rl, unsigned long vcn, unsigned long *clp, unsigned long *lenp)
{
    if (rl->evcn < vcn)
	return 0;

    if (rl->vcn > vcn)
    {
    	rewind_run_list(rl);
	get_next_run(rl);
    }

    while (rl->vcn + rl->clen <= vcn)
    {
	if (get_next_run (rl) == 0)
	    return 0;
    }

    if (clp)
       	*clp = rl->cnum == 0 ? 0 : rl->cnum + vcn - rl->vcn;
    
    if (lenp)
       	*lenp = rl->clen - vcn + rl->vcn;
    
    return 1;
}

static int
search_run (MFTR *mftr, unsigned long vcn)
{
    if (mftr->attr == NULL && ! search_attribute (mftr, mftr->attr_type, mftr->attr_name))
	return 0;

    if (mftr->runl.svcn > vcn)
	search_attribute (mftr, mftr->attr_type, mftr->attr_name);

#ifdef NO_ATTRIBUTE_LIST
    if (mftr->runl.evcn < vcn)
	return 0;
#else
    while (mftr->runl.evcn < vcn)
    {
	if (get_next_attribute_list (mftr, NULL) == 0)
       	{
	    mftr->attr = NULL;
	    return 0;
	}
	
	init_run_list (mftr->attr, mftr->attr_len, &mftr->runl, NULL);
	if (get_next_run (&mftr->runl) == 0)
       	{
	    mftr->attr = NULL;
	    return 0;
	}
    }
#endif

    return 1;
}

static unsigned long
read_attribute (MFTR *mftr, unsigned long offset, char *buf, unsigned long len, RUNL *from_rl)
{
    unsigned long vcn;
    unsigned long cnum, clen;
    unsigned long done = 0;
    unsigned long n;
    RUNL *rl;

    if (! from_rl && (mftr->attr_flag & ATTR_RESIDENT))
    {
	/* resident attribute */
	if (offset > mftr->attr_len)
	    return 0;
	
	if (offset + len > mftr->attr_len)
	    len = mftr->attr_len - offset;
	
	memmove (buf, mftr->attr + offset, len);
	return len;
    }

    vcn = offset / clustersize;
    offset %= clustersize;

    while (len > 0)
    {
	if (from_rl)
	    rl = from_rl;
	else if (search_run (mftr, vcn) == 0)
	    break;
	else
	    rl = &mftr->runl;
	
	if (get_run (rl, vcn, &cnum, &clen) == 0)
	    break;
	
	if (cnum == 0 && from_rl)
	    break;
	
	n = clen * clustersize - offset;
	
	if (n > len)
	    n = len;
	if (cnum == 0)
       	{
	    memset (buf, 0, n);
	} else if (! devread (cnum * (clustersize >> 9) + (offset >> 9), offset & 0x1ff, n, buf))
	    break;

	buf += n;
	vcn += (offset + n) / clustersize;
	done += n;
	offset = 0;
	len -= n;	/* len always >= 0 */
    }
    
    return done;
}

static int read_mft_record (unsigned long mftno, char *mft, unsigned long self)
{
#ifdef DEBUG_NTFS
    printf("Reading MFT record: mftno=%d\n", mftno);
#endif
    if (read_attribute (mmft, mftno * mft_record_size,
	    mft, mft_record_size, self ? mft_run : NULL) != mft_record_size)
	return 0;
    
    if (! fixup_record (mft, "FILE", mft_record_size))
	return 0;
    
    return 1;
}

#ifndef NO_NTFS_DECOMPRESSION
static unsigned long get_16_cluster (MFTR *mftr, unsigned long vcn)
{
    unsigned long n = 0, cnum, clen;

    while (n < 16 && search_run (mftr, vcn) && get_run (&mftr->runl, vcn, &cnum, &clen) && cnum)
    {
	if (clen > 16 - n)
	    clen = 16 - n;
	
	vcn += clen;
	
	while (clen--)
	    cluster16[n++] = cnum++;
    }
    
    cluster16[n] = 0;
    return n;
}

static inline unsigned long compressed_block_size (unsigned char *src)
{
    return 3 + (*(unsigned short *)src & 0xfff);
}

static unsigned long decompress_block (unsigned char *dest, unsigned char *src)
{
    unsigned long head;
    unsigned long copied=0;
    unsigned char *last;
    unsigned long bits;
    unsigned long tag=0;

    /* high bit indicates that compression was performed */
    if (! (*(unsigned short *)src & 0x8000))
    {
	memmove (dest, src + 2, 0x1000);
	return 0x1000;
    }

    if ((head = *(unsigned short *)src & 0xFFF) == 0)
	/* block is not used */
	return 0;

    src += 2;
    last = src + head;
    bits = 0;

    while (src <= last)
    {
	if (copied > 4096)
	{
#ifdef DEBUG_NTFS
	    printf("decompress error 1\n");
#endif
	    errnum = ERR_FSYS_CORRUPT;
	    return 0;
	}
	if (! bits)
	{
	    tag = *(unsigned char *)src;
	    bits = 8;
	    src++;
	    if (src > last)
		break;
	}
	if (tag & 1)
	{
	    unsigned long i, len, delta, code, lmask, dshift;
	    code = *(unsigned short *)src;
	    src += 2;
	    if (! copied)
	    {
#ifdef DEBUG_NTFS
		printf("decompress error 2\n");
#endif
		errnum = ERR_FSYS_CORRUPT;
		return 0;
	    }
	    
	    for (i = copied - 1, lmask = 0xFFF, dshift = 12; i >= 0x10; i >>= 1)
	    {
		lmask >>= 1;
		dshift--;
	    }
	    
	    delta = code >> dshift;
	    len = (code & lmask) + 3;
	    
	    for (i = 0; i < len; i++)
	    {
		dest[copied] = dest[copied - delta - 1];
		copied++;
	    }
	} else
	    dest[copied++] = *(unsigned char *)src++;
	tag >>= 1;
	bits--;
    }

    return copied;
}
#endif

unsigned long
ntfs_read(char *buf, unsigned long len)
{
    unsigned long ret;
#ifdef STAGE1_5
/* stage2 can't be resident/compressed/encrypted files,
 * but does sparse flag, cause stage2 never sparsed
 */
    if ((cmft->attr_flag & ~ATTR_SPARSE) != ATTR_NORMAL)
	return 0;
    disk_read_func = disk_read_hook;
    ret = read_attribute (cmft, filepos, buf, len, 0);
    disk_read_func = NULL;
    filepos += ret;
#else

/*#ifndef NO_NTFS_DECOMPRESSION*/
    //int off;
    //int vcn;
    //int size;
    unsigned long len0 = 0;
/*endif*/

    if (len <= 0 || filepos >= cmft->attr_size || (cmft->attr_flag & ATTR_ENCRYPTED))
	return 0;

    if (filepos + len > cmft->attr_size)
	len = cmft->attr_size - filepos;
#if 0
    if(filepos >= cmft->attr_inited) {
#ifdef DEBUG_NTFS
printf("reading uninitialized data 1\n");
#endif
    	memset(buf, 0, len);
	return len;
    } else if(filepos+len > cmft->attr_inited) {
    	len0 = len;
	len = cmft->attr_inited - filepos;
	len0 -= len;
    } else
    	len0 = 0;
#endif
#ifdef DEBUG_NTFS
printf("read filepos=%x filemax=%x inited=%x len=%x len0=%x\n",filepos,filemax,cmft->attr_inited,len,len0);
#endif

    if ((cmft->attr_flag & (ATTR_COMPRESSED | ATTR_RESIDENT)) != ATTR_COMPRESSED)
    {
	if (cmft->attr_flag == ATTR_NORMAL)
	//if((cmft->attr_flag | ATTR_RESIDENT) == ATTR_RESIDENT)
	    disk_read_func = disk_read_hook;

	ret = read_attribute (cmft, filepos, buf, len, 0);

	if (cmft->attr_flag == ATTR_NORMAL)
	//if((cmft->attr_flag | ATTR_RESIDENT) == ATTR_RESIDENT)
	    disk_read_func = NULL;

	filepos += ret;

	if(ret == len && len0)
       	{
		memset (buf + len, 0, len0);
		filepos += len0;
		ret += len0;
	}
	return ret;
    }

    ret = 0;

#ifndef NO_NTFS_DECOMPRESSION
    /* NTFS don't support compression if cluster size > 4k */
    if (clustersize > 4096)
    {
	errnum = ERR_FSYS_CORRUPT;
	return 0;
    }

    while (len > 0)
    {
#ifdef DEBUG_NTFS
printf("Reading filepos=%x len=%x\n", filepos, len);
#endif
	if (filepos >= dcoff && filepos < (dcoff + dclen))
       	{
#ifdef DEBUG_NTFS
printf("decompress cache %x+%x\n", dcoff, dclen);
#endif
	    size = dcoff + dclen - filepos;
	    
	    if (size > len)
	       	size = len;
	    
	    memmove (buf, dcdbuf + filepos - dcoff, size);
	    
	    filepos += size;
	    len -= size;	/* len always >= 0 */
	    ret += size;
	    buf += size;
	    
	    if (len == 0)
	    {
		if (len0)
	       	{
#ifdef DEBUG_NTFS
printf("reading uninitialized data 2\n");
#endif
		    memset (buf, 0, len0);
		    filepos += len0;
		    ret += len0;
		}
		
		return ret;
	    }
	}

	vcn = filepos / clustersize / 16;
	vcn *= 16;
	off = filepos % (16 * clustersize);
	
	if (dcvcn != vcn || filepos < dcoff)
	    dcrem = 0;

#ifdef DEBUG_NTFS
printf("vcn %x off %x dcrem %x\n", vcn, off, dcrem);
#endif
	if (dcrem)
       	{
	    unsigned long head;

	    /* reading source */
	    if (dcslen < 2 || compressed_block_size (dcsptr) > dcslen)
	    {
		if (cluster16[index16] == 0)
	       	{
		    errnum = ERR_FSYS_CORRUPT;
		    return ret;
		}
	
		if (dcslen)
		    memmove (dcsbuf, dcsptr, dcslen);
		
		dcsptr = dcsbuf;
		
		while ((dcslen + clustersize) < DECOMP_SOURCE_BUFFER_SIZE)
	       	{
		    if (cluster16[index16] == 0)
			break;
#ifdef DEBUG_NTFS
printf("reading dcslen=%x cluster %x\n", dcslen, cluster16[index16]);
#endif
		    if (! devread (cluster16[index16] * (clustersize >> 9), 0, clustersize, dcsbuf + dcslen))
			return ret;
		    dcslen += clustersize;
		    index16++;
		}
	    }
	    /* flush destination */
	    dcoff += dclen;
	    dclen = 0;

	    while (dcrem && dclen < DECOMP_DEST_BUFFER_SIZE &&
		  dcslen >= 2 && (head = compressed_block_size (dcsptr)) <= dcslen)
	    {
		size = decompress_block (dcdbuf + dclen, dcsptr);
		if (dcrem >= 0x1000 && size != 0x1000)
	       	{
		    errnum = ERR_FSYS_CORRUPT;
		    return ret;
		}
		dcrem -= size;
		dclen += size;
		dcsptr += head;
		dcslen -= head;
	    }
	    continue;
	}
	
	dclen = dcrem = 0;
#ifdef DEBUG_NTFS
printf("get next 16 clusters\n");
#endif
	switch (get_16_cluster (cmft, vcn))
       	{
	case 0:
#ifdef DEBUG_NTFS
printf("sparse\n");
#endif
	    /* sparse */
	    size = 16 * clustersize - off;
	    if (size > len)
		size = len;
#ifndef STAGE1_5
	    memset (buf, 0, size);
#endif
	    filepos += size;
	    len -= size;	/* len always >= 0 */
	    ret += size;
	    buf += size;
	    break;

	case 16:
#ifdef DEBUG_NTFS
printf("uncompressed\n");
#endif
	    /* uncompressed */
	    index16 = off / clustersize;
	    off %= clustersize;
	    
	    while (index16 < 16)
	    {
		size = clustersize - off;
		
		if (size > len)
		    size = len;
		
		if (! devread (cluster16[index16] * (clustersize >> 9) + (off >> 9), off & 0x1ff, size, buf))
		    return ret;
		filepos += size;
		len -= size;	/* len always >= 0 */
		ret += size;
		
		if (len == 0)
		    return ret;
		
		off = 0;
		buf += size;
		index16++;
	    }
	    
	    break;

	default:
#ifdef DEBUG_NTFS
printf("compressed\n");
#endif
	    index16 = 0;
	    dcvcn = vcn;
	    dcoff = vcn * clustersize;
	    dcrem = cmft->attr_inited - dcoff;
	    if (dcrem > 16 * clustersize)
		dcrem = 16 * clustersize;
	    dcsptr = dcsbuf;
	    dcslen = 0;
	}
    }
    if (len0)
    {
#ifdef DEBUG_NTFS
printf("reading uninitialized data 3\n");
#endif
	memset (buf, 0, len0);
	filepos += len0;
	ret += len0;
    }
#else
    errnum = ERR_FSYS_CORRUPT;   
#endif /*NO_NTFS_DECOMPRESSION*/
#endif /*STAGE1_5*/
    return ret;
}

int ntfs_mount (void)
{
#define sb ((char *)FSYS_BUF)
    unsigned long mft_record;
    unsigned long spc;		/* sectors per cluster */

  if (((current_drive & 0x80) || (current_slice != 0))
       && (current_slice != /*PC_SLICE_TYPE_NTFS*/7)
       && (current_slice != /*PC_SLICE_TYPE_NTFS*/0x17))
      return 0;

    if (! devread (0, 0, 512, (char *) FSYS_BUF))
	return 0;			/* Cannot read superblock */

    //if (sb[3] != 'N' || sb[4] != 'T' || sb[5] != 'F' || sb[6] != 'S')
    if (*(long *)(sb + 3) != 0x5346544E)	/* "NTFS" */
	return 0;
    blocksize = *(unsigned short *)(sb + 0xb);
    spc = *(unsigned char *)(sb + 0xd);
    clustersize = spc * blocksize;
    mft_record_size = (long)(*(char *)(sb + 0x40));	/* XXX: signed value */
    index_record_size = *(char *)(sb + 0x44);	/* XXX */
    if (((long)mft_record_size) > 0)
	mft_record_size *= clustersize;
    else if (((long)mft_record_size) < 0)
	mft_record_size = (1 << (-mft_record_size));
    else
	return 0;
    
    index_record_size *= clustersize;
    mft_record = *(unsigned long *)(sb + 0x30); /* only support 32 bit */
    spc = clustersize >> 9;	/* clustersize / 512 */

    if (mft_record_size > MAX_MFT_RECORD_SIZE || index_record_size > MAX_INDEX_RECORD_SIZE)
    {
	/* only support 1k MFT record, 4k INDEX record */
	return 0;
    }

#ifdef DEBUG_NTFS
    printf("spc=%x mft_record=%x:%x\n", spc, *(long long *)(sb+0x30));
#endif

    if (! devread (mft_record * spc, 0, mft_record_size, mmft->mft))
	return 0;			/* Cannot read superblock */

    if (! fixup_record (mmft->mft, "FILE", mft_record_size))
	return 0;

#ifndef NO_ALTERNATE_DATASTREAM
    is_ads_completion = 0;
#endif
    if (! search_attribute (mmft, at_data, NONAME))
       	return 0;

    *mft_run = mmft->runl;

    *path_ino = FILE_ROOT;

    return 1;
#undef sb
}

int
ntfs_dir (char *dirname)
{
    char *rest, ch;
    unsigned long namelen;
    unsigned long depth = 0;
    unsigned long flag = 0;
    unsigned long record_offset;
    unsigned long my_index_record_size;
    unsigned char *index_entry = 0, *entry, *index_end;
    unsigned long i;
    int chk_sfn = 1;

    /* main loop to find desired directory entry */
loop:

#ifdef DEBUG_NTFS
    printf("dirname=%s\n", dirname);
#endif
    if (! read_mft_record (path_ino[depth], cmft->mft, 0))
    {
#ifdef DEBUG_NTFS
	printf("MFT error 1\n");
#endif
	errnum = ERR_FSYS_CORRUPT;
	return 0;
    }

    /* if we have a real file (and we're not just printing possibilities),
       then this is where we want to exit */

    if (! *dirname || isspace (*dirname) || *dirname == ':')
    {
#ifndef STAGE1_5
#ifndef NO_ALTERNATE_DATASTREAM
	if (*dirname == ':' && print_possibilities)
       	{
	    char *tmp;

	    /* preparing ADS name completion */
	    for (tmp = dirname; *tmp != '/'; tmp--);
	    
	    for (tmp++, rest = fnbuf; *tmp && ! isspace (*tmp); *(rest++) = *(tmp++))
		if (*tmp == ':')
		    dirname = rest;
	    
	    *(rest++) = '\0';

	    is_ads_completion = 1;
	    search_attribute (cmft, at_data, dirname + 1);
	    is_ads_completion = 0;

	    if (errnum == 0)
	    {
		if (print_possibilities < 0)
		    return 1;
		errnum = ERR_FILE_NOT_FOUND;
	    }
	    return 0;
	}
#endif
#endif

	if (*dirname == ':')
	    dirname++;
	
	for (rest = dirname; (ch = *rest) && ! isspace (ch); rest++);
	
	*rest = 0;

#ifdef DEBUG_NTFS
	printf("got file: search at_data\n");
#endif

	if (! search_attribute (cmft, at_data, dirname))
       	{
	    errnum = ((*(dirname - 1) == ':') ? ERR_FILE_NOT_FOUND : ERR_BAD_FILETYPE);
	    *rest = ch;
	    return 0;
	}
	
	*rest = ch;

	filemax = cmft->attr_size;
#ifdef DEBUG_NTFS
	printf("filemax=%x\n", filemax);
#endif
	return 1;
    }

    if (depth >= (MAX_DIR_DEPTH - 1))
    {
	errnum = ERR_FSYS_CORRUPT;
	return 0;
    }

    /* continue with the file/directory name interpretation */

    while (*dirname == '/')
	dirname++;

    for (rest = dirname; (ch = *rest) && ! isspace (ch) && ch != '/' && ch != ':'; rest++);

    *rest = 0;

    if (! search_attribute (cmft, at_index_root, "$I30"))
    {
	errnum = ERR_BAD_FILETYPE;
	return 0;
    }

    read_attribute (cmft, 0, fnbuf, 16, 0);
    
    my_index_record_size = *(unsigned long *)(fnbuf + 8);

    if (my_index_record_size > MAX_INDEX_RECORD_SIZE)
    {
	errnum = ERR_FSYS_CORRUPT;
	return 0;
    }

#ifdef DEBUG_NTFS
    printf("index_record_size=%x\n", my_index_record_size);
#endif

    if (cmft->attr_size > MAX_INDEX_RECORD_SIZE)
    {
	errnum = ERR_FSYS_CORRUPT;
	return 0;
    }
    
    read_attribute (cmft, 0, index_data, cmft->attr_size, 0);
    
    index_end = (unsigned char *)(index_data + cmft->attr_size);
    index_entry = (unsigned char *)(index_data + 0x20);
    record_offset = -1;

//#ifndef STAGE1_5
//    if (print_possibilities && ch != '/' && ch != ':' && !*dirname)
//    {
//	print_possibilities = -print_possibilities;
//	/* fake '.' for empty directory */
//	print_a_completion (".");
//    }
//#endif

    if (search_attribute (cmft, at_bitmap, "$I30"))
    {
	if (cmft->attr_size > MAX_INDEX_BITMAP_SIZE)
       	{
	    errnum = ERR_FSYS_CORRUPT;
	    return 0;
	}

	read_attribute (cmft, 0, (char *)bitmap_data, cmft->attr_size, 0);

	if (search_attribute (cmft, at_index_allocation, "$I30") == 0)
       	{
	    errnum = ERR_FSYS_CORRUPT;
	    return 0;
	}

	for (record_offset = 0; record_offset * my_index_record_size < cmft->attr_size; record_offset++)
	{
	    unsigned long bit = (1 << (record_offset & 3));
	    unsigned long byte = (record_offset >> 3);
#ifdef DEBUG_NTFS
	    printf("record_offset=%x\n", record_offset);
#endif
	    if ((bitmap_data[byte] & bit))
		break;
	}

	if (record_offset * my_index_record_size >= cmft->attr_size)
	    record_offset = -1;
    }

    do
    {
	entry = index_entry;
       	index_entry += *(unsigned short *)(entry + 8);
	
	if (entry + 0x50 >= index_entry || entry >= index_end || index_entry >= index_end || (entry[0x12] & 2))
	{
	    if ((long)record_offset == -1 ||
	       ! read_attribute (cmft, record_offset * my_index_record_size, index_data, my_index_record_size, 0))
	    {
		if (errnum == 0)
		{
#ifndef STAGE1_5
		    if (print_possibilities < 0)
		    {
#if 0
			putchar ('\n');
#endif
			return 1;
		    }
#endif

		    errnum = ERR_FILE_NOT_FOUND;
		}

		*rest = ch;
		return 0;
	    }
	    
	    if (! fixup_record (index_data, "INDX", my_index_record_size))
	    {
#ifdef DEBUG_NTFS
		printf("index error\n");
#endif
		errnum = ERR_FSYS_CORRUPT;
		return 0;
	    }
	    entry = (unsigned char *)(index_data + 0x18 + (*(unsigned short *)(index_data + 0x18)));
	    index_entry = entry + (*(unsigned short *)(entry + 8));
	    index_end = (unsigned char *)(index_data + my_index_record_size - 0x52);
	    
	    for (record_offset++; record_offset * my_index_record_size < cmft->attr_size; record_offset++)
	    {
		unsigned long bit = (1 << (record_offset & 3));
		unsigned long byte = (record_offset >> 3);
		
		if ((bitmap_data[byte] & bit))
		    break;
	    }
	    
	    if (record_offset * my_index_record_size >= cmft->attr_size)
	       	record_offset = -1;
#ifdef DEBUG_NTFS
	    printf("record_offset=%x\n", record_offset);
#endif
	}
	
	flag = entry[0x51];
	if ((long)(path_ino[depth + 1] = (*(unsigned long *)entry)) < 16)
//	if (path_ino[depth + 1] < 16)
	    continue;
	
	namelen = entry[0x50];
	//if(index_data[0x48]&2) printf("hidden file\n");
#ifndef STAGE1_5
	/* skip short file name */
	if (flag == 2 && print_possibilities && ch != '/' && ch != ':')
	    continue;
#endif
	
	for (i = 0, entry += 0x52; i < namelen; i++, entry += 2)
	{
	    unsigned long c = *(unsigned short *)entry;
	    
	    if (c == ' '|| c >= 0x100)
		fnbuf[i] = '_';
	    else
		fnbuf[i] = c;
	}
	
	fnbuf[namelen] = 0;
#ifdef DEBUG_NTFS
	printf("FLAG: %d  NAME: %s  inum=%d\n", flag,fnbuf,path_ino[depth+1]);
#endif

	//uncntrl(fnbuf);

	chk_sfn = substring (dirname, fnbuf, 1);
#ifndef STAGE1_5
	if (print_possibilities && ch != '/' && ch != ':' && (! *dirname || chk_sfn <= 0))
	{
	    if (print_possibilities > 0)
		print_possibilities = -print_possibilities;
	    print_a_completion (fnbuf);
	}
#endif /* STAGE1_5 */
    } while (chk_sfn != 0 || (print_possibilities && ch != '/' && ch != ':'));

    *(dirname = rest) = ch;

    depth++;

    /* go back to main loop at top of function */
    goto loop;
}

#ifdef DEBUG_NTFS
int dump_block(char *msg, char *buf, unsigned long size){
    unsigned long l = (size+15)/16;
    unsigned long off;
    unsigned long i, j;
    int c;
    printf("----- %s -----\n", msg);
    for( i = 0, off = 0; i < l; i++, off+=16)
    {
	if(off<16)
	    printf("000%x:", off);
	else if(off<256)
	    printf("00%x:", off);
	else
	    printf("0%x:", off);
	for(j=0;j<16;j++)
	{
	    c = buf[off+j]&0xff;
	    if( c >= 16 )
		printf("%c%x",j==8?'-':' ',c);
	    else
		printf("%c0%x",j==8?'-':' ',c);
	}
	printf("  ");
	for(j=0;j<16;j++) {
	    char c = buf[off+j];
	    printf("%c",c<' '||c>='\x7f'?'.':c);
	}
	printf("\n");
    }
}
/*#endif*/
#endif /* FSYS_NTFS */
