From 1b7fec7751cba4245c00690347d4122d86734fd6 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Fri, 10 Aug 2007 09:36:34 +0000 Subject: [PATCH] Added files - part of MS patch --- libisofs/data_source.c | 107 ++++++ libisofs/ecma119_read.c | 646 +++++++++++++++++++++++++++++++++++++ libisofs/ecma119_read.h | 53 +++ libisofs/ecma119_read_rr.c | 313 ++++++++++++++++++ libisofs/ecma119_read_rr.h | 144 +++++++++ 5 files changed, 1263 insertions(+) create mode 100644 libisofs/data_source.c create mode 100644 libisofs/ecma119_read.c create mode 100644 libisofs/ecma119_read.h create mode 100644 libisofs/ecma119_read_rr.c create mode 100644 libisofs/ecma119_read_rr.h diff --git a/libisofs/data_source.c b/libisofs/data_source.c new file mode 100644 index 0000000..1da8567 --- /dev/null +++ b/libisofs/data_source.c @@ -0,0 +1,107 @@ +/* + * Contains a simple implementation of a data source that reads from a + * given file. + */ + +#include +#include +#include +#include +#include +#include + +#include "libisofs.h" + +#define BLOCK_SIZE 2048 + +#define BLOCK_OUT_OF_FILE -1; +#define READ_ERROR -2; +#define SEEK_ERROR -3; + +struct file_data_src { + int fd; + int nblocks; +}; + +static int +ds_read_block(struct data_source *src, int lba, unsigned char *buffer) +{ + struct file_data_src *data; + + assert(src && buffer); + + data = (struct file_data_src*)src->data; + + /* For block devices size is always 0, so this can't be used. + * if (lba >= data->nblocks) + * return BLOCK_OUT_OF_FILE; + */ + + /* goes to requested block */ + if ( lseek(data->fd, (off_t)lba * (off_t)BLOCK_SIZE, SEEK_SET) == (off_t) -1 ) + return SEEK_ERROR; + + if ( read(data->fd, buffer, BLOCK_SIZE) != BLOCK_SIZE ) + return READ_ERROR; + + return 0; +} + +static int ds_get_size(struct data_source *src) +{ + struct file_data_src *data; + + assert(src); + + data = (struct file_data_src*)src->data; + return data->nblocks; +} + +static void ds_free_data(struct data_source *src) +{ + struct file_data_src *data; + + assert(src); + + data = (struct file_data_src*)src->data; + + /* close the file */ + close(data->fd); + free(data); +} + +struct data_source *data_source_from_file(const char *path) +{ + int fd; + struct stat info; + struct file_data_src *data; + struct data_source *ret; + + assert(path); + + fd = open(path, O_RDONLY); + if (fd == -1) + return NULL; + + fstat(fd, &info); + + data = malloc(sizeof(struct file_data_src)); + data->fd = fd; + data->nblocks = info.st_size / BLOCK_SIZE; + + ret = malloc(sizeof(struct data_source)); + ret->refcount = 1; + ret->read_block = ds_read_block; + ret->get_size = ds_get_size; + ret->free_data = ds_free_data; + ret->data = data; + return ret; +} + +void data_source_free(struct data_source *src) +{ + if (--src->refcount == 0) { + src->free_data(src); + free(src); + } +} diff --git a/libisofs/ecma119_read.c b/libisofs/ecma119_read.c new file mode 100644 index 0000000..12da06c --- /dev/null +++ b/libisofs/ecma119_read.c @@ -0,0 +1,646 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include "ecma119_read.h" +#include "ecma119_read_rr.h" +#include "ecma119.h" +#include "util.h" +#include "volume.h" +#include "tree.h" + +#define BLOCK_SIZE 2048 + +static int +iso_read_dir(struct iso_read_info *info, struct iso_tree_node_dir *parent, + uint32_t block); + +/** + * 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')) { + printf("[ERROR] Sparse files not supported.\n"); + 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 { + printf("[DEBUG] Unhandled SUSP entry %c%c\n", sue->sig[0], sue->sig[1]); + } + } + + if ( !info->error && !relocated_dir && atts.st_mode == (mode_t) 0 ) { + printf("[ERROR] Mandatory Rock Ridge PX entry is not present " + "or it contains invalid values.\n"); + 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 */ + //TODO allow to specify suitable values + atts.st_mode = 0555; + atts.st_gid = 0; + atts.st_uid = 0; + if (record->flags[0] & 0x02) + atts.st_mode |= S_IFDIR; + else + atts.st_mode |= S_IFREG; + } + + /* + * if we haven't RR extensions, or no NM entry is present, + * we use the plain ISO name + */ + if (!name) + name = strcopy((char*)record->file_id, record->len_fi[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: + { + 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 = + iso_read_bb(record->block, 4, NULL); + } + 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: + printf("[ERROR] File type not supported.\n"); + return -1; + } + + node->name = name; + node->attrib = atts; + node->refcount = 1; + 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) { + printf("[ERROR] 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] ) { + printf("[ERROR] 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; + + /* + * FIXME + * 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') ) { + printf("[DEBUG] SUSP/RR is not being used.\n"); + 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) { + + printf("[WARN] SUSP SP system use entry seems to be wrong.\n" + "Ignoring Rock Ridge Extensions.\n"); + susp_iter_free(iter); + return 0; + } + + printf("[DEBUG] SUSP is being used.\n"); + + /* + * 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) { + printf("[WARN] 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) ) { + + printf("[DEBUG] suitable Rock Ridge ER found. Version 1.10.\n"); + 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) ) ) { + + printf("[DEBUG] suitable Rock Ridge ER found. Version 1.12.\n"); + info->rr = RR_EXT_112; + //TODO check also version? + } else { + printf("[WARN] Not Rock Ridge ER (%s) found.\n" + "That will be ignored, but can cause problems in " + "image reading. Please notify us about this.\n", + sue->data.ER.ext_id); + } + + } else { + //TODO look also for other RR entries??? + //printf("[DEBUG] Unhandled SUSP entry %c%c\n", sue->sig[0], sue->sig[1]); + } + } + + 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 ) { + + printf("Wrong file.\n" + "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); + + volset = iso_volset_new(volume, volset_id); + free(volset_id); + + /* + * FIXME + * 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; + } + + /* we want to read RR? */ + info->hasRR = info->rr ? 1 : 0; + if (info->norock) + info->rr = RR_EXT_NO; + + /* Now, read the tree */ + if ( iso_read_dir(info, volume->root, + iso_read_bb(rootdr->block, 4, NULL)) ) { + + /* error, cleanup and return */ + iso_volset_free(volset); + return 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; + + 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; + + /* read primary volume description */ + volset = read_pvm(&info, opts->block + 16); + + if (volset == NULL) { + opts->error = info.error; + return NULL; + } + + opts->hasRR = info.hasRR; + + // TODO read other volume descriptors + // - supplementary: for joliet + // - boot: el-torito + // Read all volume descriptor till Volume Descriptor Set Terminator + + // TODO merge tree info + + // TODO free here? data_source_free(src); + return volset; +} diff --git a/libisofs/ecma119_read.h b/libisofs/ecma119_read.h new file mode 100644 index 0000000..2604fdc --- /dev/null +++ b/libisofs/ecma119_read.h @@ -0,0 +1,53 @@ +/* + * ecma119_read.h + * + * Defines structures for reading from a iso image. + */ + +#ifndef ECMA119_READ_H_ +#define ECMA119_READ_H_ + +#include + +#include "libisofs.h" + +enum read_error { + LIBISOFS_READ_OK = 0, + LIBISOFS_READ_FAILURE, /**< Truncated image or read error */ + LIBISOFS_WRONG_PVM, /**< Incorrect PVM */ + LIBISOFS_UNSUPPORTED_IMAGE, /**< Format not supported (interleaved...) */ + LIBISOFS_WRONG_RR /**< Wrong RR/SUSP extension format */ +}; + +/** + * Should the RR extensions be read? + */ +enum read_rr_ext { + RR_EXT_NO = 0, /*< Do not use RR extensions */ + RR_EXT_110, /*< RR extensions conforming version 1.10 */ + RR_EXT_112 /*< RR extensions conforming version 1.12 */ +}; + +/** + * Structure that keeps info needed in the read process. + */ +struct iso_read_info { + struct data_source *src; + enum read_error error; + + enum read_rr_ext rr; /*< If we need to read RR extensions. i.e., if the image + * contains RR extensions, and the user wants to read them. */ + ino_t ino; /*< RR version 1.10 does not have file serial numbers, we + * need to generate it */ + uint8_t len_skp; /*< 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. */ + + unsigned int norock:1; /*< Do not read Rock Ridge extensions */ + unsigned int hasRR:1; /*< It will be set to 1 if RR extensions are present, + to 0 if not. */ +}; + + + +#endif /*ECMA119_READ_H_*/ diff --git a/libisofs/ecma119_read_rr.c b/libisofs/ecma119_read_rr.c new file mode 100644 index 0000000..44af3f9 --- /dev/null +++ b/libisofs/ecma119_read_rr.c @@ -0,0 +1,313 @@ +/* + * This file contains functions related to the reading of SUSP and + * Rock Ridge extensions on an ECMA-119 image. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ecma119.h" +#include "ecma119_read.h" +#include "ecma119_read_rr.h" +#include "util.h" + +#define BLOCK_SIZE 2048 + +/** + * Fills a struct stat with the values of a Rock Ridge PX entry + * On error, info->error is set propertly and the function returns != 0 + */ +int +read_rr_PX(struct iso_read_info *info, struct susp_sys_user_entry *px, + struct stat *st) +{ + assert( info && px && st); + assert( px->sig[0] == 'P' && px->sig[1] == 'X'); + + if ( info->rr == RR_EXT_112 && px->len_sue[0] != 44 ) { + printf("[ERROR] Invalid PX entry for RR version 1.12\n"); + info->error = LIBISOFS_WRONG_RR; + return -1; + } else if ( info->rr == RR_EXT_110 && px->len_sue[0] != 36 ) { + printf("[ERROR] Invalid PX entry for RR version 1.10\n"); + info->error = LIBISOFS_WRONG_RR; + return -1; + } + + st->st_mode = iso_read_bb(px->data.PX.mode, 4, NULL); + st->st_nlink = iso_read_bb(px->data.PX.links, 4, NULL); + st->st_uid = iso_read_bb(px->data.PX.uid, 4, NULL); + st->st_gid = iso_read_bb(px->data.PX.gid, 4, NULL); + if (info->rr == RR_EXT_112) { + st->st_ino = iso_read_bb(px->data.PX.serial, 4, NULL); + } else { + st->st_ino = ++info->ino; + } + return 0; +} + +/** + * Fills a struct stat with the values of a Rock Ridge TF entry + * On error, info->error is set propertly and the function returns != 0 + */ +int +read_rr_TF(struct iso_read_info *info, struct susp_sys_user_entry *tf, + struct stat *st) +{ + time_t time; + int s; + int nts = 0; + + assert( info && tf && st); + assert( tf->sig[0] == 'T' && tf->sig[1] == 'F'); + + if (tf->data.TF.flags[0] & (1 << 7)) { + /* long form */ + s = 17; + } else { + s = 7; + } + + /* 1. Creation time */ + if (tf->data.TF.flags[0] & (1 << 0)) { + + /* the creation is the recording time. we ignore this */ + /* TODO maybe it would be good to manage it in ms discs, where + * the recording time could be different than now!! */ + ++nts; + } + + /* 2. modify time */ + if (tf->data.TF.flags[0] & (1 << 1)) { + if (tf->len_sue[0] < 5 + (nts+1) * s) { + printf("[ERROR] RR TF entry too short.\n"); + info->error = LIBISOFS_WRONG_RR; + return -1; + } + if (s == 7) { + time = iso_datetime_read_7(&tf->data.TF.t_stamps[nts*7]); + } else { + time = iso_datetime_read_17(&tf->data.TF.t_stamps[nts*17]); + } + st->st_mtime = time; + ++nts; + } + + /* 3. access time */ + if (tf->data.TF.flags[0] & (1 << 2)) { + if (tf->len_sue[0] < 5 + (nts+1) * s) { + printf("[ERROR] RR TF entry too short.\n"); + info->error = LIBISOFS_WRONG_RR; + return -1; + } + if (s == 7) { + time = iso_datetime_read_7(&tf->data.TF.t_stamps[nts*7]); + } else { + time = iso_datetime_read_17(&tf->data.TF.t_stamps[nts*17]); + } + st->st_atime = time; + ++nts; + } + + /* 4. attributes time */ + if (tf->data.TF.flags[0] & (1 << 3)) { + if (tf->len_sue[0] < 5 + (nts+1) * s) { + printf("[ERROR] RR TF entry too short.\n"); + info->error = LIBISOFS_WRONG_RR; + return -1; + } + if (s == 7) { + time = iso_datetime_read_7(&tf->data.TF.t_stamps[nts*7]); + } else { + time = iso_datetime_read_17(&tf->data.TF.t_stamps[nts*17]); + } + st->st_ctime = time; + ++nts; + } + + /* we ignore backup, expire and effect times */ + + return 0; +} + +char * +read_rr_NM(struct susp_sys_user_entry *nm, char *name) +{ + assert(nm); + assert( nm->sig[0] == 'N' && nm->sig[1] == 'M'); + + /* concatenate the results */ + if (name) { + name = realloc(name, strlen(name) + nm->len_sue[0] - 5 + 1); + strncat(name, (char*)nm->data.NM.name, nm->len_sue[0] - 5); + } else { + name = strcopy((char*)nm->data.NM.name, nm->len_sue[0] - 5); + } + + return name; +} + +char * +read_rr_SL(struct susp_sys_user_entry *sl, char *dest) +{ + int pos; + assert(sl); + assert( sl->sig[0] == 'S' && sl->sig[1] == 'L'); + + for (pos = 0; pos + 5 < sl->len_sue[0]; + pos += 2 + sl->data.SL.comps[pos + 1]) { + char *comp; + uint8_t len; + uint8_t flags = sl->data.SL.comps[pos]; + + if (flags & 0x2) { + /* current directory */ + len = 1; + comp = "."; + } else if (flags & 0x4) { + /* parent directory */ + len = 2; + comp = ".."; + } else if (flags & 0x8) { + /* root directory */ + len = 1; + comp = "/"; + } else if (flags & ~0x01) { + printf("[ERROR] SL component flag %x not supported.\n", flags); + return NULL; + } else { + len = sl->data.SL.comps[pos + 1]; + comp = (char*)&sl->data.SL.comps[pos + 2]; + } + + if (dest) { + int size = strlen(dest); + dest = realloc(dest, strlen(dest) + len + 2); + if ( dest[size-1] != '/' ) { + dest[size] = '/'; + dest[size+1] = '\0'; + } + strncat(dest, comp, len); + } else { + dest = strcopy(comp, len); + } + } + + return dest; +} + +struct susp_iterator { + + uint8_t* base; + int pos; + int size; + struct iso_read_info *info; + + uint32_t ce_block; + uint32_t ce_off; + uint32_t ce_len; /*< Length of the next continuation area, 0 if + no more CA are specified */ + + uint8_t *buffer; /*< If there are continuation areas */ +}; + +struct susp_iterator * +susp_iter_new(struct iso_read_info *info, struct ecma119_dir_record *record) +{ + struct susp_iterator *iter = malloc(sizeof(struct susp_iterator)); + int pad = (record->len_fi[0] + 1) % 2; + + iter->base = record->file_id + record->len_fi[0] + pad; + iter->pos = info->len_skp; /* 0 in most cases */ + iter->size = record->len_dr[0] - record->len_fi[0] - 33 -pad; + iter->info = info; + + iter->ce_len = 0; + iter->buffer = NULL; + + return iter; +} + +struct susp_sys_user_entry * +susp_iter_next(struct susp_iterator* iter) +{ + struct susp_sys_user_entry *entry; + + entry = (struct susp_sys_user_entry*)(iter->base + iter->pos); + + if ( (iter->pos + 4 > iter->size) || (SUSP_SIG(entry, 'S', 'T')) ) { + /* + * End of the System Use Area or Continuation Area. + * Note that ST is not needed when the space left is less than 4. + * (IEEE 1281, SUSP. section 4) + */ + if (iter->ce_len) { + uint32_t block; + int nblocks; + + /* A CE has found, there is another continuation area */ + nblocks = div_up(iter->ce_off + iter->ce_len, BLOCK_SIZE); + iter->buffer = realloc(iter->buffer, nblocks * BLOCK_SIZE); + + /* read all block needed to cache the full CE */ + for (block = 0; block < nblocks; ++block) { + if (iter->info->src->read_block(iter->info->src, + iter->ce_block + block, + iter->buffer + block * BLOCK_SIZE)) { + iter->info->error = LIBISOFS_READ_FAILURE; + return NULL; + } + } + iter->base = iter->buffer + iter->ce_off; + iter->pos = 0; + iter->size = iter->ce_len; + iter->ce_len = 0; + entry = (struct susp_sys_user_entry*)iter->base; + } else { + return NULL; + } + } + + if (entry->len_sue[0] == 0) { + /* a wrong image with this lead us to a infinity loop */ + printf("[ERROR] Damaged RR/SUSP information.\n"); + iter->info->error = LIBISOFS_WRONG_RR; + return NULL; + } + + iter->pos += entry->len_sue[0]; + + if ( SUSP_SIG(entry, 'C', 'E') ) { + /* Continuation entry */ + if (iter->ce_len) { + printf("[WARN] More than one CE System user entry has found " + "in a single System Use field or continuation area.\n" + "This breaks SUSP standard and it's not supported.\n" + "Ignoring last CE. Maybe the image is damaged.\n"); + } else { + iter->ce_block = iso_read_bb(entry->data.CE.block, 4, NULL); + iter->ce_off = iso_read_bb(entry->data.CE.offset, 4, NULL); + iter->ce_len = iso_read_bb(entry->data.CE.len, 4, NULL); + } + + /* we don't want to return CE entry to the user */ + return susp_iter_next(iter); + } else if ( SUSP_SIG(entry, 'P', 'D') ) { + /* skip padding */ + return susp_iter_next(iter); + } + + return entry; +} + +void +susp_iter_free(struct susp_iterator* iter) +{ + free(iter->buffer); + free(iter); +} diff --git a/libisofs/ecma119_read_rr.h b/libisofs/ecma119_read_rr.h new file mode 100644 index 0000000..1195803 --- /dev/null +++ b/libisofs/ecma119_read_rr.h @@ -0,0 +1,144 @@ +/* + * This file contains functions related to the reading of SUSP and + * Rock Ridge extensions on an ECMA-119 image. + */ + +#ifndef ECMA119_READ_RR_H_ +#define ECMA119_READ_RR_H_ + +#include "libisofs.h" +#include "ecma119.h" +#include "ecma119_read.h" + +#define SUSP_SIG(entry, a, b) ( (entry->sig[0] == a) && (entry->sig[1] == b) ) + +/** + * The SUSP iterator is used to iterate over the System User Entries + * of a ECMA-168 directory record. + * It takes care about Continuation Areas, handles the end of the different + * system user entries and skip padding areas. Thus, using an iteration + * we are accessing just to the meaning entries. + */ +struct susp_iterator; + +struct susp_iterator *susp_iter_new(struct iso_read_info *info, + struct ecma119_dir_record *record); + +/** + * Get the next SUSP System User Entry using given iterator. + * The returned pointer refers directly to an internal buffer and it's not + * guaranteed to be allocated after calling susp_iter_next() again. Thus, + * if you need to keep some entry you have to do a copy. + * + * It return NULL when no more entries are available. Also, it will return + * NULL on error. You must check info->error to distinguish between both + * situations. + */ +struct susp_sys_user_entry *susp_iter_next(struct susp_iterator* iter); + +/** + * Free a given susp iterator. + */ +void susp_iter_free(struct susp_iterator* iter); + +struct susp_CE { + uint8_t block[8]; + uint8_t offset[8]; + uint8_t len[8]; +}; + +struct susp_SP { + uint8_t be[1]; + uint8_t ef[1]; + uint8_t len_skp[1]; +}; + +struct susp_ER { + uint8_t len_id[1]; + uint8_t len_des[1]; + uint8_t len_src[1]; + uint8_t ext_ver[1]; + uint8_t ext_id[1]; /*< up to len_id bytes */ + /* ext_des, ext_src */ +}; + +/** POSIX file attributes. */ +struct rr_PX { + uint8_t mode[8]; + uint8_t links[8]; + uint8_t uid[8]; + uint8_t gid[8]; + uint8_t serial[8]; +}; + +/** Time stamps for a file. */ +struct rr_TF { + uint8_t flags[1]; + uint8_t t_stamps[1]; +}; + +/** Alternate name. */ +struct rr_NM { + uint8_t flags[1]; + uint8_t name[1]; +}; + +/** Link for a relocated directory. */ +struct rr_CL { + uint8_t child_loc[8]; +}; + +/** Sim link. */ +struct rr_SL { + uint8_t flags[1]; + uint8_t comps[1]; +}; + +/** + * Struct for a SUSP System User Entry + */ +struct susp_sys_user_entry +{ + uint8_t sig[2]; + uint8_t len_sue[1]; + uint8_t version[1]; + union { + struct susp_CE CE; + struct susp_SP SP; + struct susp_ER ER; + struct rr_PX PX; + struct rr_TF TF; + struct rr_NM NM; + struct rr_CL CL; + struct rr_SL SL; + } data; /* 5 to 4+len_sue */ +}; + +/** + * Fills a struct stat with the values of a Rock Ridge PX entry + * On error, info->error is set propertly and the function returns != 0 + */ +int read_rr_PX(struct iso_read_info *info, struct susp_sys_user_entry *px, + struct stat *st); + +/** + * Fills a struct stat with the values of a Rock Ridge TF entry + * On error, info->error is set propertly and the function returns != 0 + */ +int read_rr_TF(struct iso_read_info *info, struct susp_sys_user_entry *tf, + struct stat *st); + +/** + * Apends the content of given Rock Ridge NM entry to \p name + * On error, returns NULL + */ +char *read_rr_NM(struct susp_sys_user_entry *nm, char *name); + +/** + * Apends the components in specified SL entry to \p dest, adding + * needed '/'. + * On error, returns NULL + */ +char *read_rr_SL(struct susp_sys_user_entry *sl, char *dest); + +#endif /*ECMA119_READ_RR_H_*/