/*
 * Functions to read an ISO image.
 */
 
 /*
  * TODO 
  * we need some kind of force option, to continue reading image on
  * minor errors, such as incorrect time stamps....
  * 
  * TODO
  * need to check the ZF linux-especific extension for transparent decompresion
  * TODO
  * what the RR entry is?
  */

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "ecma119_read.h"
#include "ecma119_read_rr.h"
#include "ecma119.h"
#include "util.h"
#include "volume.h"
#include "tree.h"
#include "messages.h"
#include "eltorito.h"

#define BLOCK_SIZE 2048

static int 
iso_read_dir(struct iso_read_info *info, struct iso_tree_node_dir *parent, 
             uint32_t block);

static struct el_torito_boot_catalog *
read_el_torito_boot_catalog(struct iso_read_info *info, uint32_t block)
{
	struct el_torito_validation_entry *ve;
	struct el_torito_default_entry *entry;
	struct el_torito_boot_catalog *catalog;
	struct el_torito_boot_image *image;
	unsigned char buffer[BLOCK_SIZE];
	
	if ( info->src->read_block(info->src, block, buffer) < 0 ) {
		info->error = LIBISOFS_READ_FAILURE;
		return NULL;
	}
	
	ve = (struct el_torito_validation_entry*)buffer;
	
	/* check if it is a valid catalog (TODO: check also the checksum)*/
	if ( (ve->header_id[0] != 1) || (ve->key_byte1[0] != 0x55) 
	     || (ve->key_byte2[0] != 0xAA) ) {
	     	
		iso_msg_sorry(LIBISO_EL_TORITO_WRONG, "Wrong or damaged El-Torito " 
	     			  "Catalog.\n El-Torito info will be ignored.");
    	return NULL;
	}
	
	/* check for a valid platform */
	if (ve->platform_id[0] != 0) {
		iso_msg_hint(LIBISO_EL_TORITO_UNHANLED, "Unsupported El-Torito platform.\n"
			"Only 80x86 si supported. El-Torito info will be ignored.");
	}
	
	/* ok, once we are here we assume it is a valid catalog */
	catalog = malloc(sizeof(struct el_torito_boot_catalog));
	image = calloc(1, sizeof(struct el_torito_boot_image));
	catalog->image = image;
	catalog->proc = LIBISO_PREVIMG;
	
	{
	/* 
	 * Create the placeholder.
	 * Note that this could be modified later if we find a directory entry
	 * for the catalog in the iso tree.
	 */
	struct iso_tree_node_boot *boot;
	boot = calloc(1, sizeof(struct iso_tree_node_boot));
	boot->node.refcount = 1;
	boot->node.attrib.st_mode = S_IFREG | 0777;
	boot->node.attrib.st_atime = boot->node.attrib.st_mtime 
		= boot->node.attrib.st_ctime = time(NULL);
	boot->node.attrib.st_size = 2048;
	boot->node.type = LIBISO_NODE_BOOT;
	boot->node.procedence = LIBISO_PREVIMG;
	boot->node.name = NULL;
	boot->loc.block = block;
	catalog->node = boot;
	}
	
	/* parse the default entry */
	entry = (struct el_torito_default_entry *)(buffer + 32);
	
	image->bootable = entry->boot_indicator[0] ? 1 : 0;
	//FIXME we need a way to handle patch_isolinux in ms images!!!
	image->isolinux = 0;
	image->type = entry->boot_media_type[0];
	image->partition_type = entry->system_type[0];
	image->load_seg = iso_read_lsb(entry->load_seg, 2);
	image->load_size = iso_read_lsb(entry->sec_count, 2);
	
	{
	/* 
	 * Create the placeholder.
	 * Note that this could be modified later if we find a directory entry
	 * for the image in the iso tree.
	 */
	struct iso_tree_node_boot *boot;
	boot = calloc(1, sizeof(struct iso_tree_node_boot));
	boot->node.refcount = 1;
	boot->node.attrib.st_mode = S_IFREG | 0777;
	boot->node.attrib.st_atime = boot->node.attrib.st_mtime 
		= boot->node.attrib.st_ctime = time(NULL);
	boot->node.attrib.st_size = 2048;
	boot->node.type = LIBISO_NODE_BOOT;
	boot->node.procedence = LIBISO_PREVIMG;
	boot->node.name = NULL;
	boot->img = 1;
	boot->loc.block = iso_read_lsb(entry->block, 4);
	image->node = boot;
	}
	
	//TODO how can we check if there are more entries?
	
	return catalog;
}

static struct el_torito_boot_catalog *
read_el_torito_vol_desc(struct iso_read_info *info, unsigned char *buf)
{
	struct ecma119_boot_rec_vol_desc *vol;
	
	vol = (struct ecma119_boot_rec_vol_desc*)buf;
	
	/* some sanity checks */
	if ( strncmp((char*)vol->std_identifier, "CD001", 5)
	     || vol->vol_desc_version[0] != 1 
	     || strncmp((char*)vol->boot_sys_id, "EL TORITO SPECIFICATION", 23)) {
	     	
	     iso_msg_hint(LIBISO_BOOT_VD_UNHANLED, "Unsupported Boot Vol. Desc.\n"
	     			  "Only El-Torito Specification, Version 1.0 Volume " 
	     			  "Descriptors are supported. Ignoring boot info");
	     return NULL;
	}
	
	return read_el_torito_boot_catalog(info, iso_read_lsb(vol->boot_catalog, 4));
}
             
/**
 * This reads the "." directory entry, and set the properties of the
 * given directory propertly.
 */
static int
iso_read_dot_record(struct iso_read_info *info, 
                    struct iso_tree_node_dir *dir,
                    struct ecma119_dir_record *record)
{
	struct susp_sys_user_entry *sue;
	struct susp_iterator *iter;
	
	assert( info && dir && record );
	
	iter = susp_iter_new(info, record);
			
	while ( (sue = susp_iter_next(iter)) ) {
		
		/* ignore entries from different version */
		if (sue->version[0] != 1)
			continue; 
		
		/* we don't care about any RR entry but PX and TF */
		if (SUSP_SIG(sue, 'P', 'X')) {
			if (read_rr_PX(info, sue, &dir->node.attrib))
				break;
		} else if (SUSP_SIG(sue, 'T', 'F')) {
			if (read_rr_TF(info, sue, &dir->node.attrib))
				break;
		} 
	}
	
	susp_iter_free(iter);	
	
	if (info->error) 
		return -1;
	return 0;
}

/**
 * Creates a suitable iso_tree_node from a directory record, and adds
 * it to parent dir. If the directory record refers to a dir, it calls
 * recursively iso_read_dir.
 * On success, return 0.
 * If file is not supported, return 0 but a new tree node is not added
 * to parent.
 * On serious error, returns -1
 */
static int
iso_read_single_directory_record(struct iso_read_info *info, 
                             struct iso_tree_node_dir *parent,
                             struct ecma119_dir_record *record)
{
	struct iso_tree_node *node;
	struct stat atts;
	time_t recorded;
	char *name = NULL;
	char *linkdest = NULL;
	uint32_t relocated_dir = 0;
	
	assert(info && record && parent);
	
	memset(&atts, 0, sizeof(atts));
	
	/*
	 * The idea is to read all the RR entries (if we want to do that and RR
	 * extensions exist on image), storing the info we want from that. 
	 * Then, we need some sanity checks.
	 * Finally, we select what kind of node it is, and set values properly.
	 */
	
	if (info->rr) {
		struct susp_sys_user_entry *sue;
		struct susp_iterator *iter;
		
		iter = susp_iter_new(info, record);
			
		while ( (sue = susp_iter_next(iter)) ) {
			
			/* ignore entries from different version */
			if (sue->version[0] != 1)
				continue; 
			
			if (SUSP_SIG(sue, 'P', 'X')) {
				if (read_rr_PX(info, sue, &atts))
					break;
			} else if (SUSP_SIG(sue, 'T', 'F')) {
				if (read_rr_TF(info, sue, &atts))
					break;
			} else if (SUSP_SIG(sue, 'N', 'M')) {
				name = read_rr_NM(sue, name);
				if (!name) {
					info->error = LIBISOFS_WRONG_RR;
					break;
				}
			} else if (SUSP_SIG(sue, 'S', 'L')) {
				linkdest = read_rr_SL(sue, linkdest);
				if (!linkdest) {
					info->error = LIBISOFS_WRONG_RR;
					break;
				}
			} else if (SUSP_SIG(sue, 'R', 'E')) {
				/* 
				 * this directory entry refers to a relocated directory.
				 * We simply ignore it, as it will be correctly handled
				 * when found the CL
				 */
				susp_iter_free(iter);
				free(name);
				return 0; /* is not an error */
			} else if (SUSP_SIG(sue, 'C', 'L')) {
				/*
				 * This entry is a placeholder for a relocated dir.
				 * We need to ignore other entries, with the exception of NM.
				 * Then we create a directory node that represents the 
				 * relocated dir, and iterate over its children. 
				 */
				relocated_dir = iso_read_bb(sue->data.CL.child_loc, 4, NULL);
			} else if (SUSP_SIG(sue, 'S', 'F')) {
				iso_msg_sorry(LIBISO_RR_UNSUPPORTED, "Sparse files not supported.");
				info->error = LIBISOFS_UNSUPPORTED_IMAGE;
				break;
			} else if (SUSP_SIG(sue, 'R', 'R')) {
				/* TODO I've seen this RR on mkisofs images. what's this? */
				continue;
			} else {
				char msg[28];
				sprintf(msg, "Unhandled SUSP entry %c%c.", sue->sig[0], sue->sig[1]);
				iso_msg_hint(LIBISO_SUSP_UNHANLED, msg);
			}
		}
		
		if ( !info->error && !relocated_dir && atts.st_mode == (mode_t) 0 ) {
			iso_msg_sorry(LIBISO_RR_ERROR, "Mandatory Rock Ridge PX entry is "
				"not present or it contains invalid values.");
			info->error = LIBISOFS_WRONG_RR;
		}
		
		susp_iter_free(iter);	
		
		if (info->error) 
			return -1;
			
		//TODO convert name to needed charset!!
		
	} else {
		/* RR extensions are not read / used */
		atts.st_mode = info->mode;
		atts.st_gid = info->gid;
		atts.st_uid = info->uid;
		if (record->flags[0] & 0x02) 
			atts.st_mode |= S_IFDIR;
		else
			atts.st_mode |= S_IFREG;
		atts.st_ino = ++info->ino;
	}
	
	/* 
	 * if we haven't RR extensions, or no NM entry is present,
	 * we use the name in directory record
	 */
	if (!name) {
		size_t len;
		name = info->get_name((char*)record->file_id, record->len_fi[0]);
		
		/* remove trailing version number */
		len = strlen(name);
		if (len > 2 && name[len-2] == ';' && name[len-1] == '1') {
			name[len-2] = '\0';
		}
	}
	
	/* 
	 * if we haven't RR extensions, or a needed TF time stamp is not present,
	 * we use plain iso recording time
	 */
	recorded = iso_datetime_read_7(record->recording_time);
	if ( atts.st_atime == (time_t) 0 ) {
		atts.st_atime = recorded;
	}
	if ( atts.st_ctime == (time_t) 0 ) {
		atts.st_ctime = recorded;
	}
	if ( atts.st_mtime == (time_t) 0 ) {
		atts.st_mtime = recorded;
	}
	
	/* the size is read from iso directory record */
	atts.st_size = iso_read_bb(record->length, 4, NULL);
	
	if (relocated_dir) {
		/* 
		 * Ensure that a placeholder for a relocated dir appears as
		 * a directory (mode & S_IFDIR).
		 * This is need because the placeholder is really a file, and
		 * in theory PX entry must be ignored.
		 * However, to make code clearer, we don't ignore it, because
		 * anyway it will be replaced by "." entry when recursing.
		 */
		atts.st_mode = S_IFDIR | (atts.st_mode & ~S_IFMT);
	}
	
	//TODO sanity checks!!
	
	switch(atts.st_mode & S_IFMT) {
	case S_IFDIR: 
		{
		node = calloc(1, sizeof(struct iso_tree_node_dir));
		node->type = LIBISO_NODE_DIR;
		}
		break;
	case S_IFREG: 
		{
		uint32_t block;
		block = iso_read_bb(record->block, 4, NULL);
			
		if (info->bootcat && block == info->bootcat->node->loc.block) {
			/* it is the boot catalog */
			node = (struct iso_tree_node*)info->bootcat->node;
		} else if (info->bootcat && block == info->bootcat->image->node->loc.block) {
			/* it is the boot image */
			node = (struct iso_tree_node*)info->bootcat->image->node;
		} else {
			/* it is a file */
			node = calloc(1, sizeof(struct iso_tree_node_file));
			node->type = LIBISO_NODE_FILE;
			/* set block with extend */
			((struct iso_tree_node_file*)node)->loc.block = block;
		}
		}
		break;
	case S_IFLNK: 
		{
		node = calloc(1, sizeof(struct iso_tree_node_symlink));
		node->type = LIBISO_NODE_SYMLINK;
		
		/* set the link dest */
		((struct iso_tree_node_symlink*)node)->dest = linkdest;
		}
		break;
	default:
		iso_msg_sorry(LIBISO_RR_UNSUPPORTED, "File type not supported.");
		return -1;
	}
	
	node->name = name;
	node->attrib = atts;
	node->refcount++; /* 1 for news, 2 for boot nodes */
	node->procedence = LIBISO_PREVIMG;
	
	iso_tree_add_child(parent, node);
	
	if (node->type == LIBISO_NODE_DIR) {
		uint32_t block;
		if (relocated_dir)
			block = relocated_dir;
		else 
			block = iso_read_bb(record->block, 4, NULL);
		
		/* add all children */
		return iso_read_dir(info, (struct iso_tree_node_dir*)node, block);
	} else	
		return 0;
}

/**
 * Read all directory records in a directory, and creates a node for each
 * of them, adding them to \p dir.
 */
static int 
iso_read_dir(struct iso_read_info *info, struct iso_tree_node_dir *dir, 
             uint32_t block)
{
	unsigned char buffer[2048];
	struct ecma119_dir_record *record;
	uint32_t size;
	uint32_t pos = 0;
	uint32_t tlen = 0;
	
	if ( info->src->read_block(info->src, block, buffer) < 0 ) {
		info->error = LIBISOFS_READ_FAILURE;
		return -1;
	}
	
	/* Attributes of dir are set in the "." entry */
	record = (struct ecma119_dir_record *)(buffer + pos);
	size = iso_read_bb(record->length, 4, NULL);
	if (info->rr)
		iso_read_dot_record(info, dir, record);
	tlen += record->len_dr[0];
	pos += record->len_dr[0];
	
	/* skip ".." */
	record = (struct ecma119_dir_record *)(buffer + pos);
	tlen += record->len_dr[0];
	pos += record->len_dr[0];

	while( tlen < size ) {
		
		record = (struct ecma119_dir_record *)(buffer + pos);
	    if (pos == 2048 || record->len_dr[0] == 0) {
	    	/* 
	    	 * The directory entries are splitted in several blocks
	    	 * read next block 
	    	 */
			if ( info->src->read_block(info->src, ++block, buffer) < 0 ) {
				info->error = LIBISOFS_READ_FAILURE;
				return -1;
			}
	    	tlen += 2048 - pos;
	    	pos = 0;
	    	
	    	/* next block must begin with a non-0 directory record */
	    	assert(buffer[0] != 0);
	    	continue;
	    }
	    
	    /*
	     * What about ignoring files with existence flag?
	     * if (record->flags[0] & 0x01)
	     *	continue;
	     */
	    
	    /*
	     * TODO
	     * For a extrange reason, mkisofs relocates directories under
	     * a RR_MOVED dir. It seems that it is only used for that purposes,
	     * and thus it should be removed from the iso tree before 
	     * generating a new image with libisofs, that don't uses it.
	     * We can do that here, but I think it's a better option doing it
	     * on an app. using the library, such as genisofs. 
	     * 
	     * if ( record->len_fi[0] == 8 &&
	     *     !strncmp(record->file_id,"RR_MOVED", 8) ) {
	     *     continue;
	     * }
	     */
	         
	    /* check for unsupported multiextend */
	    if (record->flags[0] & 0x80) {
	    	iso_msg_fatal(LIBISO_IMG_UNSUPPORTED, "Unsupported image.\n"
    		    "This image makes use of Multi-Extend features, that "
    		    "are not supported at this time.\n"
    		    "If you need support for that, please request us this feature.\n"
    		    "Thank you in advance\n");
    		info->error = LIBISOFS_UNSUPPORTED_IMAGE;
    		return -1;
	    }
    	/* check for unsupported interleaved mode */
    	if ( record->file_unit_size[0] || record->interleave_gap_size[0] ) {
    		iso_msg_fatal(LIBISO_IMG_UNSUPPORTED, "Unsupported image.\n"
    		    "This image has at least one file recorded in "
    		    "interleaved mode.\n"
    		    "We don't support this mode, as we think it's not used.\n"
    		    "If you're reading this, then we're wrong :)\n"
    		    "Please contact libisofs developers, so we can fix this.\n"
    		    "Thank you in advance\n");
    		info->error = LIBISOFS_UNSUPPORTED_IMAGE;
    		return -1;
    	}
	    //TODO check for unsupported extended attribs?
	    //TODO check for other flags?
	    
	    if ( iso_read_single_directory_record(info, dir, record) )
	    	return -1;
	    
	    tlen += record->len_dr[0];
	    pos += record->len_dr[0];
	}
	
	return 0;
}

/**
 * Read the SUSP system user entries of the "." entry of the root directory, 
 * indentifying when Rock Ridge extensions are being used.
 */
static int
read_root_susp_entries(struct iso_read_info *info, 
                       struct iso_tree_node_dir *root,
                       uint32_t block)
{
	unsigned char buffer[2048];
	struct ecma119_dir_record *record;
	struct susp_sys_user_entry *sue;
	struct susp_iterator *iter;
	
	if ( info->src->read_block(info->src, block, buffer) < 0 ) {
		info->error = LIBISOFS_READ_FAILURE;
		return -1;
	}
	
	/* record will be the "." directory entry for the root record */
	record = (struct ecma119_dir_record *)buffer;
	
	/*
	 * TODO 
	 * SUSP specification claims that for CD-ROM XA the SP entry
	 * is not at position BP 1, but at BP 15. Is that used?
	 * In that case, we need to set info->len_skp to 15!!
	 */
	 
	iter = susp_iter_new(info, record);
	
	/* first entry must be an SP system use entry */
	sue = susp_iter_next(iter);
	if (!sue && info->error) {
		susp_iter_free(iter);
		return -1;
	} else if (!sue || !SUSP_SIG(sue, 'S', 'P') ) {
		iso_msg_debug("SUSP/RR is not being used.");
		susp_iter_free(iter);
		return 0;
	}
	
	/* it is a SP system use entry */
	if ( sue->version[0] != 1 || sue->data.SP.be[0] != 0xBE
	     || sue->data.SP.ef[0] != 0xEF) {
	
		iso_msg_sorry(LIBISO_SUSP_WRONG, "SUSP SP system use entry seems to "
			"be wrong. Ignoring Rock Ridge Extensions.");
		susp_iter_free(iter);
		return 0;
	}
	
	iso_msg_debug("SUSP/RR is being used.");
	
	/*
	 * The LEN_SKP field, defined in IEEE 1281, SUSP. 5.3, specifies the
	 * number of bytes to be skipped within each System Use field.
	 * I think this will be always 0, but given that support this standard
	 * features is easy...
	 */
	info->len_skp = sue->data.SP.len_skp[0];
	
	/*
	 * Ok, now search for ER entry.
	 * Just notice that the attributes for root dir are read in
	 * iso_read_dir
	 * 
	 * TODO if several ER are present, we need to identify the position of
	 *      what refers to RR, and then look for corresponding ES entry in
	 *      each directory record. I have not implemented this (it's not used,
	 *      no?), but if we finally need it, it can be easily implemented in
	 *      the iterator, transparently for the rest of the code.
	 */
	while ( (sue = susp_iter_next(iter)) ) {
		
		/* ignore entries from different version */
		if (sue->version[0] != 1)
			continue; 
		
		if (SUSP_SIG(sue, 'E', 'R')) {
			   
		    if (info->rr) {
				iso_msg_warn(LIBISO_SUSP_MULTIPLE_ER, 
					"More than one ER has found. This is not supported.\n"
		    	    "It will be ignored, but can cause problems. "
		    	    "Please notify us about this.\n");
		    }
			/* 
			 * it seems that Rock Ridge can be identified with any
			 * of the following 
			 */
			if ( sue->data.ER.len_id[0] == 10 && 
			     !strncmp((char*)sue->data.ER.ext_id, "RRIP_1991A", 10) ) {
			    
			    iso_msg_debug("Suitable Rock Ridge ER found. Version 1.10.");
			    info->rr = RR_EXT_110;
			    
			} else if ( ( sue->data.ER.len_id[0] == 10 && 
			        !strncmp((char*)sue->data.ER.ext_id, "IEEE_P1282", 10) ) 
			     || ( sue->data.ER.len_id[0] == 9 && 
			        !strncmp((char*)sue->data.ER.ext_id, "IEEE_1282", 9) ) ) {
			     
			    iso_msg_debug("Suitable Rock Ridge ER found. Version 1.12.");
			    info->rr = RR_EXT_112;
			    //TODO check also version?
			} else {
				iso_msg_warn(LIBISO_SUSP_MULTIPLE_ER, 
					"Not Rock Ridge ER found.\n"
			        "That will be ignored, but can cause problems in "
			        "image reading. Please notify us about this");
			}
		}
	}
	
	susp_iter_free(iter);	
	
	if (info->error) 
		return -1;
	
	return 0;
}

static struct iso_volset *
read_pvm(struct iso_read_info *info, uint32_t block)
{
	struct ecma119_pri_vol_desc *pvm;
	struct iso_volume *volume;
	struct iso_volset *volset;
	struct ecma119_dir_record *rootdr;
	char* volset_id;
	unsigned char buffer[BLOCK_SIZE];
	
	if ( info->src->read_block(info->src, block, buffer) < 0 ) {
		info->error = LIBISOFS_READ_FAILURE;
		return NULL;
	}
	pvm = (struct ecma119_pri_vol_desc *)buffer;
	
	/* sanity checks */
	if ( pvm->vol_desc_type[0] != 1
	     || strncmp((char*)pvm->std_identifier, "CD001", 5)
	     || pvm->vol_desc_version[0] != 1 
	     || pvm->file_structure_version[0] != 1 ) {
	     	
	     iso_msg_fatal(LIBISO_WRONG_IMG, "Wrong PVM. Maybe this is a damaged "
	                   "image, or it's not an ISO-9660 image.\n");
	     info->error = LIBISOFS_WRONG_PVM;
	     return NULL;
	}
	
	volume = iso_volume_new(NULL, NULL, NULL);
	
	/* fill strings */
	volume->volume_id = strcopy((char*)pvm->volume_id, 32);
	volume->publisher_id = strcopy((char*)pvm->publisher_id, 128);
	volume->data_preparer_id = strcopy((char*)pvm->data_prep_id, 128);
	volume->system_id = strcopy((char*)pvm->system_id, 32);
	volume->application_id = strcopy((char*)pvm->application_id, 128);
	volume->copyright_file_id = strcopy((char*)pvm->copyright_file_id, 37);
	volume->abstract_file_id = strcopy((char*)pvm->abstract_file_id, 37);
	volume->biblio_file_id = strcopy((char*)pvm->bibliographic_file_id, 37);
	
	volset_id = strcopy((char*)pvm->vol_set_id, 128);
	
	*(info->size) = iso_read_bb(pvm->vol_space_size, 4, NULL);
	
	volset = iso_volset_new(volume, volset_id);
	free(volset_id);
	
	/*
	 * TODO
	 * I don't like the way the differences volset - volume are hanled now.
	 * While theorically right (a volset can contain several volumes), in
	 * practice it seems that this never happen. Current implementation, with
	 * the volume array in volset, make things innecessarily harder. I think
	 * we can refactor that in a single way.
	 */
	
	//volset->volset_size = pvm->vol_set_size[0];
	
	rootdr = (struct ecma119_dir_record *)pvm->root_dir_record;
	
	/* 
	 * check if RR is being used. Note that this functions returns
	 * != 0 on error. Info about if RR is being used is stored in info
	 */
	if ( read_root_susp_entries(info, volume->root, 
	                            iso_read_bb(rootdr->block, 4, NULL)) ) {
		
	    /* error, cleanup and return */
	    iso_volset_free(volset);
	    return NULL;
	}
	
	/* are RR ext present */
	info->hasRR = info->rr ? 1 : 0;
	
	info->iso_root_block = iso_read_bb(rootdr->block, 4, NULL);
	
	/*
	 * PVM has things that can be interested, but don't have a member in
	 * volume struct, such as creation date. In a multisession disc, we could
	 * keep the creation date and update the modification date, for example.
	 */
	
	return volset;
}

struct iso_volset *
iso_volset_read(struct data_source *src, struct ecma119_read_opts *opts)
{
	struct iso_read_info info;
	struct iso_volset *volset;
	uint32_t block, root_dir_block;
	unsigned char buffer[BLOCK_SIZE];
	
	assert(src && opts);
	
	/* fill info with suitable values */
	info.error = LIBISOFS_READ_OK;
	info.src = src;
	info.rr = RR_EXT_NO;
	info.len_skp = 0;
	info.ino = 0;
	info.norock = opts->norock;
	info.uid = opts->uid;
	info.gid = opts->gid;
	info.mode = opts->mode & ~S_IFMT;
	info.size = &opts->size;
	info.bootcat = NULL;
	root_dir_block = 0;
	
	/* read primary volume description */
	volset = read_pvm(&info, opts->block + 16);
	
	if (volset == NULL) {
		opts->error = info.error;
		return NULL;
	}
	
	block = opts->block + 17;
	do {
		if ( info.src->read_block(info.src, block, buffer) < 0 ) {
			info.error = LIBISOFS_READ_FAILURE;
			/* cleanup and exit */
			goto read_cleanup;
		}
		switch (buffer[0]) {
		case 0:
			/* 
			 * This is a boot record 
			 * Here we handle el-torito
			 */
			info.bootcat = read_el_torito_vol_desc(&info, buffer);
			break;
		case 2:
			/* suplementary volume descritor */
			{
				struct ecma119_sup_vol_desc *sup;
				struct ecma119_dir_record *root;
				
				sup = (struct ecma119_sup_vol_desc*)buffer;
				if (sup->esc_sequences[0] == 0x25 && 
				    sup->esc_sequences[1] == 0x2F &&
				    (sup->esc_sequences[2] == 0x40 ||
				     sup->esc_sequences[2] == 0x43 ||
				     sup->esc_sequences[2] == 0x45) ) {
					
					/* it's a Joliet Sup. Vol. Desc. */
					info.hasJoliet = 1;
					root = (struct ecma119_dir_record*)sup->root_dir_record;
					root_dir_block = iso_read_bb(root->block, 4, NULL);
					//TODO maybe we can set the volume attribs from this
					//descriptor
				} else {
					iso_msg_hint(LIBISO_UNSUPPORTED_VD,
					             "Not supported Sup. Vol. Desc found.");
				}
			}
			break;
			
		case 255:
			/* 
			 * volume set terminator
			 * ignore, as it's checked in loop end condition
			 */
			break;
		default:
			{
			char msg[32];
			sprintf(msg, "Ignoring Volume descriptor %d.", buffer[0]);
			iso_msg_hint(LIBISO_UNSUPPORTED_VD, msg);
			}
			break;
		}
		block++;
	} while (buffer[0] != 255);
	
	
	opts->hasRR = info.hasRR;
	opts->hasJoliet = info.hasJoliet;
	
	/* user doesn't want to read RR extensions */
	if (info.norock)
		info.rr = RR_EXT_NO;
	
	/* select what tree to read */
	if (info.rr) {
		/* RR extensions are available */
		if (opts->preferjoliet && info.hasJoliet) {
			/* if user prefers joliet, that is used */
			iso_msg_debug("Reading Joliet extensions.");
			info.get_name = ucs2str;
			info.rr = RR_EXT_NO;
			/* root_dir_block already contains root for joliet */
		} else {
			/* RR will be used */
			iso_msg_debug("Reading Rock Ridge extensions.");
			root_dir_block = info.iso_root_block;
			info.get_name = strcopy;
		}
	} else {
		/* RR extensions are not available */
		if (info.hasJoliet && !opts->nojoliet) {
			/* joliet will be used */
			iso_msg_debug("Reading Joliet extensions.");
			info.get_name = ucs2str;
			/* root_dir_block already contains root for joliet */
		} else {
			/* default to plain iso */
			iso_msg_debug("Reading plain ISO-9660 tree.");
			root_dir_block = info.iso_root_block;
			info.get_name = strcopy;
		}
	}
		
	/* Read the ISO/RR or Joliet tree */
	if ( iso_read_dir(&info, volset->volume[0]->root, root_dir_block) ) {
	               
	    /* error, cleanup and return */
		goto read_cleanup;
	}
	
	// TODO merge tree info
	
	/* Add El-Torito info to the volume */
	if (info.bootcat) {
		/* ok, add the bootcat to the volume */
		iso_msg_debug("Found El-Torito bootable volume"); 
		volset->volume[0]->bootcat = info.bootcat;
	}
	
	return volset;
	
read_cleanup:;
	if (info.bootcat) {
	    el_torito_boot_catalog_free(info.bootcat);
	}
	iso_volset_free(volset);
	
	return NULL;
}