/* * Copyright (c) 2007 Vreixo Formoso * Copyright (c) 2009 Thomas Schmitt * * 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 * or later as published by the Free Software Foundation. * See COPYING file for details. */ /* * This file contains functions related to the reading of SUSP, * Rock Ridge and AAIP extensions on an ECMA-119 image. */ #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include "libisofs.h" #include "ecma119.h" #include "util.h" #include "rockridge.h" #include "messages.h" #include #include #include struct susp_iterator { uint8_t* base; int pos; int size; IsoDataSource *src; int msgid; /* Number of blocks in the ISO 9660 filesystem */ uint32_t fs_blocks; /* block and offset for next continuation area */ uint32_t ce_block; uint32_t ce_off; /** Length of the next continuation area, 0 if no more CA are specified */ uint32_t ce_len; uint8_t *buffer; /*< If there are continuation areas */ }; SuspIterator* susp_iter_new(IsoDataSource *src, struct ecma119_dir_record *record, uint32_t fs_blocks, uint8_t len_skp, int msgid) { int pad = (record->len_fi[0] + 1) % 2; struct susp_iterator *iter = malloc(sizeof(struct susp_iterator)); if (iter == NULL) { return NULL; } iter->base = record->file_id + record->len_fi[0] + pad; iter->pos = len_skp; /* 0 in most cases */ iter->size = record->len_dr[0] - record->len_fi[0] - 33 - pad; iter->src = src; iter->msgid = msgid; iter->fs_blocks = fs_blocks; iter->ce_len = 0; iter->buffer = NULL; return iter; } /* More than 1 MiB in a single file's CE area is suspicious */ #define ISO_SUSP_MAX_CE_BYTES (1024 * 1024) /* @param flag bit0 = First call on root: Not yet clear whether this is SUSP at all */ int susp_iter_next(SuspIterator *iter, struct susp_sys_user_entry **sue, int flag) { struct susp_sys_user_entry *entry; entry = (struct susp_sys_user_entry*)(iter->base + iter->pos); if (flag & 1) { /* Yet unclear whether it is SUSP at all */ if (iter->size < 7) return 0; if (!SUSP_SIG(entry, 'S', 'P')) return 0; if (entry->len_sue[0] < 7) return 0; /* Looks like SUSP enough to pass the further processing here. */ } 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, nblocks, skipped_blocks, skipped_bytes; /* A CE was found, there is another continuation area */ skipped_blocks = iter->ce_off / BLOCK_SIZE; skipped_bytes = skipped_blocks * BLOCK_SIZE; nblocks = DIV_UP(iter->ce_off - skipped_bytes + iter->ce_len, BLOCK_SIZE); if (nblocks <= 0 || iter->ce_len > ISO_SUSP_MAX_CE_BYTES) return ISO_SUSP_WRONG_CE_SIZE; if (((uint64_t) iter->ce_block) + skipped_blocks + nblocks > (uint64_t) iter->fs_blocks) return ISO_SUSP_WRONG_CE_SIZE; iter->buffer = realloc(iter->buffer, nblocks * BLOCK_SIZE); /* Read blocks needed to cache the given CE area range */ for (block = 0; block < nblocks; ++block) { int ret; ret = iter->src->read_block(iter->src, iter->ce_block + skipped_blocks + block, iter->buffer + block * BLOCK_SIZE); if (ret < 0) { return ret; } } iter->base = iter->buffer + (iter->ce_off - skipped_bytes); iter->pos = 0; iter->size = iter->ce_len; iter->ce_len = 0; entry = (struct susp_sys_user_entry*)iter->base; } else { return 0; } } if (entry->len_sue[0] == 0) { /* a wrong image with this lead us to a infinity loop */ iso_msg_submit(iter->msgid, ISO_WRONG_RR, 0, "Damaged RR/SUSP information."); return ISO_WRONG_RR; } iter->pos += entry->len_sue[0]; if (SUSP_SIG(entry, 'C', 'E')) { /* Continuation entry */ if (iter->ce_len) { int ret; ret = iso_msg_submit(iter->msgid, ISO_UNSUPPORTED_SUSP, 0, "More than one CE System user entry has found in a single " "System Use field or continuation area. This breaks SUSP " "standard and it's not supported. Ignoring last CE. Maybe " "the image is damaged."); if (ret < 0) { return ret; } } 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, sue, 0); } else if (SUSP_SIG(entry, 'P', 'D')) { /* skip padding */ return susp_iter_next(iter, sue, 0); } *sue = entry; return ISO_SUCCESS; } void susp_iter_free(SuspIterator *iter) { free(iter->buffer); free(iter); } /** * Fills a struct stat with the values of a Rock Ridge PX entry (RRIP, 4.1.1). * * @return * 1 on success, < 0 on error */ int read_rr_PX(struct susp_sys_user_entry *px, struct stat *st) { if (px == NULL || st == NULL) { return ISO_NULL_POINTER; } if (px->sig[0] != 'P' || px->sig[1] != 'X') { return ISO_WRONG_ARG_VALUE; } if (px->len_sue[0] != 44 && px->len_sue[0] != 36) { return ISO_WRONG_RR; } 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); st->st_ino = 0; if (px->len_sue[0] == 44) { /* this corresponds to RRIP 1.12, so we have inode serial number */ st->st_ino = iso_read_bb(px->data.PX.serial, 4, NULL); /* Indicate that st_ino is valid */ return 2; } return 1; } /** * Fills a struct stat with the values of a Rock Ridge TF entry (RRIP, 4.1.6) * * @return * 1 on success, < 0 on error */ int read_rr_TF(struct susp_sys_user_entry *tf, struct stat *st) { time_t time; int s; int nts = 0; if (tf == NULL || st == NULL) { return ISO_NULL_POINTER; } if (tf->sig[0] != 'T' || tf->sig[1] != 'F') { return ISO_WRONG_ARG_VALUE; } 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)) { /* Linux accepts ctime by Creation time and by Attributes time. * If both are given, then Attribute time will win. */ if (tf->len_sue[0] < 5 + (nts+1) * s) { /* RR TF entry too short. */ return ISO_WRONG_RR; } 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; } /* 2. modify time */ if (tf->data.TF.flags[0] & (1 << 1)) { if (tf->len_sue[0] < 5 + (nts+1) * s) { /* RR TF entry too short. */ return ISO_WRONG_RR; } 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) { /* RR TF entry too short. */ return ISO_WRONG_RR; } 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) { /* RR TF entry too short. */ return ISO_WRONG_RR; } 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 ISO_SUCCESS; } /** * Read a RR NM entry (RRIP, 4.1.4), and appends the name stored there to * the given name. You can pass a pointer to NULL as name. * * @return * 1 on success, < 0 on error */ int read_rr_NM(struct susp_sys_user_entry *nm, char **name, int *cont) { if (nm == NULL || name == NULL) { return ISO_NULL_POINTER; } if (nm->sig[0] != 'N' || nm->sig[1] != 'M') { return ISO_WRONG_ARG_VALUE; } if (nm->len_sue[0] == 5) { if (nm->data.NM.flags[0] & 0x2) { /* it is a "." entry */ if (*name == NULL) { return ISO_SUCCESS; } else { /* we can't have a previous not-NULL name */ return ISO_WRONG_RR; } } } if (nm->len_sue[0] <= 5) { /* ".." entry is an error, as we will never call it */ return ISO_WRONG_RR; } /* concatenate the results */ if (*cont) { *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 = iso_util_strcopy((char*)nm->data.NM.name, nm->len_sue[0] - 5); } if (*name == NULL) { return ISO_OUT_OF_MEM; } /* and set cond according to the value of CONTINUE flag */ *cont = nm->data.NM.flags[0] & 0x01; return ISO_SUCCESS; } /** * Read a SL RR entry (RRIP, 4.1.3), checking if the destination continues. * * @param cont * 0 not continue, 1 continue, 2 continue component * @return * 1 on success, < 0 on error */ int read_rr_SL(struct susp_sys_user_entry *sl, char **dest, int *cont) { int pos; if (sl == NULL || dest == NULL) { return ISO_NULL_POINTER; } if (sl->sig[0] != 'S' || sl->sig[1] != 'L') { return ISO_WRONG_ARG_VALUE; } 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) { /* unsupported flag component */ return ISO_UNSUPPORTED_RR; } else { len = sl->data.SL.comps[pos + 1]; comp = (char*)&sl->data.SL.comps[pos + 2]; } if (*cont == 1) { /* new component */ size_t size = strlen(*dest); int has_slash; *dest = realloc(*dest, strlen(*dest) + len + 2); if (*dest == NULL) { return ISO_OUT_OF_MEM; } /* it is a new compoenent, add the '/' */ has_slash = 0; if (size > 0) if ((*dest)[size - 1] == '/') has_slash = 1; if (!has_slash) { (*dest)[size] = '/'; (*dest)[size+1] = '\0'; } strncat(*dest, comp, len); } else if (*cont == 2) { /* the component continues */ *dest = realloc(*dest, strlen(*dest) + len + 1); if (*dest == NULL) { return ISO_OUT_OF_MEM; } /* we don't have to add the '/' */ strncat(*dest, comp, len); } else { *dest = iso_util_strcopy(comp, len); } if (*dest == NULL) { return ISO_OUT_OF_MEM; } /* do the component continue or not? */ *cont = (flags & 0x01) ? 2 : 1; } if (*cont == 2) { /* TODO check that SL flag is set to continute too ?*/ } else { *cont = sl->data.SL.flags[0] & 0x1 ? 1 : 0; } return ISO_SUCCESS; } /** * Fills a struct stat with the values of a Rock Ridge PN entry (RRIP, 4.1.2). * * @return * 1 on success, < 0 on error */ int read_rr_PN(struct susp_sys_user_entry *pn, struct stat *st) { int high_shift= 0; if (pn == NULL || st == NULL) { return ISO_NULL_POINTER; } if (pn->sig[0] != 'P' || pn->sig[1] != 'N') { return ISO_WRONG_ARG_VALUE; } if (pn->len_sue[0] != 20) { return ISO_WRONG_RR; } /* (dev_t << 32) causes compiler warnings on FreeBSD because sizeof(dev_t) is 4. */ st->st_rdev = (dev_t)iso_read_bb(pn->data.PN.low, 4, NULL); if (sizeof(st->st_rdev) > 4) { high_shift = 32; st->st_rdev |= (dev_t)((dev_t)iso_read_bb(pn->data.PN.high, 4, NULL) << high_shift); } /* was originally: st->st_rdev = (dev_t)((dev_t)iso_read_bb(pn->data.PN.high, 4, NULL) << 32) | (dev_t)iso_read_bb(pn->data.PN.low, 4, NULL); */ return ISO_SUCCESS; } /* AA is the obsolete field signature of AAIP versions < 2.0 */ int read_aaip_AA(struct susp_sys_user_entry *sue, unsigned char **aa_string, size_t *aa_size, size_t *aa_len, size_t *prev_field, int *is_done, int flag) { unsigned char *aapt; if (*is_done) { /* To coexist with Apple ISO : Gracefully react on possibly trailing Apple AA */ if (sue->version[0] != 1 || sue->len_sue[0] == 7) return ISO_SUCCESS; return ISO_WRONG_RR; } if (*aa_size == 0 || *aa_string == NULL) { /* Gracefully react on possibly leading Apple AA */ if (sue->version[0] != 1 || sue->len_sue[0] < 9) return ISO_SUCCESS; } /* A valid AAIP AA entry has 5 header bytes and at least 1 component byte */ if (sue->len_sue[0] < 6) return ISO_WRONG_RR; /* Possibly create or grow storage */ if (*aa_size == 0 || *aa_string == NULL) { *aa_size = *aa_len + sue->len_sue[0]; *aa_string = calloc(*aa_size, 1); *aa_len = 0; } else if (*aa_len + sue->len_sue[0] > *aa_size) { if (sue->version[0] != 1) { /* Apple ISO within the AAIP field group is not AAIP compliant */ return ISO_WRONG_RR; } *aa_size += *aa_len + sue->len_sue[0]; *aa_string = realloc(*aa_string, *aa_size); } if (*aa_string == NULL) return ISO_OUT_OF_MEM; if (*aa_len > 0) { /* Mark prev_field as being continued */ (*aa_string)[*prev_field + 4] = 1; } *prev_field = *aa_len; /* Compose new SUSP header with signature aa[], cont == 0 */ aapt = *aa_string + *aa_len; aapt[0] = 'A'; aapt[1] = 'L'; aapt[2] = sue->len_sue[0]; aapt[3] = 1; aapt[4] = 0; /* Append sue payload */ memcpy(aapt + 5, sue->data.AL.comps, sue->len_sue[0] - 5); *is_done = !(sue->data.AL.flags[0] & 1); *aa_len += sue->len_sue[0]; return ISO_SUCCESS; } /* AL is the field signature of AAIP versions >= 2.0 */ int read_aaip_AL(struct susp_sys_user_entry *sue, unsigned char **aa_string, size_t *aa_size, size_t *aa_len, size_t *prev_field, int *is_done, int flag) { unsigned char *aapt; if (*is_done) return ISO_WRONG_RR; if (sue->version[0] != 1) return ISO_WRONG_RR; /* A valid AL entry has 5 header bytes and at least 1 component byte */ if (sue->len_sue[0] < 6) return ISO_WRONG_RR; /* Possibly create or grow storage */ if (*aa_size == 0 || *aa_string == NULL) { *aa_size = *aa_len + sue->len_sue[0]; *aa_string = calloc(*aa_size, 1); *aa_len = 0; } else if (*aa_len + sue->len_sue[0] > *aa_size) { *aa_size += *aa_len + sue->len_sue[0]; *aa_string = realloc(*aa_string, *aa_size); } if (*aa_string == NULL) return ISO_OUT_OF_MEM; if (*aa_len > 0) { /* Mark prev_field as being continued */ (*aa_string)[*prev_field + 4] = 1; } *prev_field = *aa_len; /* Compose new SUSP header with signature aa[], cont == 0 */ aapt = *aa_string + *aa_len; aapt[0] = 'A'; aapt[1] = 'L'; aapt[2] = sue->len_sue[0]; aapt[3] = 1; aapt[4] = 0; /* Append sue payload */ memcpy(aapt + 5, sue->data.AL.comps, sue->len_sue[0] - 5); *is_done = !(sue->data.AL.flags[0] & 1); *aa_len += sue->len_sue[0]; return ISO_SUCCESS; } /** * Reads the zisofs parameters from a ZF field (see doc/zisofs_format.txt). * * @return * 1 on success, < 0 on error */ int read_zisofs_ZF(struct susp_sys_user_entry *zf, uint8_t algorithm[2], uint8_t *header_size_div4, uint8_t *block_size_log2, uint32_t *uncompressed_size, int flag) { if (zf == NULL) { return ISO_NULL_POINTER; } if (zf->sig[0] != 'Z' || zf->sig[1] != 'F') { return ISO_WRONG_ARG_VALUE; } if (zf->len_sue[0] != 16) { return ISO_WRONG_RR; } algorithm[0] = zf->data.ZF.parameters[0]; algorithm[1] = zf->data.ZF.parameters[1]; *header_size_div4 = zf->data.ZF.parameters[2]; *block_size_log2 = zf->data.ZF.parameters[3]; *uncompressed_size = iso_read_bb(&(zf->data.ZF.parameters[4]), 4, NULL); return ISO_SUCCESS; }