/*
 * Copyright (c) 2007 Vreixo Formoso
 * 
 * This file is part of the libisofs project; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License version 2 as 
 * published by the Free Software Foundation. See COPYING file for details.
 */

/*
 * Filesystem/FileSource implementation to access an ISO image, using an
 * IsoDataSource to read image data.
 */

#include "libisofs.h"
#include "ecma119.h"
#include "messages.h"
#include "rockridge.h"
#include "image.h"
#include "tree.h"
#include "eltorito.h"

#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <langinfo.h>
#include <limits.h>


/**
 * Options for image reading.
 * There are four kind of options:
 * - Related to multisession support.
 *   In most cases, an image begins at LBA 0 of the data source. However,
 *   in multisession discs, the later image begins in the last session on
 *   disc. The block option can be used to specify the start of that last
 *   session.
 * - Related to the tree that will be read.
 *   As default, when Rock Ridge extensions are present in the image, that
 *   will be used to get the tree. If RR extensions are not present, libisofs
 *   will use the Joliet extensions if available. Finally, the plain ISO-9660
 *   tree is used if neither RR nor Joliet extensions are available. With
 *   norock, nojoliet, and preferjoliet options, you can change this
 *   default behavior.
 * - Related to default POSIX attributes.
 *   When Rock Ridege extensions are not used, libisofs can't figure out what
 *   are the the permissions, uid or gid for the files. You should supply
 *   default values for that.
 */
struct iso_read_opts
{
    /** 
     * Block where the image begins, usually 0, can be different on a 
     * multisession disc.
     */
    uint32_t block;

    unsigned int norock : 1; /*< Do not read Rock Ridge extensions */
    unsigned int nojoliet : 1; /*< Do not read Joliet extensions */
    unsigned int noiso1999 : 1; /*< Do not read ISO 9660:1999 enhanced tree */

    /** 
     * When both Joliet and RR extensions are present, the RR tree is used. 
     * If you prefer using Joliet, set this to 1. 
     */
    unsigned int preferjoliet : 1; 
    
    uid_t uid; /**< Default uid when no RR */
    gid_t gid; /**< Default uid when no RR */
    mode_t dir_mode; /**< Default mode when no RR (only permissions) */
    mode_t file_mode;
    /* TODO #00024 : option to convert names to lower case for iso reading */
    
    /**
     * Input charset for RR file names. NULL to use default locale charset.
     */
    char *input_charset;
};

/**
 * Return information for image.
 * Both size, hasRR and hasJoliet will be filled by libisofs with suitable 
 * values.
 */
struct iso_read_image_features
{
    /** 
     * Will be filled with the size (in 2048 byte block) of the image, as 
     * reported in the PVM. 
     */
    uint32_t size;

    /** It will be set to 1 if RR extensions are present, to 0 if not. */
    unsigned int hasRR :1;

    /** It will be set to 1 if Joliet extensions are present, to 0 if not. */
    unsigned int hasJoliet :1;

    /** 
     * It will be set to 1 if the image is an ISO 9660:1999, i.e. it has
     * a version 2 Enhanced Volume Descriptor. 
     */
    unsigned int hasIso1999 :1;

    /** It will be set to 1 if El-Torito boot record is present, to 0 if not.*/
    unsigned int hasElTorito :1;
};

static int ifs_fs_open(IsoImageFilesystem *fs);
static int ifs_fs_close(IsoImageFilesystem *fs);
static int iso_file_source_new_ifs(IsoImageFilesystem *fs, 
           IsoFileSource *parent, struct ecma119_dir_record *record, 
           IsoFileSource **src);

/** unique identifier for each image */
unsigned int fs_dev_id = 0;

/**
 * Should the RR extensions be read?
 */
enum read_rr_ext {
    RR_EXT_NO = 0, /*< Do not use RR extensions */
    RR_EXT_110 = 1, /*< RR extensions conforming version 1.10 */
    RR_EXT_112 = 2 /*< RR extensions conforming version 1.12 */
};

/**
 * Private data for the image IsoFilesystem
 */
typedef struct
{
    /** DataSource from where data will be read */
    IsoDataSource *src;
    
    /** unique id for the each image (filesystem instance) */
    unsigned int id;

    /** 
     * Counter of the times the filesystem has been openned still pending of
     * close. It is used to keep track of when we need to actually open or 
     * close the IsoDataSource. 
     */
    unsigned int open_count;

    uid_t uid; /**< Default uid when no RR */
    gid_t gid; /**< Default uid when no RR */
    mode_t dir_mode; /**< Default mode when no RR (only permissions) */
    mode_t file_mode;

    int msgid;

    char *input_charset; /**< Input charset for RR names */
    char *local_charset; /**< For RR names, will be set to the locale one */

    /** 
     * Will be filled with the block lba of the extend for the root directory
     * of the hierarchy that will be read, either from the PVD (ISO, RR) or
     * from the SVD (Joliet)
     */
    uint32_t iso_root_block;

    /**
     * Will be filled with the block lba of the extend for the root directory,
     * as read from the PVM
     */
    uint32_t pvd_root_block;

    /**
     * Will be filled with the block lba of the extend for the root directory,
     * as read from the SVD
     */
    uint32_t svd_root_block;

    /**
     * Will be filled with the block lba of the extend for the root directory,
     * as read from the enhanced volume descriptor (ISO 9660:1999)
     */
    uint32_t evd_root_block;

    /**
     * If we need to read RR extensions. i.e., if the image contains RR 
     * extensions, and the user wants to read them. 
     */
    enum read_rr_ext rr;

    /**
     * Bytes skipped within the System Use field of a directory record, before 
     * the beginning of the SUSP system user entries. See IEEE 1281, SUSP. 5.3. 
     */
    uint8_t len_skp;

    /* Volume attributes */
    char *volset_id;
    char *volume_id; /**< Volume identifier. */
    char *publisher_id; /**< Volume publisher. */
    char *data_preparer_id; /**< Volume data preparer. */
    char *system_id; /**< Volume system identifier. */
    char *application_id; /**< Volume application id */
    char *copyright_file_id;
    char *abstract_file_id;
    char *biblio_file_id;

    /* extension information */

    /**
     * RR version being used in image.
     * 0 no RR extension, 1 RRIP 1.10, 2 RRIP 1.12
     */
    enum read_rr_ext rr_version;
    
    /** If Joliet extensions are available on image */
    unsigned int joliet : 1;
    
    /** If ISO 9660:1999 is available on image */
    unsigned int iso1999 : 1;

    /**
     * Number of blocks of the volume, as reported in the PVM.
     */
    uint32_t nblocks;

    /* el-torito information */
    unsigned int eltorito : 1; /* is el-torito available */
    unsigned int bootable:1; /**< If the entry is bootable. */
    unsigned char type; /**< The type of image */
    unsigned char partition_type; /**< type of partition for HD-emul images */
    short load_seg; /**< Load segment for the initial boot image. */
    short load_size; /**< Number of sectors to load. */
    uint32_t imgblock; /**< Block for El-Torito boot image */
    uint32_t catblock; /**< Block for El-Torito catalog */

} _ImageFsData;

typedef struct image_fs_data ImageFileSourceData;

struct image_fs_data
{
    IsoImageFilesystem *fs; /**< reference to the image it belongs to */
    IsoFileSource *parent; /**< reference to the parent (NULL if root) */
    
    struct stat info; /**< filled struct stat */
    char *name; /**< name of this file */
    
    uint32_t block; /**< block of the extend */
    unsigned int opened : 2; /**< 0 not opened, 1 opened file, 2 opened dir */
    
    /* info for content reading */
    struct 
    {
        /**
         * - For regular files, once opened it points to a temporary data 
         *   buffer of 2048 bytes.
         * - For dirs, once opened it points to a IsoFileSource* array with
         *   its children
         * - For symlinks, it points to link destination
         */
        void *content;

        /**
         * - For regular files, number of bytes already read.
         */
        off_t offset;
    } data;
};

struct child_list
{
    IsoFileSource *file;
    struct child_list *next;
};

void child_list_free(struct child_list *list)
{
    struct child_list *temp;
    struct child_list *next = list;
    while (next != NULL) {
        temp = next->next;
        iso_file_source_unref(next->file);
        free(next);
        next = temp;
    }
}

static
char* ifs_get_path(IsoFileSource *src)
{
    ImageFileSourceData *data;
    data = src->data;
    
    if (data->parent == NULL) {
        return strdup("");
    } else {
        char *path = ifs_get_path(data->parent);
        int pathlen = strlen(path);
        path = realloc(path, pathlen + strlen(data->name) + 2);
        path[pathlen] = '/';
        path[pathlen + 1] = '\0';
        return strcat(path, data->name);
    }
}

static
char* ifs_get_name(IsoFileSource *src)
{
    ImageFileSourceData *data;
    data = src->data;
    return data->name == NULL ? NULL : strdup(data->name);
}

static
int ifs_lstat(IsoFileSource *src, struct stat *info)
{
    ImageFileSourceData *data;

    if (src == NULL || info == NULL) {
        return ISO_NULL_POINTER;
    }

    data = src->data;
    *info = data->info;
    return ISO_SUCCESS;
}

static
int ifs_stat(IsoFileSource *src, struct stat *info)
{
    ImageFileSourceData *data;
        
    if (src == NULL || info == NULL || src->data == NULL) {
        return ISO_NULL_POINTER;
    }

    data = (ImageFileSourceData*)src->data;
    
    if (S_ISLNK(data->info.st_mode)) {
        /* TODO #00012 : support follow symlinks on image filesystem */ 
        return ISO_FILE_BAD_PATH;
    }
    *info = data->info;
    return ISO_SUCCESS;
}

static
int ifs_access(IsoFileSource *src)
{
    /* we always have access, it is controlled by DataSource */
    return ISO_SUCCESS;
}

/**
 * Read all directory records in a directory, and creates an IsoFileSource for
 * each of them, storing them in the data field of the IsoFileSource for the
 * given dir. 
 */
static
int read_dir(ImageFileSourceData *data)
{
    int ret;
    uint32_t size;
    uint32_t block;
    IsoImageFilesystem *fs;
    _ImageFsData *fsdata;
    struct ecma119_dir_record *record;
    uint8_t buffer[BLOCK_SIZE];
    IsoFileSource *child = NULL;
    uint32_t pos = 0;
    uint32_t tlen = 0;

    if (data == NULL) {
        return ISO_NULL_POINTER;
    }

    fs = data->fs;
    fsdata = fs->data;

    block = data->block;
    ret = fsdata->src->read_block(fsdata->src, block, buffer);
    if (ret < 0) {
        return ret;
    }

    /* "." entry, get size of the dir and skip */
    record = (struct ecma119_dir_record *)(buffer + pos);
    size = iso_read_bb(record->length, 4, NULL);
    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 
             */
            ret = fsdata->src->read_block(fsdata->src, ++block, buffer);
            if (ret < 0) {
                return ret;
            }
            tlen += 2048 - pos;
            pos = 0;
            continue;
        }

        /*
         * What about ignoring files with existence flag?
         * if (record->flags[0] & 0x01)
         *	continue;
         */

        /*
         * 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.
         */
        if (data->parent == NULL && record->len_fi[0] == 8
            && !strncmp((char*)record->file_id, "RR_MOVED", 8)) {
            
            iso_msg_debug(fsdata->msgid, "Skipping RR_MOVE entry.");

            tlen += record->len_dr[0];
            pos += record->len_dr[0];
            continue;
        }

        /*
         * We pass a NULL parent instead of dir, to prevent the circular 
         * reference from child to parent.
         */
        ret = iso_file_source_new_ifs(fs, NULL, record, &child);
        if (ret < 0) {
            return ret;
        }

        /* add to the child list */
        if (ret != 0) {
            struct child_list *node;
            node = malloc(sizeof(struct child_list));
            if (node == NULL) {
                iso_file_source_unref(child);
                return ISO_OUT_OF_MEM;
            }
            /*
             * Note that we insert in reverse order. This leads to faster
             * addition here, but also when adding to the tree, as insertion
             * will be done, sorted, in the first position of the list.
             */
            node->next = data->data.content;
            node->file = child;
            data->data.content = node;
        }
        
        tlen += record->len_dr[0];
        pos += record->len_dr[0];
    }

    return ISO_SUCCESS;
}

static
int ifs_open(IsoFileSource *src)
{
    int ret;
    ImageFileSourceData *data;
    
    if (src == NULL || src->data == NULL) {
        return ISO_NULL_POINTER;
    }
    data = (ImageFileSourceData*)src->data;

    if (data->opened) {
        return ISO_FILE_ALREADY_OPENED;
    }
    
    if (S_ISDIR(data->info.st_mode)) {
        /* ensure fs is openned */
        ret = data->fs->open(data->fs);
        if (ret < 0) {
            return ret;
        }
        
        /* 
         * Cache all directory entries. 
         * This can waste more memory, but improves as disc is read in much more
         * sequencially way, thus reducing jump between tracks on disc
         */
        ret = read_dir(data);
        data->fs->close(data->fs);
        
        if (ret < 0) {
            /* free probably allocated children */
            child_list_free((struct child_list*)data->data.content);
        } else {
            data->opened = 2;
        }
        
        return ret;
    } else if (S_ISREG(data->info.st_mode)) {
        /* ensure fs is openned */
        ret = data->fs->open(data->fs);
        if (ret < 0) {
            return ret;
        }
        data->data.content = malloc(BLOCK_SIZE);
        if (data->data.content == NULL) {
            return ISO_OUT_OF_MEM;
        }
        data->data.offset = 0;
        data->opened = 1;
    } else {
        /* symlinks and special files inside image can't be openned */
        return ISO_FILE_ERROR;
    }
    return ISO_SUCCESS;
}

static
int ifs_close(IsoFileSource *src)
{
    ImageFileSourceData *data;
    
    if (src == NULL || src->data == NULL) {
        return ISO_NULL_POINTER;
    }
    data = (ImageFileSourceData*)src->data;
    
    if (!data->opened) {
        return ISO_FILE_NOT_OPENED;
    }
    
    if (data->opened == 2) {
        /* 
         * close a dir, free all pending pre-allocated children.
         * not that we don't need to close the filesystem, it was already 
         * closed
         */
        child_list_free((struct child_list*) data->data.content);
        data->data.content = NULL;
        data->opened = 0;
    } else if (data->opened == 1) {
        /* close regular file */
        free(data->data.content);
        data->fs->close(data->fs);
        data->data.content = NULL;
        data->opened = 0;
    } else {
        /* TODO only dirs and files supported for now */
        return ISO_ERROR;
    }
    
    return ISO_SUCCESS;
}

/**
 * Attempts to read up to count bytes from the given source into
 * the buffer starting at buf.
 * 
 * The file src must be open() before calling this, and close() when no 
 * more needed. Not valid for dirs. On symlinks it reads the destination
 * file.
 * 
 * @return 
 *     number of bytes read, 0 if EOF, < 0 on error
 *      Error codes:
 *         ISO_FILE_ERROR
 *         ISO_NULL_POINTER
 *         ISO_FILE_NOT_OPENED
 *         ISO_FILE_IS_DIR
 *         ISO_OUT_OF_MEM
 *         ISO_INTERRUPTED
 */
static
int ifs_read(IsoFileSource *src, void *buf, size_t count)
{
    int ret;
    ImageFileSourceData *data;
    uint32_t read = 0;
    
    if (src == NULL || src->data == NULL || buf == NULL) {
        return ISO_NULL_POINTER;
    }
    if (count == 0) {
        return ISO_WRONG_ARG_VALUE;
    }
    data = (ImageFileSourceData*)src->data;
    
    if (!data->opened) {
        return ISO_FILE_NOT_OPENED;
    } else if (data->opened != 1) {
        return ISO_FILE_IS_DIR;
    }
    
    while (read < count && data->data.offset < data->info.st_size) {
        size_t bytes;
        uint8_t *orig;
        
        if (data->data.offset % BLOCK_SIZE == 0) {
            /* we need to buffer next block */
            uint32_t block;
            _ImageFsData *fsdata;
            
            if (data->data.offset >= data->info.st_size) {
                /* EOF */
                break;
            }
            fsdata = data->fs->data;
            block = data->block + (data->data.offset / BLOCK_SIZE);
            ret = fsdata->src->read_block(fsdata->src, block, 
                                          data->data.content);
            if (ret < 0) {
                return ret;
            }
        }
        
        /* how much can I read */
        bytes = MIN(BLOCK_SIZE - (data->data.offset % BLOCK_SIZE), 
                    count - read);
        if (data->data.offset + (off_t)bytes > data->info.st_size) {
             bytes = data->info.st_size - data->data.offset;
        }
        orig = data->data.content;
        orig += data->data.offset % BLOCK_SIZE;
        memcpy((uint8_t*)buf + read, orig, bytes);
        read += bytes;
        data->data.offset += (off_t)bytes;
    }
    return read;
}

static
off_t ifs_lseek(IsoFileSource *src, off_t offset, int flag)
{
    ImageFileSourceData *data;

    if (src == NULL) {
        return (off_t)ISO_NULL_POINTER;
    }
    if (offset < (off_t)0) {
        return (off_t)ISO_WRONG_ARG_VALUE;
    }
    
    data = src->data;
    
    if (!data->opened) {
        return (off_t)ISO_FILE_NOT_OPENED;
    } else if (data->opened != 1) {
        return (off_t)ISO_FILE_IS_DIR;
    }

    switch (flag) {
    case 0: /* SEEK_SET */
        data->data.offset = offset;
        break;
    case 1: /* SEEK_CUR */
        data->data.offset += offset;
        break;
    case 2: /* SEEK_END */
        /* do this make sense? */
        data->data.offset = data->info.st_size + offset;
        break;
    default:
        return (off_t)ISO_WRONG_ARG_VALUE;
    }
    
    if (data->data.offset % BLOCK_SIZE != 0) {
        /* we need to buffer the block */
        uint32_t block;
        _ImageFsData *fsdata;
        
        if (data->data.offset < data->info.st_size) {
            int ret;
            fsdata = data->fs->data;
            block = data->block + (data->data.offset / BLOCK_SIZE);
            ret = fsdata->src->read_block(fsdata->src, block, 
                                          data->data.content);
            if (ret < 0) {
                return (off_t)ret;
            }
        }
    }
    return data->data.offset;
}

static
int ifs_readdir(IsoFileSource *src, IsoFileSource **child)
{
    ImageFileSourceData *data, *cdata;
    struct child_list *children;
    
    if (src == NULL || src->data == NULL || child == NULL) {
        return ISO_NULL_POINTER;
    }
    data = (ImageFileSourceData*)src->data;
    
    if (!data->opened) {
        return ISO_FILE_NOT_OPENED;
    } else if (data->opened != 2) {
        return ISO_FILE_IS_NOT_DIR;
    }
    
    /* return the first child and free it */
    if (data->data.content == NULL) {
        return 0; /* EOF */
    }
    
    children = (struct child_list*)data->data.content;
    *child = children->file;
    cdata = (ImageFileSourceData*)(*child)->data;
    
    /* set the ref to the parent */
    cdata->parent = src;
    iso_file_source_ref(src);
    
    /* free the first element of the list */
    data->data.content = children->next;
    free(children);

    return ISO_SUCCESS;
}

/**
 * Read the destination of a symlink. You don't need to open the file
 * to call this.
 * 
 * @param buf 
 *     allocated buffer of at least bufsiz bytes. 
 *     The dest. will be copied there, and it will be NULL-terminated
 * @param bufsiz
 *     characters to be copied. Destination link will be truncated if
 *     it is larger than given size. This include the \0 character.
 * @return 
 *     1 on success, < 0 on error
 *      Error codes:
 *         ISO_FILE_ERROR
 *         ISO_NULL_POINTER
 *         ISO_WRONG_ARG_VALUE -> if bufsiz <= 0
 *         ISO_FILE_IS_NOT_SYMLINK
 *         ISO_OUT_OF_MEM
 *         ISO_FILE_BAD_PATH
 *         ISO_FILE_DOESNT_EXIST
 * 
 */
static
int ifs_readlink(IsoFileSource *src, char *buf, size_t bufsiz)
{
    char *dest;
    size_t len;
    ImageFileSourceData *data;
    
    if (src == NULL || buf == NULL || src->data == NULL) {
        return ISO_NULL_POINTER;
    }
    
    if (bufsiz <= 0) {
        return ISO_WRONG_ARG_VALUE;
    }
    
    data = (ImageFileSourceData*)src->data;
    
    if (!S_ISLNK(data->info.st_mode)) {
        return ISO_FILE_IS_NOT_SYMLINK;
    }
    
    dest = (char*)data->data.content;
    len = strlen(dest);
    if (bufsiz <= len) {
        len = bufsiz - 1;
    }
    
    strncpy(buf, dest, len);
    buf[len] = '\0';
    
    return ISO_SUCCESS;
}

static
IsoFilesystem* ifs_get_filesystem(IsoFileSource *src)
{
    ImageFileSourceData *data;

    if (src == NULL) {
        return NULL;
    }

    data = src->data;
    return data->fs;
}

static
void ifs_free(IsoFileSource *src)
{
    ImageFileSourceData *data;

    data = src->data;

    /* close the file if it is already openned */
    if (data->opened) {
        src->class->close(src);
    }

    /* free destination if it is a link */
    if (S_ISLNK(data->info.st_mode)) {
        free(data->data.content);
    }
    iso_filesystem_unref(data->fs);
    if (data->parent != NULL) {
        iso_file_source_unref(data->parent);
    }
    free(data->name);
    free(data);
}

IsoFileSourceIface ifs_class = { 
    0, /* version */
    ifs_get_path,
    ifs_get_name,
    ifs_lstat,
    ifs_stat,
    ifs_access,
    ifs_open,
    ifs_close,
    ifs_read,
    ifs_readdir,
    ifs_readlink,
    ifs_get_filesystem,
    ifs_free,
    ifs_lseek
};

/**
 * Read a file name from a directory record, doing the needed charset
 * conversion
 */
static
char *get_name(_ImageFsData *fsdata, const char *str, size_t len)
{
    int ret;
    char *name = NULL;
    if (strcmp(fsdata->local_charset, fsdata->input_charset)) {
        /* charset conversion needed */
        ret = strnconv(str, fsdata->input_charset, fsdata->local_charset, len,
                       &name);
        if (ret == 1) {
            return name;
        } else {
            ret = iso_msg_submit(fsdata->msgid, ISO_FILENAME_WRONG_CHARSET, ret,
                "Charset conversion error. Can't convert %s from %s to %s",
                str, fsdata->input_charset, fsdata->local_charset);
            if (ret < 0) {
                return NULL; /* aborted */
            }
            /* fallback */
        }
    }
    
    /* we reach here when the charset conversion is not needed or has failed */
    
    name = malloc(len + 1);
    if (name == NULL) {
        return NULL;
    }
    memcpy(name, str, len);
    name[len] = '\0';
    return name;
}

/**
 * 
 * @return
 *      1 success, 0 record ignored (not an error, can be a relocated dir),
 *      < 0 error
 */
static
int iso_file_source_new_ifs(IsoImageFilesystem *fs, IsoFileSource *parent, 
                            struct ecma119_dir_record *record, 
                            IsoFileSource **src)
{
    int ret;
    struct stat atts;
    time_t recorded;
    _ImageFsData *fsdata;
    IsoFileSource *ifsrc = NULL;
    ImageFileSourceData *ifsdata = NULL;
    
    int namecont = 0; /* 1 if found a NM with CONTINUE flag */
    char *name = NULL;

    /* 1 if found a SL with CONTINUE flag, 
     * 2 if found a component with continue flag */
    int linkdestcont = 0; 
    char *linkdest = NULL;
    
    uint32_t relocated_dir = 0;
    
    if (fs == NULL || fs->data == NULL || record == NULL || src == NULL) {
        return ISO_NULL_POINTER;
    }

    fsdata = (_ImageFsData*)fs->data;
    
    memset(&atts, 0, sizeof(struct stat));
    
    /*
     * First of all, check for unsupported ECMA-119 features
     */

    /* check for unsupported multiextend */
    if (record->flags[0] & 0x80) {
        iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_ECMA119, 0,
                      "Unsupported image. This image makes use of Multi-Extend"
                      " features, that are not supported at this time. If you "
                      "need support for that, please request us this feature.");
        return ISO_UNSUPPORTED_ECMA119;
    }

    /* check for unsupported interleaved mode */
    if (record->file_unit_size[0] || record->interleave_gap_size[0]) {
        iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_ECMA119, 0,
              "Unsupported image. This image has at least one file recorded "
              "in interleaved mode. We don't support this mode, as we think "
              "it's not used. If you're reading this, then we're wrong :) "
              "Please contact libisofs developers, so we can fix this.");
        return ISO_UNSUPPORTED_ECMA119;
    }
    
    /*
     * Check for extended attributes, that are not supported. Note that even
     * if we don't support them, it is easy to ignore them.
     */
    if (record->len_xa[0]) {
        iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_ECMA119, 0,
              "Unsupported image. This image has at least one file with "
              "Extended Attributes, that are not supported");
        return ISO_UNSUPPORTED_ECMA119;
    }

    /* TODO #00013 : check for unsupported flags when reading a dir record */ 
    
    /*
     * 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 (fsdata->rr) {
        struct susp_sys_user_entry *sue;
        SuspIterator *iter;
        

        iter = susp_iter_new(fsdata->src, record, fsdata->len_skp, 
                             fsdata->msgid);
        if (iter == NULL) {
            return ISO_OUT_OF_MEM;
        }
        
        while ((ret = susp_iter_next(iter, &sue)) > 0) {
            
            /* ignore entries from different version */
            if (sue->version[0] != 1)
                continue; 
            
            if (SUSP_SIG(sue, 'P', 'X')) {
                ret = read_rr_PX(sue, &atts);
                if (ret < 0) {
                    /* notify and continue */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret,
                                         "Invalid PX entry");
                }
            } else if (SUSP_SIG(sue, 'T', 'F')) {
                ret = read_rr_TF(sue, &atts);
                if (ret < 0) {
                    /* notify and continue */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret,
                                 "Invalid TF entry");
                }
            } else if (SUSP_SIG(sue, 'N', 'M')) {
                if (name != NULL && namecont == 0) {
                    /* ups, RR standard violation */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, 0,
                                 "New NM entry found without previous"
                                 "CONTINUE flag. Ignored");
                    continue;
                }
                ret = read_rr_NM(sue, &name, &namecont);
                if (ret < 0) {
                    /* notify and continue */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret,
                                 "Invalid NM entry");
                }
            } else if (SUSP_SIG(sue, 'S', 'L')) {
                if (linkdest != NULL && linkdestcont == 0) {
                    /* ups, RR standard violation */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, 0,
                                 "New SL entry found without previous"
                                 "CONTINUE flag. Ignored");
                    continue;
                }
                ret = read_rr_SL(sue, &linkdest, &linkdestcont);
                if (ret < 0) {
                    /* notify and continue */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret,
                                 "Invalid SL entry");
                }
            } 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; /* it's 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);
                if (relocated_dir == 0) {
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0,
                                  "Invalid SL entry, no child location");
                    break;
                }
            } else if (SUSP_SIG(sue, 'P', 'N')) {
                ret = read_rr_PN(sue, &atts);
                if (ret < 0) {
                    /* notify and continue */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret,
                                         "Invalid PN entry");
                }
            } else if (SUSP_SIG(sue, 'S', 'F')) {
                ret = iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_RR, 0,
                                     "Sparse files not supported.");
                break;
            } else if (SUSP_SIG(sue, 'R', 'R')) {
                /* TODO I've seen this RR on mkisofs images. what's this? */
                continue;
            } else if (SUSP_SIG(sue, 'S', 'P')) {
                /* 
                 * Ignore this, to prevent the hint message, if we are dealing
                 * with root node (SP is only valid in "." of root node)
                 */
                if (parent != NULL) {
                    /* notify and continue */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0,
                                  "SP entry found in a directory entry other "
                                  "than '.' entry of root node");
                }
                continue;
            } else if (SUSP_SIG(sue, 'E', 'R')) {
                /* 
                 * Ignore this, to prevent the hint message, if we are dealing
                 * with root node (ER is only valid in "." of root node)
                 */
                if (parent != NULL) {
                    /* notify and continue */
                    ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0,
                                  "ER entry found in a directory entry other "
                                  "than '.' entry of root node");
                }
                continue;
            } else {
                ret = iso_msg_submit(fsdata->msgid, ISO_SUSP_UNHANDLED, 0,
                    "Unhandled SUSP entry %c%c.", sue->sig[0], sue->sig[1]);
            }
        }
        
        susp_iter_free(iter);
        
        /* check for RR problems */
        
        if (ret < 0) {
            /* error was already submitted above */
            iso_msg_debug(fsdata->msgid, "Error parsing RR entries");
        } else if (!relocated_dir && atts.st_mode == (mode_t) 0 ) {
            ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0, "Mandatory "
                                 "Rock Ridge PX entry is not present or it "
                                 "contains invalid values.");
        } else {
            /* ensure both name and link dest are finished */
            if (namecont != 0) {
                ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0,
                              "Incomplete RR name, last NM entry continues");
            }
            if (linkdestcont != 0) {
                ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0,
                    "Incomplete link destination, last SL entry continues");
            }
        }
        
        if (ret < 0) {
            free(name);
            return ret;
        }
           
        /* convert name to needed charset */
        if (strcmp(fsdata->input_charset, fsdata->local_charset) && name) {
            /* we need to convert name charset */
            char *newname = NULL;
            ret = strconv(name, fsdata->input_charset, fsdata->local_charset,
                          &newname);
            if (ret < 0) {
                /* its just a hint message */
                ret = iso_msg_submit(fsdata->msgid, ISO_FILENAME_WRONG_CHARSET,
                                 ret, "Charset conversion error. Can't "
                                 "convert %s from %s to %s", name, 
                                 fsdata->input_charset, fsdata->local_charset);
                free(newname);
                if (ret < 0) {
                    free(name);
                    return ret;
                }
            } else {
                free(name);
                name = newname;
            }
        }
        
        /* convert link destination to needed charset */ 
        if (strcmp(fsdata->input_charset, fsdata->local_charset) && linkdest) {
            /* we need to convert name charset */
            char *newlinkdest = NULL;
            ret = strconv(linkdest, fsdata->input_charset, 
                          fsdata->local_charset, &newlinkdest);
            if (ret < 0) {
                ret = iso_msg_submit(fsdata->msgid, ISO_FILENAME_WRONG_CHARSET,
                                 ret, "Charset conversion error. Can't "
                                 "convert %s from %s to %s", name, 
                                 fsdata->input_charset, fsdata->local_charset);
                free(newlinkdest);
                if (ret < 0) {
                    free(name);
                    return ret;
                }
            } else {
                free(linkdest);
                linkdest = newlinkdest;
            }
        }
        
    } else {
        /* RR extensions are not read / used */
        atts.st_gid = fsdata->gid;
        atts.st_uid = fsdata->uid;
        if (record->flags[0] & 0x02) {
            atts.st_mode = S_IFDIR | fsdata->dir_mode;
        } else {
            atts.st_mode = S_IFREG | fsdata->file_mode;
        }
    }
    
    /* 
     * if we haven't RR extensions, or no NM entry is present,
     * we use the name in directory record
     */
    if (!name) {
        size_t len;
        
        if (record->len_fi[0] == 1 && record->file_id[0] == 0) {
            /* "." entry, we can call this for root node, so... */
            if (!(atts.st_mode & S_IFDIR)) {
                return iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0,
                              "Wrong ISO file name. \".\" not dir");
            }
        } else {

            name = get_name(fsdata, (char*)record->file_id, record->len_fi[0]);
            if (name == NULL) {
                return iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0,
                              "Can't retrieve file name");
            }
            
            /* remove trailing version number */
            len = strlen(name);
            if (len > 2 && name[len-2] == ';' && name[len-1] == '1') {
                if (len > 3 && name[len-3] == '.') {
                    /* 
                     * the "." is mandatory, so in most cases is included only
                     * for standard compliance
                     */
                    name[len-3] = '\0';
                } else {
                    name[len-2] = '\0';
                }
            }
        }
    }

    if (relocated_dir) {
        
        /*
         * We are dealing with a placeholder for a relocated dir.
         * Thus, we need to read attributes for this directory from the "." 
         * entry of the relocated dir.
         */
        uint8_t buffer[BLOCK_SIZE];
        
        ret = fsdata->src->read_block(fsdata->src, relocated_dir, buffer);
        if (ret < 0) {
            return ret;
        }
        
        ret = iso_file_source_new_ifs(fs, parent, (struct ecma119_dir_record*)
                                      buffer, src);
        if (ret <= 0) {
            return ret;
        }
        
        /* but the real name is the name of the placeholder */
        ifsdata = (ImageFileSourceData*) (*src)->data;
        ifsdata->name = name;
        return ISO_SUCCESS;
    }

    if (fsdata->rr != RR_EXT_112) {
        /*
         * Only RRIP 1.12 provides valid inode numbers. If not, it is not easy
         * to generate those serial numbers, and we use extend block instead.
         * It BREAKS POSIX SEMANTICS, but its suitable for our needs
         */
        atts.st_ino = (ino_t) iso_read_bb(record->block, 4, NULL);
        if (fsdata->rr == 0) {
            atts.st_nlink = 1;
        }
    }
    
    /* 
     * 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);
    
    /* Fill last entries */
    atts.st_dev = fsdata->id;
    atts.st_blksize = BLOCK_SIZE;
    atts.st_blocks = DIV_UP(atts.st_size, BLOCK_SIZE);
    
    /* TODO #00014 : more sanity checks to ensure dir record info is valid */
    if (S_ISLNK(atts.st_mode) && (linkdest == NULL)) {
        ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0,
                             "Link without destination.");
        free(name);
        return ret;
    }
    
    /* ok, we can now create the file source */
    ifsdata = calloc(1, sizeof(ImageFileSourceData));
    if (ifsdata == NULL) {
        ret = ISO_OUT_OF_MEM;
        goto ifs_cleanup;
    }
    ifsrc = calloc(1, sizeof(IsoFileSource));
    if (ifsrc == NULL) {
        ret = ISO_OUT_OF_MEM;
        goto ifs_cleanup;
    }
    
    /* fill data */
    ifsdata->fs = fs;
    iso_filesystem_ref(fs);
    if (parent != NULL) {
        ifsdata->parent = parent;
        iso_file_source_ref(parent);
    }
    ifsdata->info = atts;
    ifsdata->name = name;
    ifsdata->block = iso_read_bb(record->block, 4, NULL);
    
    if (S_ISLNK(atts.st_mode)) {
        ifsdata->data.content = linkdest;
    }
    
    ifsrc->class = &ifs_class;
    ifsrc->data = ifsdata;
    ifsrc->refcount = 1;
    
    *src = ifsrc;
    return ISO_SUCCESS;
    
ifs_cleanup: ;
    free(name);
    free(linkdest);
    free(ifsdata);
    free(ifsrc);
    return ret;
}

static
int ifs_get_root(IsoFilesystem *fs, IsoFileSource **root)
{
    int ret;
    _ImageFsData *data;
    uint8_t buffer[BLOCK_SIZE];

    if (fs == NULL || fs->data == NULL || root == NULL) {
        return ISO_NULL_POINTER;
    }

    data = (_ImageFsData*)fs->data;

    /* open the filesystem */
    ret = ifs_fs_open((IsoImageFilesystem*)fs);
    if (ret < 0) {
        return ret;
    }

    /* read extend for root record */
    ret = data->src->read_block(data->src, data->iso_root_block, buffer);
    if (ret < 0) {
        ifs_fs_close((IsoImageFilesystem*)fs);
        return ret;
    }

    /* get root attributes from "." entry */
    ret = iso_file_source_new_ifs((IsoImageFilesystem*)fs, NULL,
                                   (struct ecma119_dir_record*) buffer, root);

    ifs_fs_close((IsoImageFilesystem*)fs);
    return ret;
}

/**
 * Find a file inside a node.
 * 
 * @param file
 *     it is not modified if requested file is not found
 * @return
 *     1 success, 0 not found, < 0 error
 */
static
int ifs_get_file(IsoFileSource *dir, const char *name, IsoFileSource **file)
{
    int ret;
    IsoFileSource *src;

    ret = iso_file_source_open(dir);
    if (ret < 0) {
        return ret;
    }
    while ((ret = iso_file_source_readdir(dir, &src)) == 1) {
        char *fname = iso_file_source_get_name(src);
        if (!strcmp(name, fname)) {
            free(fname);
            *file = src;
            ret = ISO_SUCCESS;
            break;
        }
        free(fname);
        iso_file_source_unref(src);
    }
    iso_file_source_close(dir);
    return ret;
}

static
int ifs_get_by_path(IsoFilesystem *fs, const char *path, IsoFileSource **file)
{
    int ret;
    _ImageFsData *data;
    IsoFileSource *src;
    char *ptr, *brk_info, *component;

    if (fs == NULL || fs->data == NULL || path == NULL || file == NULL) {
        return ISO_NULL_POINTER;
    }
    
    if (path[0] != '/') {
        /* only absolute paths supported */
        return ISO_FILE_BAD_PATH;
    }

    data = (_ImageFsData*)fs->data;

    /* open the filesystem */
    ret = ifs_fs_open((IsoImageFilesystem*)fs);
    if (ret < 0) {
        return ret;
    }
    
    ret = ifs_get_root(fs, &src);
    if (ret < 0) {
        return ret;
    }
    if (!strcmp(path, "/")) {
        /* we are looking for root */
        *file = src;
        ret = ISO_SUCCESS;
        goto get_path_exit;
    }

    ptr = strdup(path);
    if (ptr == NULL) {
        iso_file_source_unref(src);
        ret = ISO_OUT_OF_MEM;
        goto get_path_exit;
    }
    
    component = strtok_r(ptr, "/", &brk_info);
    while (component) {
        IsoFileSource *child = NULL;
        
        ImageFileSourceData *fdata;
        fdata = src->data;
        if (!S_ISDIR(fdata->info.st_mode)) {
            ret = ISO_FILE_BAD_PATH;
            break;
        }

        ret = ifs_get_file(src, component, &child);
        iso_file_source_unref(src);
        if (ret <= 0) {
            break;
        }
        
        src = child;
        component = strtok_r(NULL, "/", &brk_info);
    }

    free(ptr);
    if (ret < 0) {
        iso_file_source_unref(src);
    } else if (ret == 0) {
        ret = ISO_FILE_DOESNT_EXIST;
    } else {
        *file = src;
    }

    get_path_exit:;
    ifs_fs_close((IsoImageFilesystem*)fs);
    return ret;
}

unsigned int ifs_get_id(IsoFilesystem *fs)
{
    return ISO_IMAGE_FS_ID;
}

static
int ifs_fs_open(IsoImageFilesystem *fs)
{
    _ImageFsData *data;

    if (fs == NULL || fs->data == NULL) {
        return ISO_NULL_POINTER;
    }

    data = (_ImageFsData*)fs->data;

    if (data->open_count == 0) {
        /* we need to actually open the data source */
        int res = data->src->open(data->src);
        if (res < 0) {
            return res;
        }
    }
    ++data->open_count;
    return ISO_SUCCESS;
}

static
int ifs_fs_close(IsoImageFilesystem *fs)
{
    _ImageFsData *data;

    if (fs == NULL || fs->data == NULL) {
        return ISO_NULL_POINTER;
    }

    data = (_ImageFsData*)fs->data;

    if (--data->open_count == 0) {
        /* we need to actually close the data source */
        return data->src->close(data->src);
    }
    return ISO_SUCCESS;
}

static
void ifs_fs_free(IsoFilesystem *fs)
{
    IsoImageFilesystem *ifs;
    _ImageFsData *data;

    ifs = (IsoImageFilesystem*)fs;
    data = (_ImageFsData*) fs->data;

    /* close data source if already openned */
    if (data->open_count > 0) {
        data->src->close(data->src);
    }

    /* free our ref to datasource */
    iso_data_source_unref(data->src);

    /* free volume atts */
    free(data->volset_id);
    free(data->volume_id);
    free(data->publisher_id);
    free(data->data_preparer_id);
    free(data->system_id);
    free(data->application_id);
    free(data->copyright_file_id);
    free(data->abstract_file_id);
    free(data->biblio_file_id);

    free(data->input_charset);
    free(data->local_charset);
    free(data);
}

/**
 * Read the SUSP system user entries of the "." entry of the root directory, 
 * indentifying when Rock Ridge extensions are being used.
 * 
 * @return
 *      1 success, 0 ignored, < 0 error
 */
static 
int read_root_susp_entries(_ImageFsData *data, uint32_t block)
{
    int ret;
    unsigned char buffer[2048];
    struct ecma119_dir_record *record;
    struct susp_sys_user_entry *sue;
    SuspIterator *iter;

    ret = data->src->read_block(data->src, block, buffer);
    if (ret < 0) {
        return ret;
    }
    
    /* record will be the "." directory entry for the root record */
    record = (struct ecma119_dir_record *)buffer;
    
    /*
     * TODO #00015 : take care of CD-ROM XA discs when reading SP entry
     * 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(data->src, record, data->len_skp, data->msgid);
    if (iter == NULL) {
        return ISO_OUT_OF_MEM;
    }
    
    /* first entry must be an SP system use entry */
    ret = susp_iter_next(iter, &sue);
    if (ret < 0) {
        /* error */
        susp_iter_free(iter);
        return ret;
    } else if (ret == 0 || !SUSP_SIG(sue, 'S', 'P') ) {
        iso_msg_debug(data->msgid, "SUSP/RR is not being used.");
        susp_iter_free(iter);
        return ISO_SUCCESS;
    }
    
    /* it is a SP system use entry */
    if (sue->version[0] != 1 || sue->data.SP.be[0] != 0xBE
        || sue->data.SP.ef[0] != 0xEF) {

        susp_iter_free(iter);
        return iso_msg_submit(data->msgid, ISO_UNSUPPORTED_SUSP, 0,
                              "SUSP SP system use entry seems to be wrong. "
                              "Ignoring Rock Ridge Extensions.");
    }
    
    iso_msg_debug(data->msgid, "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
     * feature is easy...
     */
    data->len_skp = sue->data.SP.len_skp[0];
    
    /*
     * Ok, now search for ER entry.
     * Just notice that the attributes for root dir are read elsewhere.
     * 
     * TODO #00016 : handle non RR ER entries 
     * 
     * 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 ((ret = susp_iter_next(iter, &sue)) > 0) {
        
        /* ignore entries from different version */
        if (sue->version[0] != 1)
            continue; 
        
        if (SUSP_SIG(sue, 'E', 'R')) {
               
            if (data->rr_version) {
                ret = iso_msg_submit(data->msgid, ISO_SUSP_MULTIPLE_ER, 0,
                    "More than one ER has found. This is not supported. "
                    "It will be ignored, but can cause problems. "
                    "Please notify us about this.");
                if (ret < 0) {
                    break;
                }
            }

            /* 
             * 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(data->msgid, 
                              "Suitable Rock Ridge ER found. Version 1.10.");
                data->rr_version = 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(data->msgid, 
                              "Suitable Rock Ridge ER found. Version 1.12.");
                data->rr_version = RR_EXT_112;
            } else {
                ret = iso_msg_submit(data->msgid, ISO_SUSP_MULTIPLE_ER, 0,
                    "Not Rock Ridge ER found.\n"
                    "That will be ignored, but can cause problems in "
                    "image reading. Please notify us about this");
                if (ret < 0) {
                    break;
                }
            }
        }
    }
    
    susp_iter_free(iter);   
    
    if (ret < 0) { 
        return ret;
    }
    
    return ISO_SUCCESS;
}

static
int read_pvm(_ImageFsData *data, uint32_t block)
{
    int ret;
    struct ecma119_pri_vol_desc *pvm;
    struct ecma119_dir_record *rootdr;
    uint8_t buffer[BLOCK_SIZE];

    /* read PVM */
    ret = data->src->read_block(data->src, block, buffer);
    if (ret < 0) {
        return ret;
    }

    pvm = (struct ecma119_pri_vol_desc *)buffer;

    /* sanity checks */
    if (pvm->vol_desc_type[0] != 1 || pvm->vol_desc_version[0] != 1
            || strncmp((char*)pvm->std_identifier, "CD001", 5)
            || pvm->file_structure_version[0] != 1) {

        return ISO_WRONG_PVD;
    }

    /* ok, it is a valid PVD */

    /* fill volume attributes  */
    /* TODO take care of input charset */
    data->volset_id = strcopy((char*)pvm->vol_set_id, 128);
    data->volume_id = strcopy((char*)pvm->volume_id, 32);
    data->publisher_id = strcopy((char*)pvm->publisher_id, 128);
    data->data_preparer_id = strcopy((char*)pvm->data_prep_id, 128);
    data->system_id = strcopy((char*)pvm->system_id, 32);
    data->application_id = strcopy((char*)pvm->application_id, 128);
    data->copyright_file_id = strcopy((char*)pvm->copyright_file_id, 37);
    data->abstract_file_id = strcopy((char*)pvm->abstract_file_id, 37);
    data->biblio_file_id = strcopy((char*)pvm->bibliographic_file_id, 37);

    data->nblocks = iso_read_bb(pvm->vol_space_size, 4, NULL);

    rootdr = (struct ecma119_dir_record*) pvm->root_dir_record;
    data->pvd_root_block = iso_read_bb(rootdr->block, 4, NULL);

    /*
     * TODO #00017 : take advantage of other atts of PVD
     * PVD has other things that could be interesting, but that don't have a 
     * member in IsoImage, such as creation date. In a multisession disc, we 
     * could keep the creation date and update the modification date, for 
     * example.
     */

    return ISO_SUCCESS;
}

/**
 * @return
 *      1 success, 0 ignored, < 0 error
 */
static
int read_el_torito_boot_catalog(_ImageFsData *data, uint32_t block)
{
    int ret;
    struct el_torito_validation_entry *ve;
    struct el_torito_default_entry *entry;
    unsigned char buffer[BLOCK_SIZE];

    ret = data->src->read_block(data->src, block, buffer);
    if (ret < 0) {
        return ret;
    }
    
    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) ) {
            
        return iso_msg_submit(data->msgid, ISO_WRONG_EL_TORITO, 0,
                      "Wrong or damaged El-Torito Catalog. El-Torito info "
                      "will be ignored.");
    }
    
    /* check for a valid platform */
    if (ve->platform_id[0] != 0) {
        return iso_msg_submit(data->msgid, ISO_UNSUPPORTED_EL_TORITO, 0,
                     "Unsupported El-Torito platform. Only 80x86 is "
                     "supported. El-Torito info will be ignored.");
    }
    
    /* ok, once we are here we assume it is a valid catalog */
    
    /* parse the default entry */
    entry = (struct el_torito_default_entry *)(buffer + 32);
    
    data->eltorito = 1;
    data->bootable = entry->boot_indicator[0] ? 1 : 0;
    data->type = entry->boot_media_type[0];
    data->partition_type = entry->system_type[0];
    data->load_seg = iso_read_lsb(entry->load_seg, 2);
    data->load_size = iso_read_lsb(entry->sec_count, 2);
    data->imgblock = iso_read_lsb(entry->block, 4);

    /* TODO #00018 : check if there are more entries in the boot catalog */

    return ISO_SUCCESS;
}

int iso_image_filesystem_new(IsoDataSource *src, struct iso_read_opts *opts,
                             int msgid, IsoImageFilesystem **fs)
{
    int ret;
    uint32_t block;
    IsoImageFilesystem *ifs;
    _ImageFsData *data;
    uint8_t buffer[BLOCK_SIZE];

    if (src == NULL || opts == NULL || fs == NULL) {
        return ISO_NULL_POINTER;
    }

    data = calloc(1, sizeof(_ImageFsData));
    if (data == NULL) {
        return ISO_OUT_OF_MEM;
    }

    ifs = calloc(1, sizeof(IsoImageFilesystem));
    if (ifs == NULL) {
        free(data);
        return ISO_OUT_OF_MEM;
    }

    /* get our ref to IsoDataSource */
    data->src = src;
    iso_data_source_ref(src);
    data->open_count = 0;

    /* get an id for the filesystem */
    data->id = ++fs_dev_id;
    
    /* fill data from opts */
    data->gid = opts->gid;
    data->uid = opts->uid;
    data->file_mode = opts->file_mode & ~S_IFMT;
    data->dir_mode = opts->dir_mode & ~S_IFMT;
    data->msgid = msgid;
    
    setlocale(LC_CTYPE, "");
    data->local_charset = strdup(nl_langinfo(CODESET));
    if (data->local_charset == NULL) {
        ret = ISO_OUT_OF_MEM;
        goto fs_cleanup;
    }

    strncpy(ifs->type, "iso ", 4);
    ifs->data = data;
    ifs->refcount = 1;
    ifs->version = 0;
    ifs->get_root = ifs_get_root;
    ifs->get_by_path = ifs_get_by_path;
    ifs->get_id = ifs_get_id;
    ifs->open = ifs_fs_open;
    ifs->close = ifs_fs_close;
    ifs->free = ifs_fs_free;

    /* read Volume Descriptors and ensure it is a valid image */

    /* 1. first, open the filesystem */
    ifs_fs_open(ifs);

    /* 2. read primary volume description */
    ret = read_pvm(data, opts->block + 16);
    if (ret < 0) {
        goto fs_cleanup;
    }

    /* 3. read next volume descriptors */
    block = opts->block + 17;
    do {
        ret = src->read_block(src, block, buffer);
        if (ret < 0) {
            /* cleanup and exit */
            goto fs_cleanup;
        }
        switch (buffer[0]) {
        case 0:
            /* boot record */
            {
                struct ecma119_boot_rec_vol_desc *vol;
                vol = (struct ecma119_boot_rec_vol_desc*)buffer;

                /* 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)) {

                    ret = iso_msg_submit(data->msgid, 
                          ISO_UNSUPPORTED_EL_TORITO, 0, 
                          "Unsupported Boot Vol. Desc. Only El-Torito "
                          "Specification, Version 1.0 Volume "
                          "Descriptors are supported. Ignoring boot info");
                    if (ret < 0) {
                        goto fs_cleanup;
                    }
                    break;
                }
                data->catblock = iso_read_lsb(vol->boot_catalog, 4);
                ret = read_el_torito_boot_catalog(data, data->catblock);
                if (ret < 0) {
                    goto fs_cleanup;
                }
            }
            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. */
                    iso_msg_debug(data->msgid, "Found Joliet extensions");
                    data->joliet = 1;
                    root = (struct ecma119_dir_record*)sup->root_dir_record;
                    data->svd_root_block = iso_read_bb(root->block, 4, NULL);
                    /* TODO #00019 : set IsoImage attribs from Joliet SVD? */
                    /* TODO #00020 : handle RR info in Joliet tree */
                } else if (sup->vol_desc_version[0] == 2) {
                    /*
                     * It is an Enhanced Volume Descriptor, image is an 
                     * ISO 9660:1999
                     */
                    iso_msg_debug(data->msgid, "Found ISO 9660:1999");
                    data->iso1999 = 1;
                    root = (struct ecma119_dir_record*)sup->root_dir_record;
                    data->evd_root_block = iso_read_bb(root->block, 4, NULL);
                    /* TODO #00021 : handle RR info in ISO 9660:1999 tree */
                } else {
                    ret = iso_msg_submit(data->msgid, ISO_UNSUPPORTED_VD, 0,
                        "Unsupported Sup. Vol. Desc found.");
                    if (ret < 0) {
                        goto fs_cleanup;
                    }
                }
            }
            break;
        case 255:
            /* 
             * volume set terminator
             * ignore, as it's checked in loop end condition
             */
            break;
        default:
            ret = iso_msg_submit(data->msgid, ISO_UNSUPPORTED_VD, 0,
                         "Ignoring Volume descriptor %x.", buffer[0]);
            if (ret < 0) {
                goto fs_cleanup;
            }
            break;
        }
        block++;
    } while (buffer[0] != 255);

    /* 4. check if RR extensions are being used */
    ret = read_root_susp_entries(data, data->pvd_root_block);
    if (ret < 0) {
        goto fs_cleanup;
    }
    
    /* user doesn't want to read RR extensions */
    if (opts->norock) {
        data->rr = RR_EXT_NO;
    } else {
        data->rr = data->rr_version;
    }

    /* select what tree to read */
    if (data->rr) {
        /* RR extensions are available */
        if (!opts->nojoliet && opts->preferjoliet && data->joliet) {
            /* if user prefers joliet, that is used */
            iso_msg_debug(data->msgid, "Reading Joliet extensions.");
            data->input_charset = strdup("UCS-2BE");
            data->rr = RR_EXT_NO;
            data->iso_root_block = data->svd_root_block;
        } else {
            /* RR will be used */
            iso_msg_debug(data->msgid, "Reading Rock Ridge extensions.");
            data->iso_root_block = data->pvd_root_block;
        }
    } else {
        /* RR extensions are not available */
        if (!opts->nojoliet && data->joliet) {
            /* joliet will be used */
            iso_msg_debug(data->msgid, "Reading Joliet extensions.");
            data->input_charset = strdup("UCS-2BE");
            data->iso_root_block = data->svd_root_block;
        } else if (!opts->noiso1999 && data->iso1999) {
            /* we will read ISO 9660:1999 */
            iso_msg_debug(data->msgid, "Reading ISO-9660:1999 tree.");
            data->iso_root_block = data->evd_root_block;
        } else {
            /* default to plain iso */
            iso_msg_debug(data->msgid, "Reading plain ISO-9660 tree.");
            data->iso_root_block = data->pvd_root_block;
            data->input_charset = strdup("ASCII");
        }
    }

    if (data->input_charset == NULL) {
        if (opts->input_charset != NULL) {
            data->input_charset = strdup(opts->input_charset);
        } else {
            data->input_charset = strdup(data->local_charset);
        }
    }
    if (data->input_charset == NULL) {
        ret = ISO_OUT_OF_MEM;
        goto fs_cleanup;
    }
    
    /* and finally return. Note that we keep the DataSource opened */

    *fs = ifs;
    return ISO_SUCCESS;

    fs_cleanup: ;
    ifs_fs_free(ifs);
    free(ifs);
    return ret;
}

static
int image_builder_create_node(IsoNodeBuilder *builder, IsoImage *image,
                              IsoFileSource *src, IsoNode **node)
{
    int ret;
    struct stat info;
    IsoNode *new;
    char *name;
    ImageFileSourceData *data;

    if (builder == NULL || src == NULL || node == NULL || src->data == NULL) {
        return ISO_NULL_POINTER;
    }

    data = (ImageFileSourceData*)src->data;

    name = iso_file_source_get_name(src);

    /* get info about source */
    ret = iso_file_source_lstat(src, &info);
    if (ret < 0) {
        return ret;
    }

    new = NULL;
    switch (info.st_mode & S_IFMT) {
    case S_IFREG:
        {
            /* source is a regular file */
            _ImageFsData *fsdata = data->fs->data;
            
            if (fsdata->eltorito && data->block == fsdata->catblock) {
                
                if (image->bootcat->node != NULL) {
                    ret = iso_msg_submit(image->id, ISO_EL_TORITO_WARN, 0,
                                 "More than one catalog node has been found. "
                                 "We can continue, but that could lead to "
                                 "problems");
                    if (ret < 0) {
                        return ret;
                    }
                    iso_node_unref((IsoNode*)image->bootcat->node);
                }
                
                /* we create a placeholder for the catalog instead of
                 * a regular file */
                new = calloc(1, sizeof(IsoBoot));
                if (new == NULL) {
                    ret = ISO_OUT_OF_MEM;
                    free(name);
                    return ret;
                }
                
                /* and set the image node */
                image->bootcat->node = (IsoBoot*)new;
                new->type = LIBISO_BOOT;
                new->refcount = 1;
            } else {
                IsoStream *stream;
                IsoFile *file;
                
                ret = iso_file_source_stream_new(src, &stream);
                if (ret < 0) {
                    free(name);
                    return ret;
                }
                /* take a ref to the src, as stream has taken our ref */
                iso_file_source_ref(src);

                file = calloc(1, sizeof(IsoFile));
                if (file == NULL) {
                    free(name);
                    iso_stream_unref(stream);
                    return ISO_OUT_OF_MEM;
                }
    
                /* the msblock is taken from the image */
                file->msblock = data->block;
                
                /* 
                 * and we set the sort weight based on the block on image, to
                 * improve performance on image modifying.
                 */ 
                file->sort_weight = INT_MAX - data->block;
    
                file->stream = stream;
                file->node.type = LIBISO_FILE;
                new = (IsoNode*) file;
                new->refcount = 0;
                
                if (fsdata->eltorito && data->block == fsdata->imgblock) {
                    /* it is boot image node */
                    if (image->bootcat->image->image != NULL) {
                        ret = iso_msg_submit(image->id, ISO_EL_TORITO_WARN, 0,
                                "More than one image node has been found.");
                        if (ret < 0) {
                            free(name);
                            iso_stream_unref(stream);
                            return ret;
                        }
                    } else {
                        /* and set the image node */
                        image->bootcat->image->image = file;
                        new->refcount++;
                    }
                }
            }
        }
        break;
    case S_IFDIR:
        {
            /* source is a directory */
            new = calloc(1, sizeof(IsoDir));
            if (new == NULL) {
                free(name);
                return ISO_OUT_OF_MEM;
            }
            new->type = LIBISO_DIR;
            new->refcount = 0;
        }
        break;
    case S_IFLNK:
        {
            /* source is a symbolic link */
            char dest[PATH_MAX];
            IsoSymlink *link;

            ret = iso_file_source_readlink(src, dest, PATH_MAX);
            if (ret < 0) {
                free(name);
                return ret;
            }
            link = malloc(sizeof(IsoSymlink));
            if (link == NULL) {
                free(name);
                return ISO_OUT_OF_MEM;
            }
            link->dest = strdup(dest);
            link->node.type = LIBISO_SYMLINK;
            new = (IsoNode*) link;
            new->refcount = 0;
        }
        break;
    case S_IFSOCK:
    case S_IFBLK:
    case S_IFCHR:
    case S_IFIFO:
        {
            /* source is an special file */
            IsoSpecial *special;
            special = malloc(sizeof(IsoSpecial));
            if (special == NULL) {
                free(name);
                return ISO_OUT_OF_MEM;
            }
            special->dev = info.st_rdev;
            special->node.type = LIBISO_SPECIAL;
            new = (IsoNode*) special;
            new->refcount = 0;
        }
        break;
    }

    /* fill fields */
    new->refcount++;
    new->name = name;
    new->mode = info.st_mode;
    new->uid = info.st_uid;
    new->gid = info.st_gid;
    new->atime = info.st_atime;
    new->mtime = info.st_mtime;
    new->ctime = info.st_ctime;

    new->hidden = 0;

    new->parent = NULL;
    new->next = NULL;

    *node = new;
    return ISO_SUCCESS;
}

/**
 * Create a new builder, that is exactly a copy of an old builder, but where
 * create_node() function has been replaced by image_builder_create_node.
 */
static
int iso_image_builder_new(IsoNodeBuilder *old, IsoNodeBuilder **builder)
{
    IsoNodeBuilder *b;

    if (builder == NULL) {
        return ISO_NULL_POINTER;
    }

    b = malloc(sizeof(IsoNodeBuilder));
    if (b == NULL) {
        return ISO_OUT_OF_MEM;
    }

    b->refcount = 1;
    b->create_file_data = old->create_file_data;
    b->create_node_data = old->create_node_data;
    b->create_file = old->create_file;
    b->create_node = image_builder_create_node;
    b->free = old->free;

    *builder = b;
    return ISO_SUCCESS;
}

/**
 * Create a file source to access the El-Torito boot image, when it is not
 * accessible from the ISO filesystem. 
 */
static
int create_boot_img_filesrc(IsoImageFilesystem *fs, IsoFileSource **src)
{
    int ret;
    struct stat atts;
    _ImageFsData *fsdata;
    IsoFileSource *ifsrc = NULL;
    ImageFileSourceData *ifsdata = NULL;
    
    if (fs == NULL || fs->data == NULL || src == NULL) {
        return ISO_NULL_POINTER;
    }

    fsdata = (_ImageFsData*)fs->data;
    
    memset(&atts, 0, sizeof(struct stat));
    atts.st_mode = S_IFREG;
    atts.st_ino = fsdata->imgblock; /* not the best solution, but... */
    atts.st_nlink = 1;

    /* 
     * this is the greater problem. We don't know the size. For now, we
     * just use a single block of data. In a future, maybe we could figure out
     * a better idea. Another alternative is to use several blocks, that way
     * is less probable that we throw out valid data.  
     */
    atts.st_size = (off_t)BLOCK_SIZE;
    
    /* Fill last entries */
    atts.st_dev = fsdata->id;
    atts.st_blksize = BLOCK_SIZE;
    atts.st_blocks = DIV_UP(atts.st_size, BLOCK_SIZE);
    
    /* ok, we can now create the file source */
    ifsdata = calloc(1, sizeof(ImageFileSourceData));
    if (ifsdata == NULL) {
        ret = ISO_OUT_OF_MEM;
        goto boot_fs_cleanup;
    }
    ifsrc = calloc(1, sizeof(IsoFileSource));
    if (ifsrc == NULL) {
        ret = ISO_OUT_OF_MEM;
        goto boot_fs_cleanup;
    }
    
    /* fill data */
    ifsdata->fs = fs;
    iso_filesystem_ref(fs);
    ifsdata->parent = NULL;
    ifsdata->info = atts;
    ifsdata->name = NULL;
    ifsdata->block = fsdata->imgblock;

    ifsrc->class = &ifs_class;
    ifsrc->data = ifsdata;
    ifsrc->refcount = 1;
    
    *src = ifsrc;
    return ISO_SUCCESS;
    
boot_fs_cleanup: ;
    free(ifsdata);
    free(ifsrc);
    return ret;
}


int iso_image_import(IsoImage *image, IsoDataSource *src,
                     struct iso_read_opts *opts, 
                     IsoReadImageFeatures **features)
{
    int ret;
    IsoImageFilesystem *fs;
    IsoFilesystem *fsback;
    IsoNodeBuilder *blback;
    IsoDir *oldroot;
    IsoFileSource *newroot;
    _ImageFsData *data;
    struct el_torito_boot_catalog *oldbootcat;
    
    if (image == NULL || src == NULL || opts == NULL) {
        return ISO_NULL_POINTER;
    }
    
    ret = iso_image_filesystem_new(src, opts, image->id, &fs);
    if (ret < 0) {
        return ret;
    }
    data = fs->data;
    
    /* get root from filesystem */
    ret = fs->get_root(fs, &newroot);
    if (ret < 0) {
        return ret;
    }
    
    /* backup image filesystem, builder and root */
    fsback = image->fs;
    blback = image->builder;
    oldroot = image->root;
    oldbootcat = image->bootcat; /* could be NULL */
    
    image->bootcat = NULL;
    
    /* create new builder */
    ret = iso_image_builder_new(blback, &image->builder);
    if (ret < 0) {
        goto import_revert;
    }
    
    image->fs = fs;

    /* create new root, and set root attributes from source */
    ret = iso_node_new_root(&image->root);
    if (ret < 0) {
        goto import_revert;
    }
    {
        struct stat info;

        /* I know this will not fail */
        iso_file_source_lstat(newroot, &info);
        image->root->node.mode = info.st_mode;
        image->root->node.uid = info.st_uid;
        image->root->node.gid = info.st_gid;
        image->root->node.atime = info.st_atime;
        image->root->node.mtime = info.st_mtime;
        image->root->node.ctime = info.st_ctime;
    }
    
    /* if old image has el-torito, add a new catalog */
    if (data->eltorito) {
        struct el_torito_boot_catalog *catalog;
        ElToritoBootImage *boot_image= NULL;
        
        boot_image = calloc(1, sizeof(ElToritoBootImage));
        if (boot_image == NULL) {
            ret = ISO_OUT_OF_MEM;
            goto import_revert;
        }
        boot_image->bootable = data->bootable;
        boot_image->type = data->type;
        boot_image->partition_type = data->partition_type;
        boot_image->load_seg = data->load_seg;
        boot_image->load_size = data->load_size;
        
        catalog = calloc(1, sizeof(struct el_torito_boot_catalog));
        if (catalog == NULL) {
            ret = ISO_OUT_OF_MEM;
            goto import_revert;
        }
        catalog->image = boot_image;
        image->bootcat = catalog;
    }

    /* recursively add image */
    ret = iso_add_dir_src_rec(image, image->root, newroot);

    /* error during recursive image addition? */
    if (ret < 0) {
        iso_node_builder_unref(image->builder);
        goto import_revert;
    }

    if (data->eltorito) {
        /* if catalog and image nodes were not filled, we create them here */
        if (image->bootcat->image->image == NULL) {
            IsoFileSource *src;
            IsoNode *node;
            ret = create_boot_img_filesrc(fs, &src);
            if (ret < 0) {
                iso_node_builder_unref(image->builder);
                goto import_revert;
            }
            ret = image_builder_create_node(image->builder, image, src, &node);
            if (ret < 0) {
                iso_node_builder_unref(image->builder);
                goto import_revert;
            }
            image->bootcat->image->image = (IsoFile*)node;
            
            /* warn about hidden images */
            iso_msg_submit(image->id, ISO_EL_TORITO_HIDDEN, 0, 
                           "Found hidden El-Torito image. Its size could not "
                           "be figure out, so image modify or boot image "
                           "patching may lead to bad results.");
        }
        if (image->bootcat->node == NULL) {
            IsoNode *node = calloc(1, sizeof(IsoBoot));
            if (node == NULL) {
                ret = ISO_OUT_OF_MEM;
                goto import_revert;
            }
            node->type = LIBISO_BOOT;
            node->mode = S_IFREG;
            node->refcount = 1;
            image->bootcat->node = (IsoBoot*)node;
        }
    }

    iso_node_builder_unref(image->builder);
    
    /* free old root */
    iso_node_unref((IsoNode*)oldroot);

    /* free old boot catalog */
    el_torito_boot_catalog_free(oldbootcat);

    /* set volume attributes */
    iso_image_set_volset_id(image, data->volset_id);
    iso_image_set_volume_id(image, data->volume_id);
    iso_image_set_publisher_id(image, data->publisher_id);
    iso_image_set_data_preparer_id(image, data->data_preparer_id);
    iso_image_set_system_id(image, data->system_id);
    iso_image_set_application_id(image, data->application_id);
    iso_image_set_copyright_file_id(image, data->copyright_file_id);
    iso_image_set_abstract_file_id(image, data->abstract_file_id);
    iso_image_set_biblio_file_id(image, data->biblio_file_id);

    if (features != NULL) {
        *features = malloc(sizeof(IsoReadImageFeatures));
        if (*features == NULL) {
            ret = ISO_OUT_OF_MEM;
            goto import_cleanup;
        }
        (*features)->hasJoliet = data->joliet;
        (*features)->hasRR = data->rr_version != 0;
        (*features)->hasIso1999 = data->iso1999;
        (*features)->hasElTorito = data->eltorito;
        (*features)->size = data->nblocks;
    }
    
    ret = ISO_SUCCESS;
    goto import_cleanup;

    import_revert:;

    iso_node_unref((IsoNode*)image->root);
    el_torito_boot_catalog_free(image->bootcat);
    image->root = oldroot;
    image->fs = fsback;
    image->bootcat = oldbootcat;

    import_cleanup:;

    /* recover backed fs and builder */
    image->fs = fsback;
    image->builder = blback;
    
    iso_file_source_unref(newroot);
    fs->close(fs);
    iso_filesystem_unref(fs);
    
    return ret;
}

const char *iso_image_fs_get_volset_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data = (_ImageFsData*) fs->data;
    return data->volset_id;
}

const char *iso_image_fs_get_volume_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data = (_ImageFsData*) fs->data;
    return data->volume_id;
}

const char *iso_image_fs_get_publisher_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data = (_ImageFsData*) fs->data;
    return data->publisher_id;
}

const char *iso_image_fs_get_data_preparer_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data = (_ImageFsData*) fs->data;
    return data->data_preparer_id;
}

const char *iso_image_fs_get_system_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data = (_ImageFsData*) fs->data;
    return data->system_id;
}

const char *iso_image_fs_get_application_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data = (_ImageFsData*) fs->data;
    return data->application_id;
}

const char *iso_image_fs_get_copyright_file_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data = (_ImageFsData*) fs->data;
    return data->copyright_file_id;
}

const char *iso_image_fs_get_abstract_file_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data;
    data = (_ImageFsData*) fs->data;
    return data->abstract_file_id;
}

const char *iso_image_fs_get_biblio_file_id(IsoImageFilesystem *fs)
{
    _ImageFsData *data = (_ImageFsData*) fs->data;
    return data->biblio_file_id;
}

int iso_read_opts_new(IsoReadOpts **opts, int profile)
{
    IsoReadOpts *ropts;
    
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    if (profile != 0) {
        return ISO_WRONG_ARG_VALUE;
    }
    
    ropts = calloc(1, sizeof(IsoReadOpts));
    if (ropts == NULL) {
        return ISO_OUT_OF_MEM;
    }
    
    ropts->file_mode = 0444;
    ropts->dir_mode = 0555;
    *opts = ropts;
    return ISO_SUCCESS;
}

void iso_read_opts_free(IsoReadOpts *opts)
{
    if (opts == NULL) {
        return;
    }
    
    free(opts->input_charset);
    free(opts);
}

int iso_read_opts_set_start_block(IsoReadOpts *opts, uint32_t block)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->block = block;
    return ISO_SUCCESS;
}

int iso_read_opts_set_no_rockridge(IsoReadOpts *opts, int norr)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->norock = norr ? 1 :0;
    return ISO_SUCCESS;
}

int iso_read_opts_set_no_joliet(IsoReadOpts *opts, int nojoliet)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->nojoliet = nojoliet ? 1 :0;
    return ISO_SUCCESS;
}

int iso_read_opts_set_no_iso1999(IsoReadOpts *opts, int noiso1999)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->noiso1999 = noiso1999 ? 1 :0;
    return ISO_SUCCESS;
}

int iso_read_opts_set_preferjoliet(IsoReadOpts *opts, int preferjoliet)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->preferjoliet = preferjoliet ? 1 :0;
    return ISO_SUCCESS;
}

int iso_read_opts_set_default_uid(IsoReadOpts *opts, uid_t uid)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->uid = uid;
    return ISO_SUCCESS;
}

int iso_read_opts_set_default_gid(IsoReadOpts *opts, gid_t gid)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->gid = gid;
    return ISO_SUCCESS;
}

int iso_read_opts_set_default_permissions(IsoReadOpts *opts, mode_t file_perm,
                                          mode_t dir_perm)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->file_mode = file_perm;
    opts->dir_mode = dir_perm;
    return ISO_SUCCESS;
}

int iso_read_opts_set_input_charset(IsoReadOpts *opts, const char *charset)
{
    if (opts == NULL) {
        return ISO_NULL_POINTER;
    }
    opts->input_charset = charset ? strdup(charset) : NULL;
    return ISO_SUCCESS;
}

/**
 * Destroy an IsoReadImageFeatures object obtained with iso_image_import.
 */
void iso_read_image_features_destroy(IsoReadImageFeatures *f)
{
    if (f) {
        free(f);
    }
}

/**
 * Get the size (in 2048 byte block) of the image, as reported in the PVM. 
 */
uint32_t iso_read_image_features_get_size(IsoReadImageFeatures *f)
{
    return f->size;
}

/**
 * Whether RockRidge extensions are present in the image imported.
 */
int iso_read_image_features_has_rockridge(IsoReadImageFeatures *f)
{
    return f->hasRR;
}

/**
 * Whether Joliet extensions are present in the image imported.
 */
int iso_read_image_features_has_joliet(IsoReadImageFeatures *f)
{
    return f->hasJoliet;
}

/**
 * Whether the image is recorded according to ISO 9660:1999, i.e. it has
 * a version 2 Enhanced Volume Descriptor. 
 */
int iso_read_image_features_has_iso1999(IsoReadImageFeatures *f)
{
    return f->hasIso1999;
}

/**
 * Whether El-Torito boot record is present present in the image imported.
 */
int iso_read_image_features_has_eltorito(IsoReadImageFeatures *f)
{
    return f->hasElTorito;
}