First cut at RockRidge support.

Large thanks to the easy-to-read and well-documented
sys/isofs/cd9660 source code, which provided many of the
details I needed for this exercise.
This commit is contained in:
Tim Kientzle 2005-01-03 05:51:33 +00:00
parent e87e3960b6
commit 8544432b98

View File

@ -70,7 +70,7 @@ __FBSDID("$FreeBSD$");
*/
/* Structure of on-disk PVD. */
struct primary_volume_descriptor {
struct iso9660_primary_volume_descriptor {
unsigned char type[1];
char id[5];
unsigned char version[1];
@ -106,7 +106,7 @@ struct primary_volume_descriptor {
};
/* Structure of an on-disk directory record. */
struct directory_record {
struct iso9660_directory_record {
unsigned char length[1];
unsigned char ext_attr_length[1];
unsigned char extent[8];
@ -126,15 +126,19 @@ struct directory_record {
*/
/* In-memory storage for a directory record. */
struct dir_rec {
struct dir_rec *parent;
struct file_info {
struct file_info *parent;
int refcount;
unsigned char flags;
uint64_t offset; /* Offset on disk. */
uint64_t size; /* File size in bytes. */
time_t mtime; /* File last modified time. */
time_t atime; /* File last accessed time. */
time_t ctime; /* File creation time. */
mode_t mode;
char name[1]; /* Null-terminated filename. */
uid_t uid;
gid_t gid;
int nlinks;
char *name; /* Null-terminated filename. */
};
@ -149,7 +153,7 @@ struct iso9660 {
struct archive_string previous_pathname;
/* TODO: Make this a heap for fast inserts and deletions. */
struct dir_rec **pending_files;
struct file_info **pending_files;
int pending_files_allocated;
int pending_files_used;
@ -166,14 +170,18 @@ static int archive_read_format_iso9660_read_data(struct archive *,
const void **, size_t *, off_t *);
static int archive_read_format_iso9660_read_header(struct archive *,
struct archive_entry *);
static const char *build_pathname(struct archive_string *, struct dir_rec *);
static void dump_isodirent(FILE *, const struct directory_record *);
static time_t isodate(const void *);
static const char *build_pathname(struct archive_string *, struct file_info *);
static void dump_isodirrec(FILE *, const struct iso9660_directory_record *);
static time_t isodate17(const void *);
static time_t isodate7(const void *);
static int isPVD(struct iso9660 *, const char *);
static struct dir_rec *next_entry(struct iso9660 *);
static void release_dirrec(struct iso9660 *, struct dir_rec *);
static int store_pending(struct iso9660 *, struct dir_rec *parent,
const struct directory_record *);
static struct file_info *next_entry(struct iso9660 *);
static void parse_rockridge(struct iso9660 *iso9660,
const struct iso9660_directory_record *isodirrec,
struct file_info *file);
static void release_file(struct iso9660 *, struct file_info *);
static int store_pending(struct iso9660 *, struct file_info *parent,
const struct iso9660_directory_record *);
static int toi(const void *p, int n);
int
@ -246,7 +254,7 @@ archive_read_format_iso9660_bid(struct archive *a)
static int
isPVD(struct iso9660 *iso9660, const char *h)
{
const struct primary_volume_descriptor *voldesc;
const struct iso9660_primary_volume_descriptor *voldesc;
if (h[0] != 1)
return (0);
@ -254,12 +262,12 @@ isPVD(struct iso9660 *iso9660, const char *h)
return (0);
voldesc = (const struct primary_volume_descriptor *)h;
voldesc = (const struct iso9660_primary_volume_descriptor *)h;
iso9660->logical_block_size = toi(&voldesc->logical_block_size, 2);
/* Store the root directory in the pending list. */
store_pending(iso9660, NULL,
(struct directory_record *)&voldesc->root_directory_record);
(struct iso9660_directory_record *)&voldesc->root_directory_record);
return (48);
}
@ -269,63 +277,68 @@ archive_read_format_iso9660_read_header(struct archive *a,
{
struct stat st;
struct iso9660 *iso9660;
struct dir_rec *dirrec;
struct file_info *file;
ssize_t bytes_read;
const void *buff;
iso9660 = *(a->pformat_data);
/* Get the next entry that appears after the current offset. */
dirrec = next_entry(iso9660);
if (dirrec == NULL)
file = next_entry(iso9660);
if (file == NULL)
return (ARCHIVE_EOF);
iso9660->entry_bytes_remaining = dirrec->size;
iso9660->entry_bytes_remaining = file->size;
iso9660->entry_sparse_offset = 0; /* Offset for sparse-file-aware clients. */
/* Set up the entry structure with information about this entry. */
memset(&st, 0, sizeof(st));
st.st_mode = dirrec->mode;
st.st_mtime = dirrec->mtime;
st.st_mode = file->mode;
st.st_uid = file->uid;
st.st_gid = file->gid;
st.st_nlink = file->nlinks;
st.st_mtime = file->mtime;
st.st_ctime = file->ctime;
st.st_atime = file->atime;
st.st_size = iso9660->entry_bytes_remaining;
archive_entry_copy_stat(entry, &st);
archive_string_empty(&iso9660->pathname);
archive_entry_set_pathname(entry,
build_pathname(&iso9660->pathname, dirrec));
build_pathname(&iso9660->pathname, file));
/* If this entry points to the same data as the previous
* entry, convert this into a hardlink to that entry.
* But don't bother for zero-length files. */
if (dirrec->offset == iso9660->previous_offset
&& dirrec->size == iso9660->previous_size
&& dirrec->size > 0) {
if (file->offset == iso9660->previous_offset
&& file->size == iso9660->previous_size
&& file->size > 0) {
archive_entry_set_hardlink(entry,
iso9660->previous_pathname.s);
iso9660->entry_bytes_remaining = 0;
iso9660->entry_sparse_offset = 0;
release_dirrec(iso9660, dirrec);
release_file(iso9660, file);
return (ARCHIVE_OK);
}
/* If the offset is before our current position, we can't
* seek backwards to extract it, so issue a warning. */
if (dirrec->offset < iso9660->current_position) {
if (file->offset < iso9660->current_position) {
archive_set_error(a, ARCHIVE_ERRNO_MISC,
"Ignoring out-of-order file");
iso9660->entry_bytes_remaining = 0;
iso9660->entry_sparse_offset = 0;
release_dirrec(iso9660, dirrec);
release_file(iso9660, file);
return (ARCHIVE_WARN);
}
/* Seek forward to the start of the entry. */
while (iso9660->current_position < dirrec->offset) {
ssize_t step = dirrec->offset - iso9660->current_position;
while (iso9660->current_position < file->offset) {
ssize_t step = file->offset - iso9660->current_position;
if (step > iso9660->logical_block_size)
step = iso9660->logical_block_size;
bytes_read = (a->compression_read_ahead)(a, &buff, step);
if (bytes_read <= 0) {
release_dirrec(iso9660, dirrec);
release_file(iso9660, file);
return (ARCHIVE_FATAL);
}
if (bytes_read > step)
@ -334,8 +347,8 @@ archive_read_format_iso9660_read_header(struct archive *a,
(a->compression_read_consume)(a, bytes_read);
}
iso9660->previous_size = dirrec->size;
iso9660->previous_offset = dirrec->offset;
iso9660->previous_size = file->size;
iso9660->previous_offset = file->offset;
archive_strcpy(&iso9660->previous_pathname, iso9660->pathname.s);
/* If this is a directory, read in all of the entries right now. */
@ -350,7 +363,7 @@ archive_read_format_iso9660_read_header(struct archive *a,
if (bytes_read < step) {
archive_set_error(a, ARCHIVE_ERRNO_MISC,
"Failed to read full block when scanning ISO9660 directory list");
release_dirrec(iso9660, dirrec);
release_file(iso9660, file);
return (ARCHIVE_FATAL);
}
if (bytes_read > step)
@ -361,8 +374,8 @@ archive_read_format_iso9660_read_header(struct archive *a,
for (p = block;
*p != 0 && p < (const unsigned char *)block + bytes_read;
p += *p) {
const struct directory_record *dr
= (const struct directory_record *)p;
const struct iso9660_directory_record *dr
= (const struct iso9660_directory_record *)p;
/* Skip '.' entry. */
if (dr->name_len[0] == 1
&& dr->name[0] == '\0')
@ -371,12 +384,12 @@ archive_read_format_iso9660_read_header(struct archive *a,
if (dr->name_len[0] == 1
&& dr->name[0] == '\001')
continue;
store_pending(iso9660, dirrec, dr);
store_pending(iso9660, file, dr);
}
}
}
release_dirrec(iso9660, dirrec);
release_file(iso9660, file);
return (ARCHIVE_OK);
}
@ -413,21 +426,33 @@ static int
archive_read_format_iso9660_cleanup(struct archive *a)
{
struct iso9660 *iso9660;
struct file_info *file;
iso9660 = *(a->pformat_data);
while ((file = next_entry(iso9660)) != NULL)
release_file(iso9660, file);
archive_string_free(&iso9660->pathname);
archive_string_free(&iso9660->previous_pathname);
free(iso9660);
*(a->pformat_data) = NULL;
return (ARCHIVE_OK);
}
/*
* This routine parses a single ISO directory record, makes sense
* of any extensions, and stores the result in memory.
*/
static int
store_pending(struct iso9660 *iso9660, struct dir_rec *parent,
const struct directory_record *isodirent)
store_pending(struct iso9660 *iso9660, struct file_info *parent,
const struct iso9660_directory_record *isodirrec)
{
struct dir_rec *new_dirent;
struct file_info *file;
/* TODO: Sanity check that name_len doesn't exceed length, etc. */
/* Expand our pending files list as necessary. */
if (iso9660->pending_files_used >= iso9660->pending_files_allocated) {
struct dir_rec **new_pending_files;
struct file_info **new_pending_files;
int new_size = iso9660->pending_files_allocated * 2;
if (new_size < 1024)
@ -441,47 +466,53 @@ store_pending(struct iso9660 *iso9660, struct dir_rec *parent,
iso9660->pending_files_allocated = new_size;
}
new_dirent = malloc(sizeof(*new_dirent) + isodirent->name_len[0] + 1);
new_dirent->parent = parent;
/* Create a new file entry and copy data from the ISO dir record. */
file = malloc(sizeof(*file));
file->parent = parent;
if (parent != NULL)
parent->refcount++;
new_dirent->refcount = 0;
new_dirent->flags = isodirent->flags[0];
new_dirent->offset = toi(isodirent->extent, 4)
file->refcount = 0;
file->offset = toi(isodirrec->extent, 4)
* iso9660->logical_block_size;
new_dirent->size = toi(isodirent->size, 4);
new_dirent->mtime = isodate(isodirent->date);
memcpy(new_dirent->name, isodirent->name, isodirent->name_len[0]);
new_dirent->name[(int)isodirent->name_len[0]] = '\0';
if (isodirent->flags[0] & 0x02)
new_dirent->mode = S_IFDIR | 0700;
file->size = toi(isodirrec->size, 4);
file->mtime = isodate7(isodirrec->date);
file->ctime = file->atime = file->mtime;
file->name = malloc(isodirrec->name_len[0] + 1);
file->nlinks = 0;
file->uid = 0;
file->gid = 0;
memcpy(file->name, isodirrec->name, isodirrec->name_len[0]);
file->name[(int)isodirrec->name_len[0]] = '\0';
if (isodirrec->flags[0] & 0x02)
file->mode = S_IFDIR | 0700;
else
new_dirent->mode = S_IFREG | 0400;
file->mode = S_IFREG | 0400;
iso9660->pending_files[iso9660->pending_files_used++] = new_dirent;
/* Rockridge extensions overwrite information from above. */
parse_rockridge(iso9660, isodirrec, file);
iso9660->pending_files[iso9660->pending_files_used++] = file;
/* DEBUGGING: Warn about attributes I don't yet fully support. */
if ((isodirent->flags[0] & ~0x02) != 0) {
if ((isodirrec->flags[0] & ~0x02) != 0) {
fprintf(stderr, "\n ** Unrecognized flag: ");
dump_isodirent(stderr, isodirent);
dump_isodirrec(stderr, isodirrec);
fprintf(stderr, "\n");
} else if (toi(isodirent->volume_sequence_number, 2) != 1) {
} else if (toi(isodirrec->volume_sequence_number, 2) != 1) {
fprintf(stderr, "\n ** Unrecognized sequence number: ");
dump_isodirent(stderr, isodirent);
dump_isodirrec(stderr, isodirrec);
fprintf(stderr, "\n");
} else if (isodirent->file_unit_size[0] != 0) {
} else if (isodirrec->file_unit_size[0] != 0) {
fprintf(stderr, "\n ** Unexpected file unit size: ");
dump_isodirent(stderr, isodirent);
dump_isodirrec(stderr, isodirrec);
fprintf(stderr, "\n");
} else if (isodirent->interleave[0] != 0) {
} else if (isodirrec->interleave[0] != 0) {
fprintf(stderr, "\n ** Unexpected interleave: ");
dump_isodirent(stderr, isodirent);
dump_isodirrec(stderr, isodirrec);
fprintf(stderr, "\n");
} else if (isodirent->ext_attr_length[0] != 0) {
} else if (isodirrec->ext_attr_length[0] != 0) {
fprintf(stderr, "\n ** Unexpected extended attribute length: ");
dump_isodirent(stderr, isodirent);
dump_isodirrec(stderr, isodirrec);
fprintf(stderr, "\n");
}
@ -489,28 +520,162 @@ store_pending(struct iso9660 *iso9660, struct dir_rec *parent,
}
static void
release_dirrec(struct iso9660 *iso9660, struct dir_rec *dr)
parse_rockridge(struct iso9660 *iso9660,
const struct iso9660_directory_record *isodirrec, struct file_info *file)
{
struct dir_rec *parent;
const unsigned char *p, *end;
if (dr->refcount == 0) {
dr->flags = 0xff;
parent = dr->parent;
free(dr);
(void)iso9660; /* UNUSED */
end = (const unsigned char *)isodirrec + isodirrec->length[0];
p = isodirrec->name + isodirrec->name_len[0];
if ((isodirrec->name_len[0] & 1) == 0)
p++;
while (p + 4 < end /* Enough space for another entry. */
&& p[0] >= 'A' && p[0] <= 'Z' /* Sanity-check 1st char of name. */
&& p[1] >= 'A' && p[1] <= 'Z' /* Sanity-check 2nd char of name. */
&& p + p[2] <= end) { /* Sanity-check length. */
const unsigned char *data = p + 4;
int data_length = p[2] - 4;
int version = p[3]; /* Currently unused. */
switch(p[0]) {
case 'N':
if (p[1] == 'M' && version == 1) { /* NM */
/*
* NM extension comprises:
* one byte flag
* rest is long name
*/
/* TODO: Obey flags. */
char *old_name = file->name;
file->name = malloc(data_length);
if (file->name != NULL) {
free(old_name);
memcpy(file->name, data + 1, data_length - 1);
file->name[data_length - 1] = '\0';
} else {
file->name = old_name;
}
}
break;
case 'P':
if (p[1] == 'X' && version == 1) { /* PX */
/*
* PX extension comprises:
* 8 bytes for mode,
* 8 bytes for nlinks,
* 8 bytes for uid,
* 8 bytes for gid.
*/
if (data_length == 32) {
file->mode = toi(data, 4);
file->nlinks = toi(data + 8, 4);
file->uid = toi(data + 16, 4);
file->gid = toi(data + 24, 4);
}
}
break;
case 'R':
if (p[1] == 'R' && version == 1) { /* RR */
/*
* RR extension comprises:
*
*/
/* TODO: Handle RR extension. */
}
break;
case 'T':
if (p[1] == 'F' && version == 1) { /* TF */
char flag = data[0];
/*
* TF extension comprises:
* one byte flag
* create time (optional)
* modify time (optional)
* access time (optional)
* attribute time (optional)
* Time format and presence of fields
* is controlled by flag bits.
*/
data++;
if (flag & 0x80) {
/* Use 17-byte time format. */
if (flag & 1) /* Create time. */
data += 17;
if (flag & 2) { /* Modify time. */
file->mtime = isodate17(data);
data += 17;
}
if (flag & 4) { /* Access time. */
file->atime = isodate17(data);
data += 17;
}
if (flag & 8) { /* Attribute time. */
file->ctime = isodate17(data);
data += 17;
}
} else {
/* Use 7-byte time format. */
if (flag & 1) /* Create time. */
data += 7;
if (flag & 2) { /* Modify time. */
file->mtime = isodate7(data);
data += 7;
}
if (flag & 4) { /* Access time. */
file->atime = isodate7(data);
data += 7;
}
if (flag & 8) { /* Attribute time. */
file->ctime = isodate7(data);
data += 7;
}
}
}
break;
default:
{
const unsigned char *t;
fprintf(stderr, "\nUnsupported RRIP extension for %s\n", file->name);
fprintf(stderr, " %c%c(%d):", p[0], p[1], data_length);
for (t = data; t < data + data_length && t < data + 16; t++)
fprintf(stderr, " %02x", *t);
fprintf(stderr, "\n");
}
}
p += p[2];
}
}
static void
release_file(struct iso9660 *iso9660, struct file_info *file)
{
struct file_info *parent;
if (file->refcount == 0) {
parent = file->parent;
if (file->name)
free(file->name);
free(file);
if (parent != NULL) {
parent->refcount--;
release_dirrec(iso9660, parent);
release_file(iso9660, parent);
}
}
}
static struct dir_rec *
static struct file_info *
next_entry(struct iso9660 *iso9660)
{
int least_index;
uint64_t least_end_offset;
int i;
struct dir_rec *r;
struct file_info *r;
if (iso9660->pending_files_used < 1)
return (NULL);
@ -548,7 +713,7 @@ toi(const void *p, int n)
}
static time_t
isodate(const void *p)
isodate7(const void *p)
{
struct tm tm;
const unsigned char *v = (const unsigned char *)p;
@ -561,33 +726,61 @@ isodate(const void *p)
tm.tm_sec = v[5];
/* v[6] is the timezone offset, in 1/4-hour increments. */
offset = ((const signed char *)p)[6];
tm.tm_hour -= offset / 4;
tm.tm_min -= (offset % 4) * 15;
if (offset > -48 && offset < 52) {
tm.tm_hour -= offset / 4;
tm.tm_min -= (offset % 4) * 15;
}
return (timegm(&tm));
}
static time_t
isodate17(const void *p)
{
struct tm tm;
const unsigned char *v = (const unsigned char *)p;
int offset;
tm.tm_year = (v[0] - '0') * 1000 + (v[1] - '0') * 100
+ (v[2] - '0') * 10 + (v[3] - '0')
- 1900;
tm.tm_mon = (v[4] - '0') * 10 + (v[5] - '0');
tm.tm_mday = (v[6] - '0') * 10 + (v[7] - '0');
tm.tm_hour = (v[8] - '0') * 10 + (v[9] - '0');
tm.tm_min = (v[10] - '0') * 10 + (v[11] - '0');
tm.tm_sec = (v[12] - '0') * 10 + (v[13] - '0');
/* v[16] is the timezone offset, in 1/4-hour increments. */
offset = ((const signed char *)p)[16];
if (offset > -48 && offset < 52) {
tm.tm_hour -= offset / 4;
tm.tm_min -= (offset % 4) * 15;
}
return (timegm(&tm));
}
static const char *
build_pathname(struct archive_string *as, struct dir_rec *dr)
build_pathname(struct archive_string *as, struct file_info *file)
{
if (dr->parent != NULL && dr->parent->name[0] != '\0') {
build_pathname(as, dr->parent);
if (file->parent != NULL && file->parent->name[0] != '\0') {
build_pathname(as, file->parent);
archive_strcat(as, "/");
}
archive_strcat(as, dr->name);
if (file->name[0] == '\0')
archive_strcat(as, ".");
else
archive_strcat(as, file->name);
return (as->s);
}
static void
dump_isodirent(FILE *out, const struct directory_record *isodirent)
dump_isodirrec(FILE *out, const struct iso9660_directory_record *isodirrec)
{
fprintf(out, " l %d,", isodirent->length[0]);
fprintf(out, " a %d,", isodirent->ext_attr_length[0]);
fprintf(out, " ext 0x%x,", toi(isodirent->extent, 4));
fprintf(out, " s %d,", toi(isodirent->size, 4));
fprintf(out, " f 0x%02x,", isodirent->flags[0]);
fprintf(out, " u %d,", isodirent->file_unit_size[0]);
fprintf(out, " ilv %d,", isodirent->interleave[0]);
fprintf(out, " seq %d,", toi(isodirent->volume_sequence_number,2));
fprintf(out, " nl %d:", isodirent->name_len[0]);
fprintf(out, " `%.*s'", isodirent->name_len[0], isodirent->name);
fprintf(out, " l %d,", isodirrec->length[0]);
fprintf(out, " a %d,", isodirrec->ext_attr_length[0]);
fprintf(out, " ext 0x%x,", toi(isodirrec->extent, 4));
fprintf(out, " s %d,", toi(isodirrec->size, 4));
fprintf(out, " f 0x%02x,", isodirrec->flags[0]);
fprintf(out, " u %d,", isodirrec->file_unit_size[0]);
fprintf(out, " ilv %d,", isodirrec->interleave[0]);
fprintf(out, " seq %d,", toi(isodirrec->volume_sequence_number,2));
fprintf(out, " nl %d:", isodirrec->name_len[0]);
fprintf(out, " `%.*s'", isodirrec->name_len[0], isodirrec->name);
}