/* 
 * This code is based on the ISO filesystem support in MILO (by
 * Dave Rusling).
 *
 * This is a set of functions that provides minimal filesystem
 * functionality to the Linux bootstrapper.  All we can do is
 * open and read files... but that's all we need 8-)
 */
#include <linux/kernel.h>
#include <linux/config.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/kdev_t.h>
#include <asm/page.h>

#include "string.h"
#include "iso.h"
#include "isolib.h"
#include "utils.h"

/* iso9660 support code */

#define MAX_OPEN_FILES 5

static struct inode_table_entry {
    struct iso_inode inode;
    int inumber;
    int free;
    unsigned short old_mode;
    unsigned size;
    int nlink;
    int mode;
    void *start;
} inode_table[MAX_OPEN_FILES];

static unsigned long root_inode = 0;
static struct isofs_super_block sb;
static char data_block[1024];
static char big_data_block[2048];

#ifndef S_IRWXUGO
# define S_IRWXUGO	(S_IRWXU|S_IRWXG|S_IRWXO)
# define S_IXUGO	(S_IXUSR|S_IXGRP|S_IXOTH)
# define S_IRUGO	(S_IRUSR|S_IRGRP|S_IROTH)
#endif

extern long iso_dev_read (void * buf, long offset, long size);

static int parse_rock_ridge_inode(struct iso_directory_record * de,
				  struct iso_inode * inode);
static char *get_rock_ridge_symlink(struct iso_inode *inode);
static int get_rock_ridge_filename(struct iso_directory_record * de,
				   char * retname,
				   struct iso_inode * inode);

int
isonum_711 (char * p)
{
	return (*p & 0xff);
}


int
isonum_712 (char * p)
{
	int val;

	val = *p;
	if (val & 0x80)
		val |= 0xffffff00;
	return val;
}


int
isonum_721 (char * p)
{
	return ((p[0] & 0xff) | ((p[1] & 0xff) << 8));
}

int
isonum_722 (char * p)
{
	return (((p[0] & 0xff) << 8) | (p[1] & 0xff));
}


int
isonum_723 (char * p)
{
	return isonum_721(p);
}


int
isonum_731 (char * p)
{
	return ((p[0] & 0xff)
		| ((p[1] & 0xff) << 8)
		| ((p[2] & 0xff) << 16)
		| ((p[3] & 0xff) << 24));
}


int
isonum_732 (char * p)
{
	return (((p[0] & 0xff) << 24)
		| ((p[1] & 0xff) << 16)
		| ((p[2] & 0xff) << 8)
		| (p[3] & 0xff));
}


int
isonum_733 (char * p)
{
	return isonum_731(p);
}


static int
iso_bmap (struct iso_inode *inode, int block)
{
	if (block < 0) {
		printf("iso_bmap: block<0");
		return 0;
	}
	return (inode->i_first_extent >> sb.s_blocksize_bits) + block;
}

static int
iso_breadi (struct iso_inode *ip, long blkno, long nblks, char * buffer)
{
	long i_size, abs_blkno;

	/* do some error checking */
	if (!ip || !ip->i_first_extent)
		return -1;

	i_size = ((struct inode_table_entry *) ip)->size;
	/* as in ext2.c - cons_read() doesn't really cope well with
           EOF conditions - actually it should be fixed */
	if ((blkno+nblks) * sb.s_blocksize > i_size)
		nblks = ((i_size + sb.s_blocksize)
			 / sb.s_blocksize) - blkno;

	/* figure out which iso block number(s) we're being asked for */
	abs_blkno = iso_bmap(ip, blkno);
	if (!abs_blkno)
		return -1;
	/* now try and read them (easy since ISO files are continguous) */
	return iso_dev_read(buffer, abs_blkno * sb.s_blocksize,
			    nblks * sb.s_blocksize);
}


/*
 * Release our hold on an inode.  Since this is a read-only application,
 * don't worry about putting back any changes...
 */
static void
iso_iput (struct iso_inode *ip)
{
	struct inode_table_entry *itp;

	/* Find and free the inode table slot we used... */
	itp = (struct inode_table_entry *) ip;

	itp->inumber = 0;
	itp->free = 1;
}

/*
 * Read the specified inode from the disk and return it to the user.
 * Returns NULL if the inode can't be read...
 *
 * Uses data_block
 */
static struct iso_inode *
iso_iget (int ino)
{
	int i;
	struct iso_inode *inode;
	struct inode_table_entry *itp;
	struct iso_directory_record * raw_inode;
	unsigned char *pnt = NULL;
	void *cpnt = NULL;
	int high_sierra;
	int block;

#ifdef DEBUG_ISO
	printf("iso_iget(ino=%d)\n", ino);
#endif

	/* find a free inode to play with */
	inode = NULL;
	itp = NULL;
	for (i = 0; i < MAX_OPEN_FILES; i++) {
		if (inode_table[i].free) {
			itp = &(inode_table[i]);
			inode = &(itp->inode);
			break;
		}
	}
	if ((inode == NULL) || (itp == NULL)) {
		printf("iso9660 (iget): no free inodes\n");
		return (NULL);
	}

	block = ino >> sb.s_blocksize_bits;
	if (iso_dev_read(data_block, block * sb.s_blocksize, sb.s_blocksize)
	    != sb.s_blocksize) {
		printf("iso9660: unable to read i-node block");
		return NULL;
	}

	pnt = ((unsigned char *) data_block + (ino & (sb.s_blocksize - 1)));
	raw_inode = ((struct iso_directory_record *) pnt);
	high_sierra = sb.s_high_sierra;

	if ((ino & (sb.s_blocksize - 1)) + *pnt > sb.s_blocksize){
		int frag1, offset;

		offset = (ino & (sb.s_blocksize - 1));
		frag1 = sb.s_blocksize - offset;
		cpnt = big_data_block;
		memcpy(cpnt, data_block + offset, frag1);
		offset += *pnt - sb.s_blocksize; /* DUH! pnt would get
                                                    wiped out by the
                                                    iso_dev_read here. */
		if (iso_dev_read(data_block, ++block * sb.s_blocksize,
				 sb.s_blocksize)
		    != sb.s_blocksize) {
			printf("unable to read i-node block");
			return NULL;
		}
		memcpy((char *)cpnt+frag1, data_block, offset);
		pnt = ((unsigned char *) cpnt);
		raw_inode = ((struct iso_directory_record *) pnt);
	}

	if (raw_inode->flags[-high_sierra] & 2) {
		itp->mode = S_IRUGO | S_IXUGO | S_IFDIR;
		itp->nlink = 1; /* Set to 1.  We know there are 2, but
				   the find utility tries to optimize
				   if it is 2, and it screws up.  It is
				   easier to give 1 which tells find to
				   do it the hard way. */
	} else {
		itp->mode = sb.s_mode; /* Everybody gets to read the file. */
		itp->nlink = 1;
		itp->mode |= S_IFREG;
		/*
		 * If there are no periods in the name, then set the
		 * execute permission bit
		 */
		for(i=0; i< raw_inode->name_len[0]; i++)
			if(raw_inode->name[i]=='.' || raw_inode->name[i]==';')
				break;
		if(i == raw_inode->name_len[0] || raw_inode->name[i] == ';') 
			itp->mode |= S_IXUGO; /* execute permission */
	}

	itp->size = isonum_733 (raw_inode->size);

	/* There are defective discs out there - we do this to protect
	   ourselves.  A cdrom will never contain more than 700Mb */
	if((itp->size < 0 || itp->size > 700000000) &&
	   sb.s_cruft == 'n')
	{
		printf("Warning: defective cdrom.  "
		       "Enabling \"cruft\" mount option.\n");
		sb.s_cruft = 'y';
	}

	/*
	 * Some dipshit decided to store some other bit of information
	 * in the high byte of the file length.  Catch this and
	 * holler.  WARNING: this will make it impossible for a file
	 * to be > 16Mb on the CDROM!!!
	 */
	if(sb.s_cruft == 'y' && 
	   itp->size & 0xff000000)
	{
		itp->size &= 0x00ffffff;
	}

	if (raw_inode->interleave[0]) {
		printf("Interleaved files not (yet) supported.\n");
		itp->size = 0;
	}

	/* I have no idea what file_unit_size is used for, so
	   we will flag it for now */
	if (raw_inode->file_unit_size[0] != 0){
		printf("File unit size != 0 for ISO file (%d).\n", ino);
	}

	/* I have no idea what other flag bits are used for, so
	   we will flag it for now */
#ifdef DEBUG_ISO
	if ((raw_inode->flags[-high_sierra] & ~2)!= 0){
		printf("Unusual flag settings for ISO file (%d %x).\n",
		       ino, raw_inode->flags[-high_sierra]);
	}
#endif

	inode->i_first_extent = (isonum_733 (raw_inode->extent) + 
				 isonum_711 (raw_inode->ext_attr_length))
					 << sb.s_log_zone_size;

	/* Now we check the Rock Ridge extensions for further info */

	if (sb.s_rock) 
		parse_rock_ridge_inode(raw_inode,inode);

	/* Will be used for previous directory */
	inode->i_backlink = 0xffffffff;
	switch (sb.s_conversion) {
	      case 'a':
		inode->i_file_format = ISOFS_FILE_UNKNOWN; /* File type */
		break;
	      case 'b':
		inode->i_file_format = ISOFS_FILE_BINARY; /* File type */
		break;
	      case 't':
		inode->i_file_format = ISOFS_FILE_TEXT; /* File type */
		break;
	      case 'm':
		inode->i_file_format = ISOFS_FILE_TEXT_M; /* File type */
		break;
	}

	/* keep our inode table correct */
	itp->free = 0;
	itp->inumber = ino;

	/* return a pointer to it */
	return inode;
}


/*
 * ok, we cannot use strncmp, as the name is not in our data space.
 * Thus we'll have to use iso_match. No big problem. Match also makes
 * some sanity tests.
 *
 * NOTE! unlike strncmp, iso_match returns 1 for success, 0 for failure.
 */
static int
iso_match (int len, const char *name, const char *compare, int dlen)
{
	if (!compare)
		return 0;

#ifdef DEBUG_ISO
	printf("iso_match: comparing %d chars of %s with %s\n",
	       dlen, name, compare);
#endif

	/* check special "." and ".." files */
	if (dlen == 1) {
		/* "." */
		if (compare[0] == 0) {
			if (!len)
				return 1;
			compare = ".";
		} else if (compare[0] == 1) {
			compare = "..";
			dlen = 2;
		}
	}
	if (dlen != len)
		return 0;
	return !memcmp(name, compare, len);
}

/*
 * Find an entry in the specified directory with the wanted name. It
 * returns the cache buffer in which the entry was found, and the entry
 * itself (as an inode number). It does NOT read the inode of the
 * entry - you'll have to do that yourself if you want to.
 *
 * uses data_block
 */
static int
iso_find_entry (struct iso_inode *dir, const char *name, int namelen,
		unsigned long *ino, unsigned long *ino_back)
{
	unsigned long bufsize = sb.s_blocksize;
	unsigned char bufbits = sb.s_blocksize_bits;
	unsigned int block, f_pos, offset, inode_number = 0; /*shut up, gcc*/
	void * cpnt = NULL;
	unsigned int old_offset;
	unsigned int backlink;
	int dlen, match, i;
	struct iso_directory_record * de;
	char c;
	struct inode_table_entry *itp = (struct inode_table_entry *) dir;

	*ino = 0;
	if (!dir) return -1;
	
	if (!(block = dir->i_first_extent)) return -1;
  
	f_pos = 0;
	offset = 0;
	block = iso_bmap(dir,f_pos >> bufbits);
	if (!block) return -1;

	if (iso_dev_read(data_block, block * sb.s_blocksize, sb.s_blocksize)
	    != sb.s_blocksize) return -1;
  
	while (f_pos < itp->size) {
		de = (struct iso_directory_record *) (data_block + offset);
		backlink = itp->inumber;
		inode_number = (block << bufbits) + (offset & (bufsize - 1));

		/* If byte is zero, this is the end of file, or time to move to
		   the next sector. Usually 2048 byte boundaries. */
		
		if (*((unsigned char *) de) == 0) {
			offset = 0;

			/* round f_pos up to the nearest blocksize */
			f_pos = ((f_pos & ~(ISOFS_BLOCK_SIZE - 1))
				 + ISOFS_BLOCK_SIZE);
			block = iso_bmap(dir,f_pos>>bufbits);
			if (!block) return -1;
			if (iso_dev_read(data_block,
					 block * sb.s_blocksize,
					 sb.s_blocksize)
				!= sb.s_blocksize) return -1;
  			continue; /* Will kick out if past end of directory */
		}

		old_offset = offset;
		offset += *((unsigned char *) de);
		f_pos += *((unsigned char *) de);

		/* Handle case where the directory entry spans two blocks.
		   Usually 1024 byte boundaries */
		if (offset >= bufsize) {
		        unsigned int frag1;
			frag1 = bufsize - old_offset;
			cpnt = big_data_block;
			memcpy(cpnt, data_block + old_offset, frag1);

			de = (struct iso_directory_record *) cpnt;
			offset = f_pos & (bufsize - 1);
			block = iso_bmap(dir,f_pos>>bufbits);
			if (!block) return -1;
                        if (iso_dev_read(data_block,
					 block * sb.s_blocksize,
					 sb.s_blocksize)
				!= sb.s_blocksize) return 0;
			memcpy((char *)cpnt+frag1, data_block, offset);
		}
		
		/* Handle the '.' case */
		
		if (de->name[0]==0 && de->name_len[0]==1) {
		  inode_number = itp->inumber;
		  backlink = 0;
		}
		
		/* Handle the '..' case */

		if (de->name[0]==1 && de->name_len[0]==1) {

		  if((int) sb.s_firstdatazone != itp->inumber)
  		    inode_number = (isonum_733(de->extent) + 
			  	    isonum_711(de->ext_attr_length))
				    << sb.s_log_zone_size;
		  else
		    inode_number = itp->inumber;
		  backlink = 0;
		}
    
		{
		  /* should be sufficient, since get_rock_ridge_filename
		   * truncates at 254 chars */
		  char retname[256]; 
		  dlen = get_rock_ridge_filename(de, retname, dir);
		  if (dlen) {
		    strcpy(de->name, retname);
		  } else {
		    dlen = isonum_711(de->name_len);
		    if(sb.s_mapping == 'n') {
		      for (i = 0; i < dlen; i++) {
			c = de->name[i];
			if (c >= 'A' && c <= 'Z') c |= 0x20;  /* lower case */
			if (c == ';' && i == dlen-2
			    && de->name[i+1] == '1') {
			  dlen -= 2;
			  break;
			}
			if (c == ';') c = '.';
			de->name[i] = c;
		      }
		      /* This allows us to match with and without a trailing
			 period.  */
		      if(de->name[dlen-1] == '.' && namelen == dlen-1)
			dlen--;
		    }
		  }
		}
		/*
		 * Skip hidden or associated files unless unhide is set
		 */
		match = 0;
		if(   !(de->flags[-sb.s_high_sierra] & 5)
		   || sb.s_unhide == 'y' ) 
		{
		  match = iso_match(namelen, name, de->name, dlen);
		}

		if (cpnt) cpnt = NULL;

		if (match) {
		  if ((int) inode_number == -1) {
		    /* Should never happen */
		    printf("iso9660: error inode_number = -1\n");
		    return -1;
		  }

		  *ino = inode_number;
		  *ino_back = backlink;
#ifdef DEBUG_ISO
		  printf("iso_find_entry returning successfully (ino = %d)\n",
			 inode_number);
#endif
		  return 0;
		}
	}
#ifdef DEBUG_ISO
		  printf("iso_find_entry returning unsuccessfully (ino = %d)\n",
			 inode_number);
#endif
	return -1;
}


/*
 *  Look up name in the current directory and return its corresponding
 *  inode if it can be found.
 *
 */
struct iso_inode *
iso_lookup(struct iso_inode *dir, const char *name)
{
	struct inode_table_entry *itp = (struct inode_table_entry *) dir;
	unsigned long ino, ino_back;
	struct iso_inode *result = NULL;
	int first, last;

#ifdef DEBUG_ISO
	printf("iso_lookup: %s\n", name);
#endif

	/* is the current inode a directory? */
	if (!S_ISDIR(itp->mode)) {
#ifdef DEBUG_ISO
		printf("iso_lookup: inode %d not a directory\n", itp->inumber);
#endif
		iso_iput(dir);
		return NULL;
	}

	/* work through the name finding each directory in turn */
	ino = 0;
	first = last = 0;
	while (last < (int) strlen(name)) {
		if (name[last] == '/') {
			if (iso_find_entry(dir, &name[first], last - first, 
					   &ino, &ino_back)) 
				return NULL;
			/* throw away the old directory inode, we
			   don't need it anymore */
			iso_iput(dir);

			if (!(dir = iso_iget(ino))) 
				return NULL;
			first = last + 1;
			last = first;
		} else
			last++;
	}
	{
		int rv;
		if ((rv = iso_find_entry(dir, &name[first], last - first, &ino, &ino_back))) {
			iso_iput(dir);
			return NULL;
		}
	}
	if (!(result = iso_iget(ino))) {
		iso_iput(dir);
		return NULL;
	}
	/*
	 * We need this backlink for the ".." entry unless the name
	 * that we are looking up traversed a mount point (in which
	 * case the inode may not even be on an iso9660 filesystem,
	 * and writing to u.isofs_i would only cause memory
	 * corruption).
	 */
	result->i_backlink = ino_back; 
	
	iso_iput(dir);
	return result;
}

/* follow a symbolic link, returning the inode of the file it points to */
static struct iso_inode *
iso_follow_link(struct iso_inode *from, const char *basename) 
{
	struct inode_table_entry *itp = (struct inode_table_entry *)from;
	struct iso_inode *root = iso_iget(root_inode);
	struct iso_inode *result = NULL;
	char *linkto;
	
#ifdef DEBUG_ISO
	printf("iso_follow_link(%s): ",basename);
#endif

	if (!S_ISLNK(itp->mode)) /* Hey, that's not a link! */
		return NULL;

	if (!itp->size) 
		return NULL;
	
	if (!(linkto = get_rock_ridge_symlink(from)))
		return NULL;

	linkto[itp->size]='\0';
#ifdef DEBUG_ISO
	printf("%s->%s\n",basename,linkto ? linkto : "[failed]");
#endif
	
	/* Resolve relative links. */

	if (linkto[0] !='/') {
		char *end = strrchr(basename, '/');
		if (end) {
			char fullname[(end - basename + 1) + strlen(linkto) + 1];
			strncpy(fullname, basename, end - basename + 1);
			fullname[end - basename + 1] = '\0';
			strcat(fullname, linkto);
#ifdef DEBUG_ISO
			printf("resolved to %s\n", fullname);
#endif
			result = iso_lookup(root,fullname);
		} else {
			/* Assume it's in the root */
			result = iso_lookup(root,linkto);
		}
	} else {
		result = iso_lookup(root,linkto);
	}
	free(linkto);
	iso_iput(root);
	return result;
}

/*
 * look if the driver can tell the multi session redirection value
 */
static inline unsigned int
iso_get_last_session (void)
{
#ifdef DEBUG_ISO 
	printf("iso_get_last_session() called\n");
#endif	
	return 0;
}


int
iso_read_super (void *data, int silent)
{
	static int first_time = 1;
	int high_sierra;
	unsigned int iso_blknum, vol_desc_start;
	char rock = 'y';
	int i;

	struct iso_volume_descriptor *vdp;
	struct hs_volume_descriptor *hdp;

	struct iso_primary_descriptor *pri = NULL;
	struct hs_primary_descriptor *h_pri = NULL;

	struct iso_directory_record *rootp;

	/* Initialize the inode table */
	for (i = 0; i < MAX_OPEN_FILES; i++) {
		inode_table[i].free = 1;
		inode_table[i].inumber = 0;
	}

#ifdef DEBUG_ISO 
	printf("iso_read_super() called\n");
#endif	

	/* set up the block size */
	sb.s_blocksize = 1024;
	sb.s_blocksize_bits = 10;

	sb.s_high_sierra = high_sierra = 0; /* default is iso9660 */

	vol_desc_start = iso_get_last_session();
	
	for (iso_blknum = vol_desc_start+16; iso_blknum < vol_desc_start+100; 
	     iso_blknum++) {
#ifdef DEBUG_ISO
		printf("iso_read_super: iso_blknum=%d\n", iso_blknum);
#endif
		if (iso_dev_read(data_block, iso_blknum * 2048,
				 sb.s_blocksize) != sb.s_blocksize)
		{
			printf("iso_read_super: bread failed, dev "
			       "iso_blknum %d\n", iso_blknum);
			return -1;
		}
		vdp = (struct iso_volume_descriptor *)data_block;
		hdp = (struct hs_volume_descriptor *)data_block;

		if (strncmp (hdp->id, HS_STANDARD_ID, sizeof hdp->id) == 0) {
			if (isonum_711 (hdp->type) != ISO_VD_PRIMARY)
				return -1;
			if (isonum_711 (hdp->type) == ISO_VD_END)
				return -1;

			sb.s_high_sierra = 1;
			high_sierra = 1;
			rock = 'n';
			h_pri = (struct hs_primary_descriptor *)vdp;
			break;
		}

		if (strncmp (vdp->id, ISO_STANDARD_ID, sizeof vdp->id) == 0) {
			if (isonum_711 (vdp->type) != ISO_VD_PRIMARY)
				return -1;
			if (isonum_711 (vdp->type) == ISO_VD_END)
				return -1;
			
			pri = (struct iso_primary_descriptor *)vdp;
			break;
		}
	}
	if(iso_blknum == vol_desc_start + 100) {
		if (!silent)
			printf("iso: Unable to identify CD-ROM format.\n");
		return -1;
	}
	
	if (high_sierra) {
		rootp = (struct iso_directory_record *)
			h_pri->root_directory_record;
		if (isonum_723 (h_pri->volume_set_size) != 1) {
			printf("Multi-volume disks not (yet) supported.\n");
			return -1;
		};
		sb.s_nzones = isonum_733 (h_pri->volume_space_size);
		sb.s_log_zone_size = 
			isonum_723 (h_pri->logical_block_size);
		sb.s_max_size = isonum_733(h_pri->volume_space_size);
	} else {
		rootp = (struct iso_directory_record *)
			pri->root_directory_record;
		if (isonum_723 (pri->volume_set_size) != 1) {
			printf("Multi-volume disks not (yet) supported.\n");
			return -1;
		}
		sb.s_nzones = isonum_733 (pri->volume_space_size);
		sb.s_log_zone_size = isonum_723 (pri->logical_block_size);
		sb.s_max_size = isonum_733(pri->volume_space_size);
	}

	sb.s_ninodes = 0; /* No way to figure this out easily */

	/* RDE: convert log zone size to bit shift */

	switch (sb.s_log_zone_size) {
	      case  512: sb.s_log_zone_size =  9; break;
	      case 1024: sb.s_log_zone_size = 10; break;
	      case 2048: sb.s_log_zone_size = 11; break;

	      default:
		printf("Bad logical zone size %ld\n", sb.s_log_zone_size);
		return -1;
	}

	/* RDE: data zone now byte offset! */

	sb.s_firstdatazone = (isonum_733( rootp->extent) 
					 << sb.s_log_zone_size);
	/*
	 * The CDROM is read-only, has no nodes (devices) on it, and
	 * since all of the files appear to be owned by root, we
	 * really do not want to allow suid.  (suid or devices will
	 * not show up unless we have Rock Ridge extensions).
	 */
	if (first_time) {
		first_time = 0;
		printf("iso: Max size:%ld   Log zone size:%ld\n",
		       sb.s_max_size, 1UL << sb.s_log_zone_size);
		printf("iso: First datazone:%ld   Root inode number %d\n",
		       sb.s_firstdatazone >> sb.s_log_zone_size,
		       isonum_733 (rootp->extent) << sb.s_log_zone_size);
		if (high_sierra)
			printf("iso: Disc in High Sierra format.\n");
	}

	/* set up enough so that it can read an inode */

	sb.s_mapping = 'n';
	sb.s_rock = (rock == 'y' ? 1 : 0);
	sb.s_conversion = 'b';
	sb.s_cruft = 'n';
	sb.s_unhide = 'n';
	/*
	 * It would be incredibly stupid to allow people to mark every file 
	 * on the disk as suid, so we merely allow them to set the default 
	 * permissions.
	 */
	sb.s_mode = S_IRUGO & 0777;

	/* return successfully */
	root_inode = isonum_733 (rootp->extent) << sb.s_log_zone_size;
	return 0;
}


int
iso_bread (int fd, long blkno, long nblks, char * buffer)
{
	struct iso_inode *inode;

	/* find the inode for this file */
	inode = &inode_table[fd].inode;
	return iso_breadi(inode, blkno, nblks, buffer); 
}


int
iso_open (const char *filename)
{
	struct iso_inode *inode, *oldinode;
	struct iso_inode *root;

	/* get the root directory */
	root = iso_iget(root_inode);
	if (!root) {
		printf("iso9660: get root inode failed\n");
		return -1;
	}

	/* lookup the file */
	inode = iso_lookup(root, filename);

#ifdef DEBUG_ISO
	if (inode && S_ISLNK(((struct inode_table_entry *)inode)->mode)) {
		printf("%s is a link (len %u)\n",filename,
		       ((struct inode_table_entry *)inode)->size);
	} else {
		printf("%s is not a link\n",filename);
	}
#endif

	while ((inode) && (S_ISLNK(((struct inode_table_entry *)inode)->mode))) {
		oldinode = inode;
		inode = iso_follow_link(oldinode, filename);
		iso_iput(oldinode);
	}
	if (inode == NULL) 
		return -1;
	else {
		struct inode_table_entry * itp =
			(struct inode_table_entry *) inode;
		return (itp - inode_table);
	}
}


void
iso_close (int fd)
{
	iso_iput(&inode_table[fd].inode);
}


long
iso_map (int fd, long block)
{
	return iso_bmap(&inode_table[fd].inode, block) * sb.s_blocksize;
}

#include <linux/stat.h>
int
iso_fstat (int fd, struct stat * buf)
{
	struct inode_table_entry * ip = inode_table + fd;

	if (fd >= MAX_OPEN_FILES)
		return -1;

	memset(buf, 0, sizeof(struct stat));
	/* fill in relevant fields */
	buf->st_ino   = ip->inumber;
	buf->st_mode  = ip->mode;
	buf->st_nlink = ip->nlink;
	buf->st_size  = ip->size;
	return 0;
}

/*
 * NOTE: mixing calls to this and calls to any other function that reads from
 * the filesystem will clobber the buffers and cause much wailing and gnashing
 * of teeth.
 *
 * Sorry this function is so ugly. It was written as a way for me to
 * learn how the ISO filesystem stuff works. 
 * 
 * Will Woods, 2/2001
 *
 * Uses data_block
 */
char *iso_readdir_i(int fd, int rewind) {
    struct inode_table_entry *itp = &(inode_table[fd]);
    struct iso_directory_record *dirent = 0;
    unsigned int fraglen = 0, block, dirent_len, name_len = 0, oldoffset;
    static unsigned int blockoffset = 0, diroffset = 0;
    
    if (!S_ISDIR(itp->mode)) {
	printf("Not a directory\n");
	return NULL;
    }
    
    /* Initial read to this directory, get the first block */
    if (rewind) {
	blockoffset = diroffset = 0;
	block = iso_bmap(&itp->inode,0); 
#ifdef DEBUG_ISO
	printf("fd #%d, inode %d, first_extent %d, block %u\n",
	       fd,itp->inumber,itp->inode.i_first_extent,block);
#endif
	if (!block) return NULL;
	
	if (iso_dev_read(data_block, block * sb.s_blocksize, sb.s_blocksize) 
	    != sb.s_blocksize) return NULL;
    }

    /* keep doing this until we get a filename or we fail */ 
    while (!name_len) {
	/* set up our dirent pointer into the block of data we've read */
	dirent = (struct iso_directory_record *) (data_block + blockoffset);
	dirent_len = isonum_711(dirent->length);
#ifdef DEBUG_ISO
	printf("diroffset=%u, blockoffset=%u, length=%u\n",
	       diroffset,blockoffset,dirent_len);
#endif
    
	/* End of directory listing or end of sector */
	if (dirent_len == 0) { 
	    /* round diroffset up to the nearest blocksize */
	    diroffset = ((diroffset & ~(ISOFS_BLOCK_SIZE - 1))
			 + ISOFS_BLOCK_SIZE);
#ifdef DEBUG_ISO
	    printf("dirent_len == 0. diroffset=%u, itp->size=%u. ",
		   diroffset,itp->size);
#endif
	    if (diroffset >= itp->size) {
#ifdef DEBUG_ISO
		printf("End of directory.\n");
#endif
		return NULL;
	    } else {
#ifdef DEBUG_ISO
		printf("End of sector. Need next block.\n");
#endif
		/* Get the next block. */
		block = iso_bmap(&itp->inode, diroffset>>sb.s_blocksize_bits);
		if (!block) return NULL;
		if (iso_dev_read(data_block, block * sb.s_blocksize, sb.s_blocksize) 
		    != sb.s_blocksize) return NULL;

		/* set the offsets and the pointers properly */
		blockoffset = 0;
		dirent = (struct iso_directory_record *) data_block;
		dirent_len = isonum_711(dirent->length);
#ifdef DEBUG_ISO
		printf("diroffset=%u, blockoffset=%u, length=%u\n",
		       diroffset,blockoffset,dirent_len);
#endif
	    }
	}

	/* update the offsets for the next read */
	oldoffset = blockoffset;
	blockoffset += dirent_len;
	diroffset += dirent_len;
	
	/* 
	 * directory entry spans two blocks - 
	 * get next block and glue the two halves together 
	 */
	if (blockoffset >= sb.s_blocksize) {
	    fraglen = sb.s_blocksize - oldoffset; 
#ifdef DEBUG_ISO
	    printf("fragmented block: blockoffset = %u, fraglen = %u\n",
		   blockoffset, fraglen);
#endif
	    /* copy the fragment at end of old block to front of new buffer */
	    memcpy(big_data_block, data_block + oldoffset, fraglen);
	    
	    /* read the next block into the buffer after the old fragment */
	    block = iso_bmap(&itp->inode, diroffset >> sb.s_blocksize_bits);
	    if (!block) return NULL;
	    if (iso_dev_read(big_data_block + fraglen, block * sb.s_blocksize, sb.s_blocksize) 
		!= sb.s_blocksize) return NULL;
#ifdef DEBUG_ISO
	    printf("read %u bytes from offset %u\n",
		   sb.s_blocksize, block * sb.s_blocksize);
#endif
	    
	    blockoffset = 0;
	    dirent = (struct iso_directory_record *) big_data_block;
	}
	
	/* 
	 * Everything's cool, let's get the filename. 
	 * First we need to figure out the length. 
	 */
	name_len = isonum_711(dirent->name_len);
#ifdef DEBUG_ISO
	if (name_len==0) printf("dirent->name_len = 0, skipping.\n");
#endif
	
	/* skip '.' and '..' */
	if (name_len == 1) {
	    if (dirent->name[0] == (char)0) name_len = 0;
	    if (dirent->name[0] == (char)1) name_len = 0;
	} 


	if (sb.s_unhide == 'n') {
	    /* sb.s_high_sierra is the offset for the position of the flags.
	       this adjusts for differences between iso9660 and high sierra.
	       
	       if bit 0 (exists) or bit 2 (associated) are set, we ignore
	       this record. */
	    if (dirent->flags[-sb.s_high_sierra] & 5) name_len = 0;
	}

	/* if we have a real filename here.. */
	if (name_len) {
	    /* should be sufficient, since get_rock_ridge_filename truncates
	       at 254 characters */
	    char rrname[256]; 
	    if ((name_len = get_rock_ridge_filename(dirent, rrname, &itp->inode))) {
		rrname[name_len] = '\0';
		/* it's okay if rrname is longer than dirent->name, because
		   we're just overwriting parts of the now-useless dirent */
		strcpy(dirent->name, rrname); 
	    } else {
	        int i;
	        char c;
		if (sb.s_mapping == 'n') { /* downcase the name */
		    for (i = 0; i < name_len; i++) {
			c = dirent->name[i];
			
                        /* lower case */ 
			if ((c >= 'A') && (c <= 'Z')) c |= 0x20; 
			
			/* Drop trailing '.;1' */
			if ((c == '.') && (i == name_len-3) && 
			    (dirent->name[i+1] == ';') && 
                            (dirent->name[i+2] == '1')) {
			    name_len -= 3 ; break; 
			}
			
			/* Drop trailing ';1' */
			if ((c == ';') && (i == name_len-2) && 
                            (dirent->name[i+1] == '1')) {
			    name_len -= 2; break;
			}
			
			/* convert ';' to '.' */
			if (c == ';') 
			    c = '.';
			
			dirent->name[i] = c;
		    }
		    dirent->name[name_len] = '\0';
		}
	    }
	}
	/* now that we're done using it, and it's smaller than a full block, 
	 * copy big_data_block back into data_block */
	if (fraglen) { 
            int len = sb.s_blocksize - dirent_len;
	    memcpy(data_block, big_data_block + dirent_len, len);
#ifdef DEBUG_ISO
	    printf("copied %u bytes of data back to data_block\n", len);
#endif
	    blockoffset = 0;
	    fraglen = 0;
	}
    }
    return dirent->name;
}

/********************************************************************** 
 * 
 * Rock Ridge functions and definitions, from the Linux kernel source.
 * linux/fs/isofs/rock.c, (c) 1992, 1993 Eric Youngdale.
 * 
 **********************************************************************/

#define SIG(A,B) ((A << 8) | B)

/* This is a way of ensuring that we have something in the system
   use fields that is compatible with Rock Ridge */
#define CHECK_SP(FAIL)	       			\
      if(rr->u.SP.magic[0] != 0xbe) FAIL;	\
      if(rr->u.SP.magic[1] != 0xef) FAIL;

/* We define a series of macros because each function must do exactly the
   same thing in certain places.  We use the macros to ensure that everything
   is done correctly */

#define CONTINUE_DECLS \
  int cont_extent = 0, cont_offset = 0, cont_size = 0;   \
  void * buffer = 0

#define CHECK_CE	       			\
      {cont_extent = isonum_733(rr->u.CE.extent); \
      cont_offset = isonum_733(rr->u.CE.offset); \
      cont_size = isonum_733(rr->u.CE.size);}

#define SETUP_ROCK_RIDGE(DE,CHR,LEN)	      		      	\
  {LEN= sizeof(struct iso_directory_record) + DE->name_len[0];	\
  if(LEN & 1) LEN++;						\
  CHR = ((unsigned char *) DE) + LEN;				\
  LEN = *((unsigned char *) DE) - LEN;}

#define MAYBE_CONTINUE(LABEL) \
  {if (buffer) free(buffer); \
  if (cont_extent){ \
    buffer = malloc(cont_size); \
    if (!buffer) goto out; \
    if (iso_dev_read(buffer, cont_extent * ISOFS_BLOCK_SIZE + cont_offset, cont_size) \
	  == cont_size) { \
        chr = (unsigned char *) buffer; \
        len = cont_size; \
        cont_extent = cont_size = cont_offset = 0; \
        goto LABEL; \
    }; \
    printf("Unable to read rock-ridge attributes\n");    \
  }}

int get_rock_ridge_filename(struct iso_directory_record * de,
			    char * retname,
			    struct iso_inode * inode)
{
  int len;
  unsigned char * chr;
  int retnamlen = 0, truncate=0;
  int cont_extent = 0, cont_offset = 0, cont_size = 0;
  void *buffer = 0;

  /* No rock ridge? well then... */
  if (!sb.s_rock) return 0;
  *retname = '\0';

  len = sizeof(struct iso_directory_record) + isonum_711(de->name_len);
  if (len & 1) len++;
  chr = ((unsigned char *) de) + len;
  len = *((unsigned char *) de) - len;
  {
    struct rock_ridge * rr;
    int sig;
    
  repeat:
    while (len > 1){ /* There may be one byte for padding somewhere */
      rr = (struct rock_ridge *) chr;
      if (rr->len == 0) break; /* Something got screwed up here */

      sig = (chr[0] << 8) + chr[1];
      chr += rr->len; 
      len -= rr->len;

      switch(sig){
      case SIG('R','R'):
	  if((rr->u.RR.flags[0] & RR_NM) == 0) goto out;
	  break;
      case SIG('S','P'):
	  if (rr->u.SP.magic[0] != 0xbe ||
	      rr->u.SP.magic[1] != 0xef)
	      goto out;
	  break;
      case SIG('C','E'):
	  cont_extent = isonum_733(rr->u.CE.extent);
	  cont_offset = isonum_733(rr->u.CE.offset);
	  cont_size = isonum_733(rr->u.CE.size);
	  break;
      case SIG('N','M'):
	  if (truncate) break;
        /*
	 * If the flags are 2 or 4, this indicates '.' or '..'.
	 * We don't want to do anything with this, because it
	 * screws up the code that calls us.  We don't really
	 * care anyways, since we can just use the non-RR
	 * name.
	 */
	if (rr->u.NM.flags & 6) {
	    break;
	}

	if (rr->u.NM.flags & ~1) {
	  printf("Unsupported NM flag settings (%d)\n",rr->u.NM.flags);
	  break;
	};
	if((strlen(retname) + rr->len - 5) >= 254) {
	    int i = 254-strlen(retname);
	    strncat(retname, rr->u.NM.name, i); 
	    retnamlen += i;
	    truncate = 1;
	    break;
	};
	strncat(retname, rr->u.NM.name, rr->len - 5);
	retnamlen += rr->len - 5;
	break;
      case SIG('R','E'):
	  goto out;
      default:
	  break;
      }
    };
  }
  if (buffer) free(buffer);
  if (cont_extent) { /* we had a continued record */
      buffer = malloc(cont_size);
      if (!buffer) goto out;
      if (iso_dev_read(buffer, cont_extent * ISOFS_BLOCK_SIZE + cont_offset, cont_size) 
	  != cont_size) goto out;
      chr = buffer + cont_offset;
      len = cont_size;
      cont_extent = cont_size = cont_offset = 0;
      goto repeat;
  }
  return retnamlen; /* If 0, this file did not have a NM field */
 out:
  if (buffer) free(buffer);
  return 0;
}

static int parse_rock_ridge_inode(struct iso_directory_record * de,
				  struct iso_inode * inode){
  int len;
  unsigned char *chr;
  int symlink_len = 0;
  struct inode_table_entry *itp = (struct inode_table_entry *) inode;
  CONTINUE_DECLS;

#ifdef DEBUG_ROCK
  printf("parse_rock_ridge_inode(%u)\n",itp->inumber);
#endif

  if (!sb.s_rock) return 0;

  SETUP_ROCK_RIDGE(de, chr, len);
 repeat:
  {
    int sig;
    /* struct iso_inode * reloc; */
    struct rock_ridge * rr;
    int rootflag;
    
    while (len > 1){ /* There may be one byte for padding somewhere */
      rr = (struct rock_ridge *) chr;
      if (rr->len == 0) goto out; /* Something got screwed up here */
      sig = (chr[0] << 8) + chr[1];
      chr += rr->len; 
      len -= rr->len;
      
      switch(sig){
      case SIG('R','R'):
#ifdef DEBUG_ROCK
  printf("RR ");
#endif
	if((rr->u.RR.flags[0] & 
 	    (RR_PX | RR_TF | RR_SL | RR_CL)) == 0) goto out;
	break;
      case SIG('S','P'):
#ifdef DEBUG_ROCK
  printf("SP ");
#endif
	CHECK_SP(goto out);
	break;
      case SIG('C','E'):
#ifdef DEBUG_ROCK
  printf("CE ");
#endif
	CHECK_CE;
	break;
      case SIG('E','R'):
#ifdef DEBUG_ROCK
	printf("ISO 9660 Extensions: ");
	{ int p;
	  for(p=0;p<rr->u.ER.len_id;p++) printf("%c",rr->u.ER.data[p]);
	};
	printf("\n");
#endif
	break;
      case SIG('P','X'):
#ifdef DEBUG_ROCK
  printf("PX ");
#endif
	itp->mode  = isonum_733(rr->u.PX.mode);
	itp->nlink = isonum_733(rr->u.PX.n_links);
	/* Ignore uid and gid. We're only a simple bootloader, after all. */
	break;
      case SIG('P','N'):
	/* Ignore device files. */
	break;
      case SIG('T','F'):
        /* create/modify/access times are uninteresting to us. */
	break;
      case SIG('S','L'):
#ifdef DEBUG_ROCK
  printf("SL ");
#endif
	{int slen;
	 struct SL_component * slp;
	 struct SL_component * oldslp;
	 slen = rr->len - 5;
	 slp = &rr->u.SL.link;
	 itp->size = symlink_len;
	 while (slen > 1){
	   rootflag = 0;
	   switch(slp->flags &~1){
	   case 0:
	     itp->size += slp->len;
	     break;
	   case 2:
	     itp->size += 1;
	     break;
	   case 4:
	     itp->size += 2;
	     break;
	   case 8:
	     rootflag = 1;
	     itp->size += 1;
	     break;
	   default:
	     printf("Symlink component flag not implemented\n");
	   };
	   slen -= slp->len + 2;
	   oldslp = slp;
	   slp = (struct SL_component *) (((char *) slp) + slp->len + 2);

	   if(slen < 2) {
	     if(    ((rr->u.SL.flags & 1) != 0) 
		    && ((oldslp->flags & 1) == 0) ) itp->size += 1;
	     break;
	   }

	   /*
	    * If this component record isn't continued, then append a '/'.
	    */
	   if(   (!rootflag)
		 && ((oldslp->flags & 1) == 0) ) itp->size += 1;
	 }
	}
	symlink_len = itp->size;
	break;
      case SIG('R','E'):
	printf("Attempt to read inode for relocated directory\n");
	goto out;
      case SIG('C','L'):
#ifdef DEBUG_ROCK
  printf("CL(!) ");
#endif
        /* I'm unsure as to the function of this signature.
	   We'll ignore it and hope that everything will be OK.
	*/
#if 0
#ifdef DEBUG
	printf("RR CL (%x)\n",inode->i_ino);
#endif
	inode->inode.first_extent = isonum_733(rr->u.CL.location);
	reloc = iso_iget(inode->i_sb,
			 (inode->u.isofs_i.i_first_extent <<
			  inode -> i_sb -> u.isofs_sb.s_log_zone_size));
	if (!reloc)
		goto out;
	inode->mode = reloc->mode;
	inode->nlink = reloc->nlink;
	inode->size = reloc->size;
	iso_iput(reloc);
#endif /* 0 */
	break;
      default:
	break;
      }
    };
  }
  MAYBE_CONTINUE(repeat);
#ifdef DEBUG_ROCK
  printf("\nparse_rock_ridge_inode(): ok\n");
#endif
  return 1;
 out:
  if(buffer) free(buffer);
#ifdef DEBUG_ROCK
  printf("\nparse_rock_ridge_inode(): failed\n");
#endif
  return 0;
}

/* Returns the name of the file that this inode is symlinked to.  This is
   in malloc memory, so we have to free it when we're done */

static char * get_rock_ridge_symlink(struct iso_inode *inode)
{
  int blocksize = ISOFS_BLOCK_SIZE;
  int blockbits = ISOFS_BLOCK_BITS;
  char * rpnt = NULL;
  unsigned char * pnt;
  struct iso_directory_record * raw_inode;
  struct inode_table_entry *itp = (struct inode_table_entry *)inode;
  CONTINUE_DECLS;
  int block, blockoffset;
  int sig;
  int rootflag;
  int len;
  unsigned char * chr, * buf = NULL;
  struct rock_ridge * rr;

#ifdef DEBUG_ROCK
  printf("get_rock_ridge_symlink(%u): link is %u bytes long\n",itp->inumber, itp->size);
#endif
  
  if (!sb.s_rock) goto out;

  block = itp->inumber >> blockbits;
  blockoffset = itp->inumber & (blocksize - 1);

  buf=malloc(blocksize);

  if (iso_dev_read(buf, block << blockbits, blocksize) != blocksize)
	  goto out_noread;

  pnt = ((unsigned char *) buf) + blockoffset;
  
  raw_inode = ((struct iso_directory_record *) pnt);
  
  /*
   * If we go past the end of the buffer, there is some sort of error.
   */
  if (blockoffset + *pnt > blocksize)
	goto out_bad_span;
  
  /* Now test for possible Rock Ridge extensions which will override some of
     these numbers in the inode structure. */
  
  SETUP_ROCK_RIDGE(raw_inode, chr, len);

 repeat:
  while (len > 1){ /* There may be one byte for padding somewhere */
    rr = (struct rock_ridge *) chr;
    if (rr->len == 0) goto out; /* Something got screwed up here */
    sig = (chr[0] << 8) + chr[1];
    chr += rr->len; 
    len -= rr->len;
    
#ifdef DEBUG_ROCK
    printf("%c%c ",chr[0],chr[1]);
#endif
    switch(sig){
    case SIG('R','R'):
      if((rr->u.RR.flags[0] & RR_SL) == 0) goto out;
      break;
    case SIG('S','P'):
      CHECK_SP(goto out);
      break;
    case SIG('S','L'):
      {int slen;
       struct SL_component * oldslp;
       struct SL_component * slp;
       slen = rr->len - 5;
       slp = &rr->u.SL.link;
       while (slen > 1){
	 if (!rpnt){
	   rpnt = (char *) malloc (itp->size +1);
	   if (!rpnt) goto out;
	   *rpnt = 0;
	 };
	 rootflag = 0;
	 switch(slp->flags &~1){
	 case 0:
	   strncat(rpnt,slp->text, slp->len);
	   break;
	 case 2:
	   strcat(rpnt,".");
	   break;
	 case 4:
	   strcat(rpnt,"..");
	   break;
	 case 8:
	   rootflag = 1;
	   strcat(rpnt,"/");
	   break;
	 default:
#ifdef DEBUG_ROCK
	   printf("Symlink component flag not implemented (%d)\n",slen);
#endif
	   break;
	 };
	 slen -= slp->len + 2;
	 oldslp = slp;
	 slp = (struct SL_component *) (((char *) slp) + slp->len + 2);

	 if(slen < 2) {
	   /*
	    * If there is another SL record, and this component record
	    * isn't continued, then add a slash.
	    */
	   if(    ((rr->u.SL.flags & 1) != 0) 
	       && ((oldslp->flags & 1) == 0) ) strcat(rpnt,"/");
	   break;
	 }

	 /*
	  * If this component record isn't continued, then append a '/'.
	  */
	 if(   (!rootflag)
	    && ((oldslp->flags & 1) == 0) ) strcat(rpnt,"/");

       };
       break;
     case SIG('C','E'):
       CHECK_CE; /* This tells is if there is a continuation record */
       break;
     default:
       break;
     }
    };
  };
  MAYBE_CONTINUE(repeat);
  
 out_freebh:
#ifdef DEBUG_ROCK
  printf("\nget_rock_ridge_symlink() exiting\n");
#endif
        if (buf)
		free(buf);
	return rpnt;

	/* error exit from macro */
out:
#ifdef DEBUG_ROCK
	printf("abort");
#endif
	if(buffer)
		free(buffer);
	if(rpnt)
		free(rpnt);
	rpnt = NULL;
	goto out_freebh;
out_noread:
	printf("unable to read block");
	goto out_freebh;
out_bad_span:
	printf("symlink spans iso9660 blocks\n");
	goto out_freebh;
}



