530 lines
14 KiB
C
530 lines
14 KiB
C
/* Reader for SUSP and Rock Ridge information.
|
|
|
|
Copyright (c) 2013 Thomas Schmitt <scdbackup@gmx.net>
|
|
Provided under GNU General Public License version 2 or later.
|
|
|
|
Based on:
|
|
SUSP 1.12 (entries CE , PD , SP , ST , ER , ES)
|
|
ftp://ftp.ymi.com/pub/rockridge/susp112.ps
|
|
RRIP 1.12 (entries PX , PN , SL , NM , CL , PL , RE , TF , SF)
|
|
ftp://ftp.ymi.com/pub/rockridge/rrip112.ps
|
|
ECMA-119 aka ISO 9660
|
|
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf
|
|
|
|
Shortcommings / Future improvements:
|
|
(XXX): Avoid memcpy() with Continuation Areas wich span over more than one
|
|
block ? (Will then need memcpy() with entries which are hit by a
|
|
block boundary.) (Questionable whether the effort is worth it.)
|
|
(XXX): Take into respect ES entries ? (Hardly anybody does this.)
|
|
|
|
*/
|
|
|
|
#ifndef Isolinux_rockridge_in_libisofS
|
|
|
|
/* Mindlessly copied from core/fs/iso9660/iso9660.c */
|
|
#include <dprintf.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/dirent.h>
|
|
#include <core.h>
|
|
#include <cache.h>
|
|
#include <disk.h>
|
|
#include <fs.h>
|
|
#include <byteswap.h>
|
|
#include "iso9660_fs.h"
|
|
|
|
#else /* ! Isolinux_rockridge_in_libisofS */
|
|
|
|
/* ====== Test mock-up of definitions which should come from syslinux ====== */
|
|
|
|
/* With defined Isolinux_rockridge_in_libisofS this source file can be included
|
|
into libisofs/fs_image.c and the outcome of its public functions can be
|
|
compared with the perception of libisofs when loading an ISO image.
|
|
|
|
Test results look ok with 50 ISO images when read by xorriso underneath
|
|
valgrind.
|
|
*/
|
|
|
|
typedef uint32_t block_t;
|
|
|
|
#define dprintf printf
|
|
|
|
struct device {
|
|
IsoDataSource *src;
|
|
};
|
|
|
|
|
|
struct susp_rr_dir_rec_wrap {
|
|
char data[256];
|
|
};
|
|
|
|
struct iso_sb_info {
|
|
struct susp_rr_dir_rec_wrap root;
|
|
|
|
int do_rr; /* 1 = try to process Rock Ridge info , 0 = do not */
|
|
int susp_skip; /* Skip length from SUSP enntry SP */
|
|
};
|
|
|
|
struct fs_info {
|
|
struct device *fs_dev;
|
|
struct iso_sb_info *fs_info;
|
|
};
|
|
|
|
#define get_cache dummy_get_cache
|
|
|
|
static char *dummy_get_cache(struct device *fs_dev, block_t lba)
|
|
{
|
|
static uint8_t buf[2048];
|
|
int ret;
|
|
|
|
ret = fs_dev->src->read_block(fs_dev->src, lba, buf);
|
|
if (ret < 0)
|
|
return NULL;
|
|
return (char *) buf;
|
|
}
|
|
|
|
/* =========================== End of test mock-up ========================= */
|
|
|
|
#endif /* ! Isolinux_rockridge_for_reaL */
|
|
|
|
|
|
static int susp_rr_is_out_of_mem(void *pt)
|
|
{
|
|
if (pt != NULL)
|
|
return 0;
|
|
dprintf("susp_rr.c: Out of memory !\n");
|
|
|
|
/* XXX : Should one abort on global level ? */
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static uint32_t susp_rr_read_lsb32(const void *buf)
|
|
{
|
|
return get_le32(buf);
|
|
}
|
|
|
|
|
|
/* State of iteration over SUSP entries.
|
|
|
|
This would be quite trivial if there was not the possibility of Continuation
|
|
Areas announced by the CE entry. In general they are quite rare, because
|
|
often all Rock Ridge entries fit into the ISO 9660 directory record.
|
|
So it seems unwise to invest much complexity into optimization of
|
|
Continuation Areas.
|
|
(I found 35 CE in a backup of mine which contains 63000 files, 2 CE in
|
|
a Debian netinst ISO, 2 CE in a Fedora live CD.)
|
|
*/
|
|
struct susp_rr_iter {
|
|
struct fs_info *fs; /* From where to read Continuation Area data */
|
|
char *dir_rec; /* ISO 9660 directory record */
|
|
int in_ce; /* 0= still reading dir_rec, 1= reading ce_data */
|
|
char *ce_data; /* Loaded Continuation Area data */
|
|
int ce_allocated; /* 0= do not free ce_data, 1= do free */
|
|
size_t read_pos; /* Current read offset in dir_rec or ce_data */
|
|
size_t read_end; /* Current first invalid read_pos */
|
|
|
|
block_t next_lba; /* Block number of start of next Continuation Area */
|
|
size_t next_offset; /* Byte offset within the next_lba block */
|
|
size_t next_length; /* Number of valid bytes in next Cont. Area */
|
|
};
|
|
|
|
|
|
static int susp_rr_iter_new(struct susp_rr_iter **iter,
|
|
struct fs_info *fs, char *dir_rec)
|
|
{
|
|
struct iso_sb_info *sbi = fs->fs_info;
|
|
struct susp_rr_iter *o;
|
|
uint8_t len_fi;
|
|
int read_pos, read_end;
|
|
|
|
len_fi = ((uint8_t *) dir_rec)[32];
|
|
read_pos = 33 + len_fi + !(len_fi % 2) + sbi->susp_skip;
|
|
read_end = ((uint8_t *) dir_rec)[0];
|
|
if (read_pos + 4 > read_end)
|
|
return 0; /* Not enough System Use data present for SUSP */
|
|
if (dir_rec[read_pos + 3] != 1)
|
|
return 0; /* Not SUSP version 1 */
|
|
|
|
o= *iter= malloc(sizeof(struct susp_rr_iter));
|
|
if (susp_rr_is_out_of_mem(o))
|
|
return -1;
|
|
o->fs = fs;
|
|
o->dir_rec= dir_rec;
|
|
o->in_ce= 0;
|
|
o->read_pos = read_pos;
|
|
o->read_end = read_end;
|
|
o->next_lba = 0;
|
|
o->next_offset = o->next_length = 0;
|
|
o->ce_data = NULL;
|
|
o->ce_allocated = 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int susp_rr_iter_destroy(struct susp_rr_iter **iter)
|
|
{
|
|
struct susp_rr_iter *o;
|
|
|
|
o = *iter;
|
|
if (o == NULL)
|
|
return 0;
|
|
if (o->ce_data != NULL && o->ce_allocated)
|
|
free(o->ce_data);
|
|
free(o);
|
|
*iter = NULL;
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Switch to next Continuation Area.
|
|
*/
|
|
static int susp_rr_switch_to_ca(struct susp_rr_iter *iter)
|
|
{
|
|
block_t num_blocks, i;
|
|
const char *data = NULL;
|
|
|
|
num_blocks = (iter->next_offset + iter->next_length + 2047) / 2048;
|
|
|
|
if (iter->ce_data != NULL && iter->ce_allocated)
|
|
free(iter->ce_data);
|
|
iter->ce_data = NULL;
|
|
iter->ce_allocated = 0;
|
|
if (num_blocks > 1) {
|
|
/* The blocks are expected contiguously. Need to consolidate them. */
|
|
if (num_blocks > 50) {
|
|
dprintf("susp_rr.c: More than 100 KB claimed by a CE entry.\n");
|
|
return -1;
|
|
}
|
|
iter->ce_data = malloc(num_blocks * 2048);
|
|
if (susp_rr_is_out_of_mem(iter->ce_data))
|
|
return -1;
|
|
iter->ce_allocated = 1;
|
|
for (i = 0; i < num_blocks; i++) {
|
|
data = get_cache(iter->fs->fs_dev, iter->next_lba + i);
|
|
if (data == NULL) {
|
|
dprintf("susp_rr.c: Failure to read block %lu\n",
|
|
(unsigned long) iter->next_lba + i);
|
|
return -1;
|
|
}
|
|
memcpy(iter->ce_data + i * 2048, data, 2048);
|
|
}
|
|
} else {
|
|
/* Avoiding malloc() and memcpy() in the single block case */
|
|
data = get_cache(iter->fs->fs_dev, iter->next_lba);
|
|
if (data == NULL) {
|
|
dprintf("susp_rr.c: Failure to read block %lu\n",
|
|
(unsigned long) iter->next_lba);
|
|
return -1;
|
|
}
|
|
iter->ce_data = (char *) data;
|
|
}
|
|
|
|
iter->in_ce = 1;
|
|
iter->read_pos = iter->next_offset;
|
|
iter->read_end = iter->next_offset + iter->next_length;
|
|
iter->next_lba = 0;
|
|
iter->next_offset = iter->next_length = 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Obtain the next SUSP entry.
|
|
*/
|
|
static int susp_rr_iterate(struct susp_rr_iter *iter, char **pos_pt)
|
|
{
|
|
char *entries;
|
|
uint8_t susp_len, *u_entry;
|
|
int ret;
|
|
|
|
if (iter->in_ce) {
|
|
entries = iter->ce_data + iter->read_pos;
|
|
} else {
|
|
entries = iter->dir_rec + iter->read_pos;
|
|
}
|
|
if (iter->read_pos + 4 <= iter->read_end)
|
|
if (entries[3] != 1) {
|
|
/* Not SUSP version 1 */
|
|
dprintf("susp_rr.c: Chain of SUSP entries broken\n");
|
|
return -1;
|
|
}
|
|
if (iter->read_pos + 4 > iter->read_end ||
|
|
(entries[0] == 'S' && entries[1] == 'T')) {
|
|
/* This part of the SU area is done */
|
|
if (iter->next_length == 0) {
|
|
/* No further CE entry was encountered. Iteration ends now. */
|
|
return 0;
|
|
}
|
|
ret = susp_rr_switch_to_ca(iter);
|
|
if (ret <= 0)
|
|
return ret;
|
|
entries = iter->ce_data + iter->read_pos;
|
|
}
|
|
|
|
if (entries[0] == 'C' && entries[1] == 'E') {
|
|
if (iter->next_length > 0) {
|
|
dprintf("susp_rr.c: Surplus CE entry detected\n");
|
|
return -1;
|
|
}
|
|
/* Register address data of next Continuation Area */
|
|
u_entry = (uint8_t *) entries;
|
|
iter->next_lba = susp_rr_read_lsb32(u_entry + 4);
|
|
iter->next_offset = susp_rr_read_lsb32(u_entry + 12);
|
|
iter->next_length = susp_rr_read_lsb32(u_entry + 20);
|
|
}
|
|
|
|
*pos_pt = entries;
|
|
susp_len = ((uint8_t *) entries)[2];
|
|
iter->read_pos += susp_len;
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Check for SP entry at position try_skip in the System Use area.
|
|
*/
|
|
static int susp_rr_check_sp(struct fs_info *fs, char *dir_rec, int try_skip)
|
|
{
|
|
struct iso_sb_info *sbi = fs->fs_info;
|
|
int read_pos, read_end, len_fi;
|
|
uint8_t *sua;
|
|
|
|
len_fi = ((uint8_t *) dir_rec)[32];
|
|
read_pos = 33 + len_fi + !(len_fi % 2) + try_skip;
|
|
read_end = ((uint8_t *) dir_rec)[0];
|
|
if (read_end - read_pos < 7)
|
|
return 0;
|
|
sua = (uint8_t *) (dir_rec + read_pos);
|
|
if (sua[0] != 'S' || sua[1] != 'P' || sua[2] != 7 || sua[3] != 1 ||
|
|
sua[4] != 0xbe || sua[5] != 0xef)
|
|
return 0;
|
|
dprintf("susp_rr.c: SUSP signature detected\n");
|
|
sbi->susp_skip = ((uint8_t *) dir_rec)[6];
|
|
if (sbi->susp_skip > 0 && sbi->susp_skip != try_skip)
|
|
dprintf("susp_rr.c: Unusual: Non-zero skip length in SP entry\n");
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Public function. See susp_rr.h
|
|
|
|
Rock Ridge specific knowledge about NM and SL has been integrated here,
|
|
because this saves one malloc and memcpy for the file name.
|
|
*/
|
|
int susp_rr_get_entries(struct fs_info *fs, char *dir_rec, char *sig,
|
|
char **data, int *len_data, int flag)
|
|
{
|
|
int count = 0, ret = 0, head_skip = 4, nmsp_flags = -1, is_done = 0;
|
|
char *pos_pt, *new_data;
|
|
uint8_t pay_len;
|
|
struct susp_rr_iter *iter = NULL;
|
|
struct iso_sb_info *sbi = fs->fs_info;
|
|
|
|
*data = NULL;
|
|
*len_data = 0;
|
|
|
|
if (!sbi->do_rr)
|
|
return 0; /* Rock Ridge is not enabled */
|
|
|
|
if (flag & 1)
|
|
head_skip = 5;
|
|
|
|
ret = susp_rr_iter_new(&iter, fs, dir_rec);
|
|
if (ret <= 0)
|
|
goto ex;
|
|
while (!is_done) {
|
|
ret = susp_rr_iterate(iter, &pos_pt);
|
|
if (ret < 0)
|
|
goto ex;
|
|
if (ret == 0)
|
|
break; /* End SUSP iteration */
|
|
if (sig[0] != pos_pt[0] || sig[1] != pos_pt[1])
|
|
continue; /* Next SUSP iteration */
|
|
|
|
pay_len = ((uint8_t *) pos_pt)[2];
|
|
if (pay_len < head_skip) {
|
|
dprintf("susp_rr.c: Short NM entry encountered.\n");
|
|
ret = -1;
|
|
goto ex;
|
|
}
|
|
pay_len -= head_skip;
|
|
if ((flag & 1)) {
|
|
if (nmsp_flags < 0)
|
|
nmsp_flags = ((uint8_t *) pos_pt)[4];
|
|
if (!(pos_pt[4] & 1)) /* No CONTINUE bit */
|
|
is_done = 1; /* This is the last iteration cycle */
|
|
}
|
|
count += pay_len;
|
|
if (count > 102400) {
|
|
dprintf("susp_rr.c: More than 100 KB in '%c%c' entries.\n",
|
|
sig[0], sig[1]);
|
|
ret = -1;
|
|
goto ex;
|
|
}
|
|
new_data = malloc(count + 1);
|
|
if (susp_rr_is_out_of_mem(new_data)) {
|
|
ret = -1;
|
|
goto ex;
|
|
}
|
|
if (*data != NULL) {
|
|
/* This case should be rare. An extra iteration pass to predict
|
|
the needed data size would hardly pay off.
|
|
*/
|
|
memcpy(new_data, *data, *len_data);
|
|
free(*data);
|
|
}
|
|
new_data[count] = 0;
|
|
*data = new_data;
|
|
memcpy(*data + *len_data, pos_pt + head_skip, pay_len);
|
|
*len_data += pay_len;
|
|
}
|
|
if (*data == NULL) {
|
|
ret = 0;
|
|
} else if (flag & 1) {
|
|
ret = 0x100 | nmsp_flags;
|
|
} else {
|
|
ret = 1;
|
|
}
|
|
ex:;
|
|
susp_rr_iter_destroy(&iter);
|
|
if (ret <= 0 && *data != NULL) {
|
|
free(*data);
|
|
*data = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Public function. See susp_rr.h
|
|
*/
|
|
int susp_rr_get_nm(struct fs_info *fs, char *dir_rec,
|
|
char **name, int *len_name)
|
|
{
|
|
int ret;
|
|
|
|
ret = susp_rr_get_entries(fs, dir_rec, "NM", name, len_name, 1);
|
|
if (ret <= 0)
|
|
return ret;
|
|
|
|
/* Interpret flags */
|
|
if (ret & 0x6) {
|
|
if (*name != NULL)
|
|
free(*name);
|
|
*len_name = 0;
|
|
*name = strdup(ret & 0x2 ? "." : "..");
|
|
if (susp_rr_is_out_of_mem(*name)) {
|
|
return -1;
|
|
}
|
|
*len_name = strlen(*name);
|
|
}
|
|
if (*len_name >= 256) {
|
|
dprintf("susp_rr.c: Rock Ridge name longer than 255 characters.\n");
|
|
free(*name);
|
|
*name = NULL;
|
|
*len_name = 0;
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Public function. See susp_rr.h
|
|
*/
|
|
int susp_rr_check_signatures(struct fs_info *fs, int flag)
|
|
{
|
|
struct iso_sb_info *sbi = fs->fs_info;
|
|
char *dir_rec;
|
|
char *data = NULL;
|
|
uint8_t *u_data;
|
|
block_t lba;
|
|
int len_data, i, len_er = 0, len_id, ret;
|
|
int rrip_112 = 0;
|
|
|
|
sbi->do_rr = 1; /* provisory for the time of examination */
|
|
sbi->susp_skip = 0;
|
|
|
|
#ifndef Isolinux_rockridge_in_libisofS
|
|
/* (There is a name collision with libisofs BLOCK_SIZE. On the other hand,
|
|
libisofs has hardcoded blocksize 2048.) */
|
|
|
|
/* For now this works only with 2 KB blocks */
|
|
if (BLOCK_SIZE(fs) != 2048) {
|
|
dprintf("susp_rr.c: Block size is not 2048. Rock Ridge disabled.\n");
|
|
goto no_susp;
|
|
}
|
|
|
|
#endif /* Isolinux_rockridge_in_libisofS */
|
|
|
|
/* Obtain first dir_rec of root directory */
|
|
lba = susp_rr_read_lsb32(((uint8_t *) &(sbi->root)) + 2);
|
|
dir_rec = (char *) get_cache(fs->fs_dev, lba);
|
|
if (dir_rec == NULL)
|
|
goto no_susp;
|
|
|
|
/* First System Use entry must be SP */
|
|
ret = susp_rr_check_sp(fs, dir_rec, 0);
|
|
if (ret == 0) {
|
|
/* SUSP 1.12 prescribes that on CD-ROM XA discs the SP entry is at
|
|
offset 14 of the System Use area.
|
|
How to detect a CD-ROM XA disc here ?
|
|
(libisofs ignores this prescription and lives well with that.
|
|
/usr/src/linux/fs/isofs/ makes a blind try with 14.)
|
|
*/
|
|
ret = susp_rr_check_sp(fs, dir_rec, 14);
|
|
}
|
|
if (ret <= 0)
|
|
goto no_susp;
|
|
|
|
if (!(flag & 1)) {
|
|
ret = 1;
|
|
goto ex;
|
|
}
|
|
|
|
/* Look for ER entries */
|
|
ret = susp_rr_get_entries(fs, dir_rec, "ER", &data, &len_data, 0);
|
|
if (ret <= 0 || len_data < 8)
|
|
goto no_rr;
|
|
u_data = (uint8_t *) data;
|
|
for (i = 0; i < len_data; i += len_er) {
|
|
len_id = u_data[0];
|
|
len_er = 4 + len_id + u_data[1] + u_data[2];
|
|
if (i + len_er > len_data) {
|
|
dprintf("susp_rr.c: Error with field lengths in ER entry\n");
|
|
goto no_rr;
|
|
}
|
|
if (len_id == 10 && strncmp(data + 4, "RRIP_1991A", len_id) == 0) {
|
|
dprintf("susp_rr.c: Signature of Rock Ridge 1.10 detected\n");
|
|
break;
|
|
} else if ((len_id == 10 &&
|
|
strncmp(data + 4, "IEEE_P1282", len_id) == 0) ||
|
|
(len_id == 9 &&
|
|
strncmp(data + 4, "IEEE_1282", len_id) == 0)) {
|
|
dprintf("susp_rr.c: Signature of Rock Ridge 1.12 detected\n");
|
|
rrip_112 = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (i >= len_data)
|
|
goto no_rr;
|
|
|
|
sbi->do_rr = 1 + rrip_112;
|
|
ret = 2 + rrip_112;
|
|
goto ex;
|
|
|
|
no_susp:;
|
|
dprintf("susp_rr.c: No SUSP signature detected\n");
|
|
ret = 0;
|
|
goto ex;
|
|
|
|
no_rr:;
|
|
dprintf("susp_rr.c: No Rock Ridge signature detected\n");
|
|
ret = 0;
|
|
|
|
ex:;
|
|
if (ret <= 0)
|
|
sbi->do_rr = 0;
|
|
if (data != NULL)
|
|
free(data);
|
|
return ret;
|
|
}
|