/*
 * hostfs.c  -  Routines for reading the ramdisk contents from a host
 *              filesystem
 *
 * Copyright (C) 2002-2007 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: hostfs.c,v 1.13 2007/01/06 18:31:22 gkminix Exp $
 */

#define NEED_BINARY 1
#define NEED_DIR 1
#define NEED_TIME 1
#include <common.h>
#include <nblib.h>
#include "mknbi.h"
#include "dir.h"
#include "headers/boot.h"
#include "headers/fat.h"

#ifndef MKNBI_H_DOS
#error Included wrong header file
#endif



/*
 * Array of characters allowed in a 8.3 DOS file name in addition to alpha
 * characters and numbers.
 */
static const pcchar_t nonalphachars[] = {
  126,  33,  64,  35,  36,  37,  94,  38,  40,  41,  95,  45, 123, 125,  96, 0
};




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

			Routines to parse directory tree

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

/*
 * Encode a file name into a unicode string
 */
static void encodeucs __F((src, dest, len),
				const char *fname AND
				struct fname_struct *namep)
{
  hchar_t c;
  ucs_t *lp;
  const char *cp;
  size_t len;
  int k;

  /* Determine number of characters in file name */
  cp = fname;
  len = 0;
  while (*cp) {
	if (charcollect(*cp) != 0)
		len++;
	cp++;
  }

  /* Allocate enough space for unicode string */
  k = roundup((int)(len + 1), LFN_CHARS);
  namep->lfn_num = k / LFN_CHARS;
  namep->lfn_name = (ucs_t *)nbmalloc(k * sizeof(ucs_t));
  memset((__u8 *)(namep->lfn_name), 0xff, (k * sizeof(ucs_t)));

  /* Copy file name into unicode memory */
  lp = namep->lfn_name;
  while (*cp && len > 0) {
	if ((c = charcollect(*cp)) != 0) {
		*lp++ = chartoucs2(c);
		len--;
	}
	cp++;
  }
  *lp = 0;
}



/*
 * Check if a host character is allowed in a 8.3 DOS file name
 */
static hchar_t cvtchar __F((c), hchar_t c)
{
  int i;

  /* Convert character to upper case */
  c = chartoupper(c);

  /* Check for alpha characters */
  for (i = 65; i < 91; i++)
	if (chartohost((pcchar_t)(i & 0xff)) == c)
		return(c);

  /* Check for number characters */
  for (i = 48; i < 58; i++)
	if (chartohost((pcchar_t)(i & 0xff)) == c)
		return(c);

  /* Check for non-alpha characters */
  for (i = 0; nonalphachars[i] != 0; i++)
	if (chartohost(nonalphachars[i]) == c)
		return(c);

  /* Check for special extended characters */
  for (i = 128; i < 166; i++)
	if (chartohost((pcchar_t)(i & 0xff)) == c)
		return(c);

  /* The character is invalid in a 8.3 file name */
  return(0);
}



/*
 * Convert UNIX path name into DOS file name. If the file name doesn't
 * fit into 8.3 format, the name will be converted accoording to DOS
 * rules.
 */
static void cvtname __F((dsp, namep, path),
				struct dir_struct *dsp AND
				struct fname_struct *namep AND
				char *path)
{
  char *cp, *fname;
  char numbuf[5];
  int name_end;
  int i, j;
  int needs_lfn = FALSE;
  hchar_t c;

  /* Isolate file name */
  if ((cp = strrchr(path, '/')) == NULL)
	cp = path;
  else
	cp++;
  fname = cp;

  /* Copy file name (max. 8 chars) */
  i = 0;
  while (*cp) {
	if ((c = charcollect(*cp)) != 0) {
		if (c == '.')
			break;
		c = cvtchar(c);
		if (i < 8 && c != 0)
			namep->name[i++] = c;
		else
			needs_lfn = TRUE;
	}
	cp++;
  }
  name_end = i;
  while (i < 8)
	namep->name[i++] = ' ';
  if (*cp == '.')
	cp++;

  /* Copy file extension (max. 3 chars) */
  i = 0;
  while (*cp) {
	if ((c = charcollect(*cp)) != 0) {
		c = cvtchar(c);
		if (i < 3 && c != 0)
			namep->ext[i++] = c;
		else
			needs_lfn = TRUE;
	}
	cp++;
  }
  while (i < 3)
	namep->ext[i++] = ' ';

  /* Clear long file name fields */
  namep->lfn_num = 0;
  if (namep->lfn_name != NULL) {
	free(namep->lfn_name);
	namep->lfn_name = NULL;
  }

  /* Check if the file name already exists after conversion */
  if (!needs_lfn && (findfile(dsp, FALSE, namep->name, namep->ext) != NULL))
	needs_lfn = TRUE;
  if (!needs_lfn)
	return;

  /* For long file names we have to search for a suitable 8.3 name */
  for (i = 1; i <= 999; i++) {
	sprintf(numbuf, "%d", i);
	if (((int)strlen(numbuf) + 1 + name_end) > 8)
		j = 8 - (int)strlen(numbuf) - 1;
	else
		j = name_end;
	cp = numbuf;
	namep->name[j++] = '~';
	while (*cp)
		namep->name[j++] = (hchar_t)(*cp++);
	if (findfile(dsp, FALSE, namep->name, namep->ext) == NULL) {
		encodeucs(fname, namep);
		return;
	}
  }
  prnerr("unable to convert name of file %s into 8.3 format", path);
  nbexit(EXIT_DOS_DOUBLEFILE);
}



/*
 * Read in the complete directory structure for the ramdisk image.
 */
static struct dir_struct *rdhostdir __F((parent, path, mtime),
				struct dir_struct *parent AND
				const char *path AND
				time_t mtime)
{
  DIR *dirp;
  char *cp;
  char tmppath[MAXNAMLEN + 1];
  struct stat sbuf;
  struct dirent *ent;
  struct dir_struct *dsp;
  struct dir_struct *tmpdsp;
  struct file_struct *fsp;

  /* Initialize directory structure */
  dsp = (struct dir_struct *)nbmalloc(sizeof(struct dir_struct) + strlen(path));
  strcpy(dsp->src.path, path);
  if (parent != NULL) {
	if ((cp = strrchr(dsp->src.path, '/')) == NULL)
		cp = dsp->src.path;
	else
		cp++;
	if (*cp == '.') {
		dsp->attrib |= ATTR_HIDDEN;
		cp++;
	}
	dsp->attrib = ATTR_DIR;
	cvtname(parent, &(dsp->name), cp);
	gettime(mtime, &(dsp->date), &(dsp->time));
  } 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;

  /* Open directory */
  if ((dirp = opendir(path)) == NULL) {
	prnerr("unable to open directory %s", path);
	nbexit(EXIT_OPENDIR);
  }

  /* Read in whole directory */
  while ((ent = readdir(dirp)) != NULL) {
	if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
		continue;
	sprintf(tmppath, "%s/%s", path, ent->d_name);
	if (stat(tmppath, &sbuf) != 0) {
		prnerr("unable to stat file %s", tmppath);
		nbexit(EXIT_STAT);
	}
	if (S_ISREG(sbuf.st_mode)) {
		fsp = (struct file_struct *)nbmalloc
				(sizeof(struct file_struct) + strlen(tmppath));
		fsp->attrib = 0;
		cp = ent->d_name;
		if (*cp == '.') {
			fsp->attrib |= ATTR_HIDDEN;
			cp++;
		}
		cvtname(dsp, &(fsp->name), cp);
		if (!(sbuf.st_mode & S_IWUSR))
			fsp->attrib |= ATTR_READONLY;
		if (cmpname(&(fsp->name), "IO", "SYS") ||
		    cmpname(&(fsp->name), "MSDOS", "SYS") ||
		    cmpname(&(fsp->name), "KERNEL", "SYS") ||
		    cmpname(&(fsp->name), "IBMBIO", "COM") ||
		    cmpname(&(fsp->name), "IBMDOS", "COM"))
			fsp->attrib |= ATTR_HIDDEN + ATTR_SYSTEM + ATTR_READONLY;
		strcpy(fsp->src.path, tmppath);
		gettime(sbuf.st_mtime, &(fsp->date), &(fsp->time));
		fsp->size = (unsigned long)sbuf.st_size;
		fsp->next = dsp->files;
		dsp->totsize += howmany(fsp->size, 1024);
		dsp->lfnnum += fsp->name.lfn_num;
		dsp->files = fsp;
		dsp->filenum++;
	} else if (S_ISDIR(sbuf.st_mode)) {
		tmpdsp = rdhostdir(dsp, tmppath, sbuf.st_mtime);
		tmpdsp->next = dsp->subdirs;
		dsp->totsize += tmpdsp->totsize;
		dsp->lfnnum += tmpdsp->name.lfn_num;
		dsp->subdirs = tmpdsp;
		dsp->subdirnum++;
	}
  }

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

  /* Close directory and return */
  closedir(dirp);
  return(dsp);
}





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

			Routines to open and close the host directory

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

/*
 * Open a host directory and read it recursively
 */
void hostopen __F((sysentry, filesys),
				struct sysdef *sysentry AND
				struct fs_struct *filesys)
{
  struct boot_record *bp;
  union {
	time_t        curtime;
	unsigned long serno;
  } serial;

  /* Just for safety */
  assert(filesys != NULL);

  /* Read the host root directory */
  filesys->root_dir = rdhostdir(NULL, sysentry->rdname, (time_t)0);

  /* Prepare the boot block */
  filesys->boot_size = roundup(boot_data_size, SECTSIZE);
  filesys->boot_block = (__u8 *)nbmalloc(filesys->boot_size);
  memcpy(filesys->boot_block, boot_data, boot_data_size);
  bp = (struct boot_record *)(filesys->boot_block);
  assign(bp->reserved_sect, htot(filesys->boot_size / SECTSIZE));
  bytecpy(BOOT_OEM_NAME, bp->oem_name, sizeof(bp->oem_name));

  /* We use the current UNIX time as the serial number */
  serial.serno = 0;
  time(&serial.curtime);
  assign(bp->vol_num.low, htot(low_word(serial.serno)));
  assign(bp->vol_num.high, htot(high_word(serial.serno)));
}



/*
 * Close host directory
 */
void hostclose __F((filesys), struct fs_struct *filesys)
{
  assert(filesys != NULL);
  if (filesys->boot_block != NULL) {
	free(filesys->boot_block);
	filesys->boot_block = NULL;
	filesys->boot_size = 0;
  }
}



/*
 * Copy a file from the host directory into the output file
 */
unsigned long hostcopy __F((clustsize, handle, fsp),
				int clustsize AND
				int handle AND
				const struct file_struct *fsp)
{
  __u8 *buf;
  unsigned long num;
  unsigned long writecnt = 0;
  int eof = FALSE;
  int readsize;
  int infile;

  if ((infile = open(fsp->src.path, O_RDONLY | O_BINARY)) < 0) {
	perror(fsp->src.path);
	nbexit(EXIT_OPEN);
  }

  buf = (__u8 *)nbmalloc(clustsize);
  for (num = 0; num < fsp->clustnum; num++) {
	memzero(buf, clustsize);
	if (!eof) {
		readsize = nbread(buf, clustsize, infile);
		eof = (readsize < clustsize);
	}
	writecnt += nbwrite(buf, clustsize, handle);
  }
  close(infile);
  free(buf);
  return(writecnt);
}

