/*
 * fatfs.c  -  Routines for reading a FAT filesystem
 *
 * Copyright (C) 2002-2003 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  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
 *  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.
 *
 * $Id: fatfs.c,v 1.3 2003/01/25 23:29:43 gkminix Exp $
 */

#define NEED_BINARY 1
#define NEED_TIME 1
#include <common.h>
#include <nblib.h>
#include "mknbi.h"
#include "dir.h"

#ifndef _MKNBI_H_DOS_
#error Included wrong header file
#endif



/*
 * Buffers for temporary holding the disk FAT
 */
static enum { TYPE_FAT12, TYPE_FAT16 } fattype;
static unsigned long fatsize;
static __u8 *fatbuf = NULL;



/*
 * Variables holding important values of a FAT filesystem, All size
 * values here are in bytes, not sectors.
 */
static int fatfile = -1;
static unsigned long clustsize;
static unsigned long rootsize;
static unsigned long dataoffset;
static unsigned long rootoffset;





/***************************************************************************

			Routines to read a file sector

 ***************************************************************************/

/* Current file position */
struct filepos {
	unsigned long cursect;
	unsigned long curclust;
	unsigned long cursize;
};

/* Current disk position */
static unsigned long lastpos = 0L; 



/*
 * Read one sector from FAT image file
 */
static void readsect(buf, offset)
__u8 *buf;
unsigned long offset;
{
  if (offset != lastpos) {
	if (lseek(fatfile, offset, 0) != offset) {
		prnerr0("unable to seek within DOS image file");
		exit(EXIT_SEEK);
	}
  }
  if (nbread(buf, SECTSIZE, fatfile) != SECTSIZE) {
	prnerr0("unexpected end of DOS image file/device");
	exit(EXIT_DOS_RDEOF);
  }
  lastpos = offset + SECTSIZE;
}



/*
 * Open a file/directory
 */
static struct filepos *openfile(cluster, size)
unsigned long cluster;
unsigned long size;
{
  struct filepos *fp;

  fp = (struct filepos *)nbmalloc(sizeof(struct filepos));
  fp->curclust = cluster;
  fp->cursize = size;
  fp->cursect = 0;
  return(fp);
}



/*
 * Close a file/directory
 */
static void closefile(fp)
struct filepos *fp;
{
  if (fp != NULL)
	free(fp);
}



/*
 * Read a sector from a cluster, returns FALSE if end of file
 */
static int readclust(fp, buf)
struct filepos *fp;
__u8 *buf;
{
  unsigned long offset;
  unsigned long entry;

  /* Check if at end of file */
  if (((fp->cursize > 0) && ((fp->cursect * SECTSIZE) >= fp->cursize)) ||
      ((fattype == TYPE_FAT12) && (fp->curclust >= 0x0ff0)) ||
      ((fattype == TYPE_FAT16) && (fp->curclust >= 0xfff0)))
	return(FALSE);

  /* Read requested sector */
  if (fp->curclust > 0) {
	offset = (fp->curclust - FIRST_CLUSTER) * clustsize + dataoffset;
	offset += (fp->cursect * SECTSIZE) % clustsize;
	readsect(buf, offset);
  } else {
	offset = rootoffset + (fp->cursect * SECTSIZE);
	readsect(buf, offset);
  }

  /* Advance pointers to next sector */
  fp->cursect++;
  if ((fp->curclust > 0) &&
      (((fp->cursect * SECTSIZE) % clustsize) == 0)) {
	if (fattype == TYPE_FAT12) {
		entry  = ttoh(getval(*((__u16 *)(fatbuf +
					(fp->curclust * 12) / 8))));
		entry |= ttoh(getval(*((__u16 *)(fatbuf +
					(fp->curclust * 12) / 8 + 2)))) << 16;
		entry >>= (fp->curclust * 12) % 8;
		entry &= 0x0fff;
	} else {
		entry  = ttoh(getval(*((__u16 *)
					(fatbuf + (fp->curclust * 2)))));
		entry &= 0xffff;
	}
	fp->curclust = entry;
  }
  return(TRUE);
}





/***************************************************************************

			Routines to read directory tree

 ***************************************************************************/

/* Directory buffers */
struct dirpos {
	__u8            dirbuf[SECTSIZE];
	unsigned int    diroffset;
	struct filepos *fp;
};



/*
 * Open directory
 */
static struct dirpos *fatopendir(dirp)
struct dos_dir *dirp;
{
  struct dirpos *dp;

  dp = (struct dirpos *)nbmalloc(sizeof(struct dirpos));
  if (dirp != NULL)
	dp->fp = openfile(ttoh(getval(dirp->cluster)), get_long(dirp->size));
  else
	dp->fp = openfile(0, rootsize);
  dp->diroffset = SECTSIZE;
  return(dp);
}



/*
 * Close directory
 */
static void fatclosedir(dp)
struct dirpos *dp;
{
  if (dp != NULL)
	free(dp);
}



/*
 * Get next directory entry, returns NULL if nothing to read
 */
static struct dos_dir *getnextdir(dp)
struct dirpos *dp;
{
  struct dos_dir *dirp;

  while (TRUE) {
	/* Read next directory sector */
	if (dp->diroffset >= SECTSIZE) {
		if (!readclust(dp->fp, dp->dirbuf))
			return(NULL);
		dp->diroffset = 0;
	}

	/* Check that directory entry is not empty */
	dirp = (struct dos_dir *)&(dp->dirbuf[dp->diroffset]);
	dp->diroffset += sizeof(struct dos_dir);
	if (dirp->name[0] != 0 && dirp->name[0] != 0xe5)
		break;
  }
  return(dirp);
}



/*
 * Compute checksum of short file name
 */
static int getchksum(dirp)
struct dos_dir *dirp;
{
  int chksum = 0;
  int i;

  for (i = 0; i < 8; i++) {
	chksum = (chksum >> 1) | ((chksum & 0x01) << 7);
	chksum = (chksum + dirp->name[i]) & 0xff;
  }
  for (i = 0; i < 3; i++) {
	chksum = (chksum >> 1) | ((chksum & 0x01) << 7);
	chksum = (chksum + dirp->ext[i]) & 0xff;
  }
  return(chksum);
}



/*
 * Table to convert DOS characters into latin1 characters
 */
static unsigned char const doschars[256] = {
      0,   1,   2,   3,   4,   5,   6,   7,	/*   0 -   7  */
      8,   9,  10,  11,  12,  13,  14,  15,	/*   8 -  15  */
     16,  17,  18,  19, 182, 167,  22,  23,	/*  16 -  23  */
     24,  25,  26,  27,  28,  29,  30,  31,	/*  24 -  31  */
     32,  33,  34,  35,  36,  37,  38,  39,	/*  32 -  39  */
     40,  41,  42,  43,  44,  45,  46,  47,	/*  40 -  47  */
     48,  49,  50,  51,  52,  53,  54,  55,	/*  48 -  55  */
     56,  57,  58,  59,  60,  61,  62,  63,	/*  56 -  63  */
     64,  65,  66,  67,  68,  69,  70,  71,	/*  64 -  71  */
     72,  73,  74,  75,  76,  77,  78,  79,	/*  72 -  79  */
     80,  81,  82,  83,  84,  85,  86,  87,	/*  80 -  87  */
     88,  89,  90,  91,  92,  93,  94,  95,	/*  88 -  95  */
     96,  97,  98,  99, 100, 101, 102, 103,	/*  96 - 103  */
    104, 105, 106, 107, 108, 109, 110, 111,	/* 104 - 111  */
    112, 113, 114, 115, 116, 117, 118, 119,	/* 112 - 119  */
    120, 121, 122, 123, 124, 125, 126, 127,	/* 120 - 127  */
    199, 252, 233, 226, 228, 224, 229, 231,	/* 128 - 135  */
    234, 235, 232, 239, 238, 236, 196, 197,	/* 136 - 143  */
    201, 230, 198, 244, 246, 242, 251, 249,	/* 144 - 151  */
    255, 214, 220, 162, 163, 165, 158, 159,	/* 152 - 159  */
    225, 237, 243, 250, 241, 209, 170, 186,	/* 160 - 167  */
    191, 169, 172, 189, 188, 161, 171, 187,	/* 168 - 175  */
    248, 164, 253, 179, 180, 145,  20, 156,	/* 176 - 183  */
    184, 185,  21, 175, 166, 174, 190, 168,	/* 184 - 191  */
    192, 193, 194, 195, 142, 143, 146, 128,	/* 192 - 199  */
    200, 144, 202, 203, 204, 205, 206, 207,	/* 200 - 207  */
    208, 157, 210, 211, 212, 213, 153, 215,	/* 208 - 215  */
    216, 217, 218, 219, 154, 221, 222, 152,	/* 216 - 223  */
    133, 223, 131, 227, 132, 134, 181, 135,	/* 224 - 231  */
    138, 130, 136, 137, 141, 173, 140, 139,	/* 232 - 239  */
    240, 177, 149, 155, 147, 245, 247, 148,	/* 240 - 247  */
    176, 151, 183, 150, 129, 178, 254, 160,	/* 248 - 255  */
};



/*
 * Copy file name from directory entry into directory record.
 * We do this byte by byte because on the host system, characters
 * don't necessarily need to be encoded as with DOS.
 */
static void namecopy(dest, src, len)
char *dest;
__u8 *src;
int len;
{
  int i;

  for (i = 0; i < len; i++)
	*(dest++) = doschars[(int)*(src++)];
}



/*
 * Read in the complete directory structure
 */
static struct dir_struct *rdfatdir(src_dirp, src_lfn_name, src_lfn_num)
struct dos_dir *src_dirp;
utf16_t *src_lfn_name;
int src_lfn_num;
{
  struct dirpos *dp;
  struct dos_dir *dirp;
  struct dir_struct *dsp;
  struct dir_struct *tmpdsp;
  struct file_struct *fsp;
  utf16_t *tmp_name;
  utf16_t *lfn_name;
  __u16 *tmpptr;
  int lfn_size = 0;
  int lfn_chksum = 0;
  int i, j;

  /* Initialize temporary LFN buffer */
  tmp_name = (utf16_t *)nbmalloc(LFN_CHARS * 64 * sizeof(utf16_t));
  memset(tmp_name, 0xff, LFN_CHARS * 64 * sizeof(utf16_t));

  /* Initialize directory structure */
  dsp = (struct dir_struct *)nbmalloc(sizeof(struct dir_struct));
  if (src_dirp != NULL) {
	namecopy(dsp->name.name, src_dirp->name, sizeof(src_dirp->name));
	namecopy(dsp->name.ext, src_dirp->ext, sizeof(src_dirp->ext));
	dsp->name.lfn_name = src_lfn_name;
	dsp->name.lfn_num = src_lfn_num;
	dsp->attrib = src_dirp->attrib;
	dsp->time = ttoh(getval(src_dirp->time));
	dsp->date = ttoh(getval(src_dirp->date));
	dsp->src.cluster = ttoh(getval(src_dirp->cluster));
  } else {
	dsp->attrib = ATTR_DIR;
	gettime(time(NULL), &(dsp->date), &(dsp->time));
  }
  dsp->subdirnum = 0;
  dsp->filenum = 0;
  dsp->lfnnum = 0;
  dsp->totsize = 0L;
  dsp->subdirs = NULL;
  dsp->files = NULL;
  dsp->next = NULL;

  /* Read in whole directory */
  dp = fatopendir(src_dirp);
  while ((dirp = getnextdir(dp)) != NULL) {
	if ((bytecmp(".       ", dirp->name, 8) ||
	     bytecmp("..      ", dirp->name, 8)) &&
	     bytecmp("   ", dirp->ext, 3))
		continue;
	if (dirp->attrib == ATTR_LFN) {
		struct lfn_dir *lfnp = (struct lfn_dir *)dirp;

		/*
		 * Check that the checksum is the same as of the preceding
		 * long file name entry.
		 */
		if (lfn_size == 0)
			lfn_chksum = lfnp->checksum;
		else if (lfn_chksum != lfnp->checksum) {
			lfn_size = 0;
			continue;
		}

		/*
		 * Copy long file name characters into temporary buffer
		 */
		i = ((lfnp->sequence & LFN_SEQ_MASK) - 1) * LFN_CHARS;
		tmpptr = lfnp->first_part;
		for (j = 0; j < LFN_CHARS; j++) {
			tmp_name[i + j] = ttoh(getval(*tmpptr));
			if (tmp_name[i + j] == 0)
				break;
			lfn_size++;
			if (j == 5)
				tmpptr = lfnp->second_part;
			else if (j == 11)
				tmpptr = lfnp->third_part;
			else
				tmpptr++;
		}
		continue;
	} else if ((lfn_size > 0) && (lfn_chksum != getchksum(dirp))) {
		/*
		 * If the checksum of the directory entry name doesn't
		 * fit to the checksum of the preceding long filename
		 * records, don't use a long file name for this directory
		 * entry.
		 */
		lfn_size = 0;
	}

	lfn_name = NULL;
	if (lfn_size > 0) {
		lfn_size = roundup(lfn_size, LFN_CHARS);
		lfn_name = (utf16_t *)nbmalloc(lfn_size * sizeof(utf16_t));
		memcpy(lfn_name, tmp_name, (lfn_size * sizeof(utf16_t)));
		memset(tmp_name, 0xff, LFN_CHARS * 64 * sizeof(utf16_t));
	}
	if (!(dirp->attrib & ATTR_DIR) &&
	    !(dirp->attrib & ATTR_LABEL)) {
		/* Handle entry for ordinary files */
		fsp = (struct file_struct *)nbmalloc (sizeof(struct file_struct));
		namecopy(fsp->name.name, dirp->name, sizeof(dirp->name));
		namecopy(fsp->name.ext, dirp->ext, sizeof(dirp->ext));
		fsp->name.lfn_name = lfn_name;
		fsp->name.lfn_num = howmany(lfn_size, LFN_CHARS);
		fsp->attrib = dirp->attrib;
		fsp->time = ttoh(getval(dirp->time));
		fsp->date = ttoh(getval(dirp->date));
		fsp->src.cluster = ttoh(getval(dirp->cluster));
		fsp->size = get_long(dirp->size);
		fsp->next = dsp->files;
		dsp->totsize += howmany(fsp->size, 1024);
		dsp->lfnnum += fsp->name.lfn_num;
		dsp->files = fsp;
		dsp->filenum++;
	} else if (!(dirp->attrib & ATTR_LABEL)) {
		/* Handle directory entry */
		tmpdsp = rdfatdir(dirp, lfn_name, howmany(lfn_size, LFN_CHARS));
		tmpdsp->next = dsp->subdirs;
		dsp->totsize += tmpdsp->totsize;
		dsp->lfnnum += tmpdsp->name.lfn_num;
		dsp->subdirs = tmpdsp;
		dsp->subdirnum++;
	} else {
		/* Handle entry for volume name */
		if (lfn_name != NULL) {
			/* For labels just eat long file name */
			free(lfn_name);
			lfn_name = NULL;
		}
		if (volumename == NULL) {
			volumename = (char *)nbmalloc(12);
			namecopy(&(volumename[0]), dirp->name,
							sizeof(dirp->name));
			namecopy(&(volumename[8]), dirp->ext,
							sizeof(dirp->ext));
		}
	}
	lfn_size = 0;
  }
  fatclosedir(dp);
  free(tmp_name);

  /* Add size of directory to total size, except for root directory  */
  if (src_dirp != NULL)
	dsp->totsize += howmany(DIR_ENTRIES(dsp) * sizeof(struct dos_dir),
									1024);
  return(dsp);
}





/***************************************************************************

			Routines to open and close the image file

 ***************************************************************************/

/*
 * Open a FAT file system image/device
 */
void fatopen(name)
char *name;
{
  static __u8 buf[SECTSIZE];
  struct boot_record *bootrec;
  unsigned long total_sects;
  unsigned long fat_offset;
  unsigned int sect_per_fat;
  unsigned int fat_num;
  unsigned int i, j;

  /*
   * Open FAT image file or device and read the boot sector.
   */
  if ((fatfile = open(name, O_RDONLY | O_BINARY)) < 0) {
	prnerr1("unable to open DOS image file %s", name);
	exit(EXIT_DOS_RDOPEN);
  }
  readsect(buf, 0);
  if (getval(*((__u16 *)&(buf[BOOT_SIG_OFF]))) != htot(BOOT_SIGNATURE)) {
	prnerr1("DOS image file %s has no boot signature", name);
	exit(EXIT_DOS_NOBOOT);
  }
  bootrec = (struct boot_record *)buf;

  /*
   * Check that the boot record of the image file really contains a FAT
   * filesystem and read some important values necessary for accessing the
   * filesystem lateron.
   */
  if (bytecmp(BOOT_FAT12_NAME, bootrec->fat_name, sizeof(bootrec->fat_name)))
	fattype = TYPE_FAT12;
  else if (bytecmp(BOOT_FAT16_NAME, bootrec->fat_name, sizeof(bootrec->fat_name)))
	fattype = TYPE_FAT16;
  else {
	prnerr0("DOS image file has invalid filesystem");
	exit(EXIT_DOS_INVFS);
  }
  if (getval(bootrec->bytes_per_sect) != htot(SECTSIZE)) {
	prnerr0("DOS image file has wrong sector size");
	exit(EXIT_DOS_SECTSIZE);
  }
  fat_num = bootrec->fat_num;
  if ((fat_num == 0) || (fat_num > 2)) {
	prnerr0("DOS image file has invalid number of FATs");
	exit(EXIT_DOS_FATNUM);
  }
  fat_offset = ttoh(getval(bootrec->reserved_sect)) * SECTSIZE;
  total_sects = ttoh(getval(bootrec->sect_num));
  if (total_sects == 0)
	total_sects = get_long(bootrec->sect_num_32);
  if (total_sects < (MIN_RDSIZE * SECTS_PER_KB)) {
	prnerr0("DOS image file has invalid number of sectors");
	exit(EXIT_DOS_INVSIZE);
  } else if (total_sects > ((ULONG_MAX / SECTSIZE) - MAX_SECTS)) {
	prnerr0("DOS image file has too many sectors");
	exit(EXIT_DOS_INVSIZE);
  }
  sect_per_fat = ttoh(getval(bootrec->sect_per_fat));
  if (sect_per_fat == 0) {
	prnerr0("DOS image file has invalid FAT size");
	exit(EXIT_DOS_INVFAT);
  }
  fatsize = sect_per_fat * SECTSIZE;
  clustsize = bootrec->sect_per_cluster * SECTSIZE;
  rootsize = roundup(ttoh(getval(bootrec->dir_num)) * sizeof(struct dos_dir),
								SECTSIZE);
  rootoffset = fat_offset + (fat_num * fatsize);
  dataoffset = rootoffset + rootsize;

  /*
   * Prepare the boot block by reading all reserved sectors. This is necesary
   * so that any additional boot sectors get copied unmodified.
   */
  boot_size = fat_offset;
  boot_block = (__u8 *)nbmalloc(boot_size);
  memcpy(boot_block, buf, SECTSIZE);
  for (i = SECTSIZE; i < boot_size; i += SECTSIZE)
	readsect(&(boot_block[i]), i);

  /*
   * Read first FAT. Note that this will destroy the buffer which
   * contains the boot block!
   */
  fatbuf = (__u8 *)nbmalloc(fatsize);
  for (i = 0; i < sect_per_fat; i++)
	readsect(&fatbuf[i * SECTSIZE], ((i * SECTSIZE) + fat_offset));
  if (fatbuf[0] != bootrec->media_id) {
	prnerr0("DOS image file has invalid FAT");
	exit(EXIT_DOS_INVFAT);
  }
  for (j = 1; j < fat_num; j++) {
	fat_offset += sect_per_fat * SECTSIZE;
	for (i = 0; i < sect_per_fat; i++) {
		readsect(buf, ((i * SECTSIZE) + fat_offset));
		if (memcmp(buf, &fatbuf[i * SECTSIZE], SECTSIZE)) {
			prnerr0("FAT copies in DOS image file differ");
			exit(EXIT_DOS_INVFAT);
		}
	}
  }

  /* Read root directory recursively */
  root_dir = rdfatdir(NULL, NULL, 0);
}



/*
 * Close FAT image file or device
 */
void fatclose()
{
  if (fatfile >= 0)
	close(fatfile);
  if (fatbuf != NULL)
	free(fatbuf);
  if (boot_block != NULL)
	free(boot_block);
}



/*
 * Copy a file from the FAT image into the output file
 */
void fatcopy(clustsize, handle, fsp)
int clustsize;
int handle;
struct file_struct *fsp;
{
  struct filepos *fp;
  __u8 *buf;
  unsigned long num;
  int eof = FALSE;
  int i;

  buf = (__u8 *)nbmalloc(clustsize);
  fp = openfile(fsp->src.cluster, fsp->size);
  for (num = 0; num < fsp->clustnum; num++) {
	memset(buf, 0, clustsize);
	for (i = 0; !eof && (i < clustsize); i += SECTSIZE)
		eof = !readclust(fp, &(buf[i]));
	(void)nbwrite(buf, clustsize, handle);
  }
  closefile(fp);
  free(buf);
}



syntax highlighted by Code2HTML, v. 0.9.1