From ad6ea1be0faa49454b11bd3900371ef584af179e Mon Sep 17 00:00:00 2001 From: Tyler Dauwalder Date: Wed, 5 Mar 2003 07:44:17 +0000 Subject: [PATCH] A mi me gusta C++; renamed from iso9660.c plus: + Now supports Joliet volume names + Root directory record information is dumped out along with each primary and supplementary volume descriptor when TRACE is turned on. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@2868 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../kernel/disk_scanner/fs/iso9660.cpp | 635 ++++++++++++++++++ 1 file changed, 635 insertions(+) create mode 100644 src/add-ons/kernel/disk_scanner/fs/iso9660.cpp diff --git a/src/add-ons/kernel/disk_scanner/fs/iso9660.cpp b/src/add-ons/kernel/disk_scanner/fs/iso9660.cpp new file mode 100644 index 0000000000..64d938a8db --- /dev/null +++ b/src/add-ons/kernel/disk_scanner/fs/iso9660.cpp @@ -0,0 +1,635 @@ +//---------------------------------------------------------------------- +// This software is part of the OpenBeOS distribution and is covered +// by the OpenBeOS license. +//--------------------------------------------------------------------- +/*! + \file iso9660.cpp + \brief disk_scanner filesystem module for iso9660 CD-ROM filesystems + +
iso9660
+ The standard to which this module is written is ECMA-119 second + edition, a freely available iso9660 equivalent. + +
Joliet
+ Joliet support comes courtesy of the following document: + + http://www-plateau.cs.berkeley.edu/people/chaffee/jolspec.htm + + As specified there, the existence of any of the following escape + sequences in a supplementary volume descriptor's "escape sequences" + field denotes a Joliet volume descriptor using unicode ucs-2 + character encoding (2-byte characters, big-endian): + + - UCS-2 Level 1: 0x252F40 == "%/@" + - UCS-2 Level 2: 0x252F43 == "%/C" + - UCS-2 Level 3: 0x252F45 == "%/E" + + The following UCS-2 characters are considered illegal (we allow them, + printing out a warning if encountered): + + - All values between 0x0000 and 0x001f inclusive == control chars + - 0x002A == '*' + - 0x002F == '/' + - 0x003A == ':' + - 0x003B == ';' + - 0x003F == '?' + - 0x005C == '\' +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TRACE(x) ; +//#define TRACE(x) dprintf x + +// misc constants +#define ISO9660_FS_MODULE_NAME "disk_scanner/fs/iso9660/v1" +static const char *kModuleDebugName = "fs/iso9660"; +static const char *kISO9660Signature = "CD001"; +static const uint32 kVolumeDescriptorLength = 2048; +#define ISO9660_VOLUME_IDENTIFIER_LENGTH 32 +#define ISO9660_ESCAPE_SEQUENCE_LENGTH 32 + +//! Volume descriptor types +typedef enum { + ISO9660VD_BOOT, + ISO9660VD_PRIMARY, + ISO9660VD_SUPPLEMENTARY, + ISO9660VD_PARTITION, + ISO9660VD_TERMINATOR = 255 +} iso9660_volume_descriptor_type; + +/*! \brief The portion of the volume descriptor common to all + descriptor types. +*/ +typedef struct iso9660_common_volume_descriptor { + uchar volume_descriptor_type; + char standard_identifier[5]; // should be 'CD001' + uchar volume_descriptor_version; + // Remaining bytes are unused +} __attribute__((packed)) iso9660_common_volume_descriptor; + +/*! \brief Primary volume descriptor +*/ +typedef struct iso9660_primary_volume_descriptor { + iso9660_common_volume_descriptor info; + uchar volume_flags; + char system_identifier[32]; + char volume_identifier[ISO9660_VOLUME_IDENTIFIER_LENGTH]; + uchar unused01[8]; + uint32 volume_space_size_little_endian; + uint32 volume_space_size_big_endian; + uchar unused02[ISO9660_ESCAPE_SEQUENCE_LENGTH]; + uint16 volume_set_size_little_endian; + uint16 volume_set_size_big_endian; + uint16 volume_sequence_number_little_endian; + uint16 volume_sequence_number_big_endian; + uint16 logical_block_size_little_endian; + uint16 logical_block_size_big_endian; + uint32 path_table_size_little_endian; + uint32 path_table_size_big_endian; + uint32 ignored02[4]; + uchar root_directory_record[34]; + char volume_set_identifier[28]; + // Remaining bytes are disinteresting to us +} __attribute__((packed)) iso9660_primary_volume_descriptor; + +typedef struct iso9660_supplementary_volume_descriptor { + iso9660_common_volume_descriptor info; + uchar volume_flags; + char system_identifier[32]; + char volume_identifier[ISO9660_VOLUME_IDENTIFIER_LENGTH]; + uchar unused01[8]; + uint32 volume_space_size_little_endian; + uint32 volume_space_size_big_endian; + char escape_sequences[ISO9660_ESCAPE_SEQUENCE_LENGTH]; + uint16 volume_set_size_little_endian; + uint16 volume_set_size_big_endian; + uint16 volume_sequence_number_little_endian; + uint16 volume_sequence_number_big_endian; + uint16 logical_block_size_little_endian; + uint16 logical_block_size_big_endian; + uint32 path_table_size_little_endian; + uint32 path_table_size_big_endian; + uint32 ignored02[4]; + uchar root_directory_record[34]; + char volume_set_identifier[28]; + // Remaining bytes are disinteresting to us +} __attribute__((packed)) iso9660_supplementary_volume_descriptor; + +typedef struct iso9660_directory_record { + uint8 length; + uint8 extended_attribute_record_length; + uint32 location_le; + uint32 location_be; + uint32 data_length; + uchar ignored[14]; + uint16 volume_space_le; +} __attribute__((packed)) iso9660_directory_record; + +/*! \brief Contains all the info of interest pertaining to an + iso9660 volume. + + Currently supported character set encoding styles (in decreasing + order of precedence): + - Joliet (UCS-12 (16-bit unicode), which is converted to UTF-8) + - iso9660 (some absurdly tiny character set, but we actually allow UTF-8) +*/ +struct iso9660_info { + iso9660_info(); + ~iso9660_info(); + + bool is_valid(); + + void set_iso9660_volume_name(const char *name, uint32 length); + void set_joliet_volume_name(const char *name, uint32 length); + + const char* get_preferred_volume_name(); + + char *iso9660_volume_name; + char *joliet_volume_name; + + void set_string(char **string, const char *new_string, uint32 new_length); +}; + +//---------------------------------------------------------------------------- +// iso9660_info +//---------------------------------------------------------------------------- + +/*! \brief Creates a new iso9660_info struct with empty volume names. + + \note Use the applicable set_XYZ_volume_name() functions rather than + messing with the volume name data members directly. +*/ +iso9660_info::iso9660_info() + : iso9660_volume_name(NULL) + , joliet_volume_name(NULL) +{ +} + +/*! \brief Destroys the struct, freeing the volume name strings. +*/ +iso9660_info::~iso9660_info() +{ + if (iso9660_volume_name) { + free(iso9660_volume_name); + iso9660_volume_name = NULL; + } + if (joliet_volume_name) { + free(joliet_volume_name); + joliet_volume_name = NULL; + } +} + +/*! \brief Returns true if a valid volume name exists. +*/ +bool +iso9660_info::is_valid() +{ + return iso9660_volume_name || joliet_volume_name; +} + +/*! \brief Sets the iso9660 volume name. + + \param name UTF-8 string containing the name. + \param length The length (in bytes) of the string. +*/ +void +iso9660_info::set_iso9660_volume_name(const char *name, uint32 length) +{ + set_string(&iso9660_volume_name, name, length); +} + +/*! \brief Sets the Joliet volume name. + + \param name UTF-8 string containing the name. + \param length The length (in bytes) of the string. +*/ +void +iso9660_info::set_joliet_volume_name(const char *name, uint32 length) +{ + set_string(&joliet_volume_name, name, length); +} + +/*! \brief Returns the volume name of highest precedence. + + Currently, the ordering is (decreasingly): + - Joliet + - iso9660 +*/ +const char* +iso9660_info::get_preferred_volume_name() +{ + if (joliet_volume_name) + return joliet_volume_name; + else + return iso9660_volume_name; +} + +/*! \brief Copies the given string into the old string, managing memory + deallocation and allocation as necessary. +*/ +void +iso9660_info::set_string(char **string, const char *new_string, uint32 new_length) +{ + TRACE(("%s: iso9660_info::set_string(%p (`%s'), `%s', %ld)\n", kModuleDebugName, + string, *string, new_string, new_length)); + if (string) { + char *&old_string = *string; + if (old_string) + free(old_string); + if (new_string) { + old_string = (char*)malloc(new_length+1); + if (old_string) { + strncpy(old_string, new_string, new_length); + old_string[new_length] = 0; + } + } else { + old_string = NULL; + } + } +} + +//---------------------------------------------------------------------------- +// C functions +//---------------------------------------------------------------------------- + +/*! \brief Converts the given unicode character to utf8. + + Courtesy Mr. Axel Dörfler. + + \todo Once OpenTracker's locale kit is done, perhaps that functionality + should be used rather than outright stealing the code. +*/ +static +void +unicode_to_utf8(uint32 c, char **out) +{ + char *s = *out; + + if (c < 0x80) + *(s++) = c; + else if (c < 0x800) { + *(s++) = 0xc0 | (c>>6); + *(s++) = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + *(s++) = 0xe0 | (c>>12); + *(s++) = 0x80 | ((c>>6) & 0x3f); + *(s++) = 0x80 | (c & 0x3f); + } else if (c <= 0x10ffff) { + *(s++) = 0xf0 | (c>>18); + *(s++) = 0x80 | ((c>>12) & 0x3f); + *(s++) = 0x80 | ((c>>6) & 0x3f); + *(s++) = 0x80 | (c & 0x3f); + } + *out = s; +} + +static +const char* +volume_descriptor_type_to_string(iso9660_volume_descriptor_type type) +{ + switch (type) { + case ISO9660VD_BOOT: return "boot"; + case ISO9660VD_PRIMARY: return "primary"; + case ISO9660VD_SUPPLEMENTARY: return "supplementary"; + case ISO9660VD_PARTITION: return "partiton"; + case ISO9660VD_TERMINATOR: return "terminator"; + default: return "invalid"; + } +} + +static +void +dump_common_volume_descriptor(iso9660_common_volume_descriptor *common, const char *indent, + bool print_header) +{ + if (print_header) + TRACE(("%siso9660_common_volume_descriptor:\n", indent)); + + TRACE(("%s volume descriptor type == %d (%s)\n", indent, + common->volume_descriptor_type, + volume_descriptor_type_to_string((iso9660_volume_descriptor_type)common->volume_descriptor_type))); + TRACE(("%s standard identifier == %.5s (%s)\n", indent, common->standard_identifier, + strncmp(common->standard_identifier, kISO9660Signature, 5) == 0 ? "valid" : "INVALID")); + TRACE(("%s volume descriptor version == %d\n", indent, common->volume_descriptor_version)); +} + +static +void +dump_directory_record(iso9660_directory_record *record, const char *indent); + +static +void +dump_primary_volume_descriptor(iso9660_primary_volume_descriptor *primary, const char *indent, + bool print_header) +{ + if (print_header) + TRACE(("%siso9660_primary_volume_descriptor:\n", indent)); + + dump_common_volume_descriptor(&(primary->info), indent, false); + TRACE(("%s volume identifier == `%.32s'\n", indent, + primary->volume_identifier)); + TRACE(("%s volume space size == %ld\n", indent, + primary->volume_space_size_little_endian)); + TRACE(("%s volume set size == %d\n", indent, + primary->volume_set_size_little_endian)); + TRACE(("%s volume sequence number == %d\n", indent, + primary->volume_sequence_number_little_endian)); + TRACE(("%s logical block size == %d\n", indent, + primary->logical_block_size_little_endian)); + TRACE(("%s path table size == %ld\n", indent, + primary->path_table_size_little_endian)); + TRACE(("%s volume set identifier == %.28s\n", indent, + primary->volume_set_identifier)); + dump_directory_record((iso9660_directory_record*)primary->root_directory_record, indent); +} + +static +void +dump_supplementary_volume_descriptor(iso9660_supplementary_volume_descriptor *supplementary, const char *indent, + bool print_header) +{ + if (print_header) + TRACE(("%siso9660_supplementary_volume_descriptor:\n", indent)); + + dump_primary_volume_descriptor((iso9660_primary_volume_descriptor*)supplementary, indent, false); + TRACE(("%s escape sequences ==", indent)); + for (int i = 0; i < ISO9660_ESCAPE_SEQUENCE_LENGTH; i++) { + TRACE((" %2x", supplementary->escape_sequences[i])); + if (i == ISO9660_ESCAPE_SEQUENCE_LENGTH/2-1) + TRACE(("\n ")); + } + TRACE(("\n")); +} + +static +void +dump_directory_record(iso9660_directory_record *record, const char *indent) +{ + TRACE(("%s root directory record:\n", indent)); + TRACE(("%s length == %d\n", indent, record->length)); + TRACE(("%s location == %ld\n", indent, record->location_le)); + TRACE(("%s data length == %ld\n", indent, record->data_length)); + TRACE(("%s volume sequence number == %d\n", indent, record->volume_space_le)); +} + +static +status_t +check_common_volume_descriptor(iso9660_common_volume_descriptor *common) +{ + status_t error = common ? B_OK : B_BAD_VALUE; + if (!error) { + error = strncmp(common->standard_identifier, kISO9660Signature, + 5) == 0 ? B_OK : B_BAD_DATA; + } + return error; +} + +// std_ops +static +status_t +std_ops(int32 op, ...) +{ + TRACE(("%s: std_ops(0x%lx)\n", kModuleDebugName, op)); + switch(op) { + case B_MODULE_INIT: + case B_MODULE_UNINIT: + return B_OK; + } + return B_ERROR; +} + +// read_block +static +status_t +read_block(fs_get_buffer get_buffer, struct fs_buffer_cache *cache, + off_t offset, size_t size, uchar **block) +{ + size_t actualSize = 0; + status_t error = get_buffer(cache, offset, size, (void**)block, + &actualSize); + if (error == B_OK && actualSize != size) { + error = B_ERROR; + free(*block); + } + return error; +} + +// iso9660_fs_identify +/*! \brief Returns true if the given partition is a valid iso9660 partition. + + See fs_identify_hook() for more information. + + \todo Fill in partitionInfo->mounted_at with something useful. +*/ +static +bool +iso9660_fs_identify(int deviceFD, struct extended_partition_info *partitionInfo, + float *priority, fs_get_buffer get_buffer, + struct fs_buffer_cache *cache) +{ + bool result = false; + uchar *buffer = NULL; + uint32 blockSize = partitionInfo->info.logical_block_size; + bool exit = false; + // The first 16 blocks are for "system use" only, and thus are + // irrelevant to us and generally just zeros + off_t offset = 16 * blockSize; + status_t error = B_OK; + + TRACE(("%s: identify(%d, %p)\n", kModuleDebugName, deviceFD, + partitionInfo)); + + iso9660_info info; + + // Read through the volume descriptors looking for a primary descriptor. + // If for some reason there are more than one primary descriptor, the + // volume name from the last encountered descriptor will be used. + while (!error && !exit) {// && count++ < 10) { + iso9660_common_volume_descriptor *common = NULL; + + // Read the block containing the current descriptor + error = read_block(get_buffer, cache, offset, blockSize, &buffer); + offset += blockSize; + if (!error) { + common = (iso9660_common_volume_descriptor*)buffer; + error = check_common_volume_descriptor(common); +// dump_common_volume_descriptor(common, "", true); + } + + // Handle each type of descriptor appropriately + if (!error) { + TRACE(("%s: found %s descriptor\n", kModuleDebugName, + volume_descriptor_type_to_string((iso9660_volume_descriptor_type)common->volume_descriptor_type))); + + switch (common->volume_descriptor_type) { + case ISO9660VD_BOOT: + break; + + case ISO9660VD_PRIMARY: + { + int i; + iso9660_primary_volume_descriptor *primary = (iso9660_primary_volume_descriptor*)buffer; + + dump_primary_volume_descriptor(primary, " ", true); + + // Cut off any trailing spaces from the volume id. Note + // that this allows for spaces INSIDE the volume id, even + // though that's not technically allowed by the standard; + // this was necessary to support certain RedHat 6.2 CD-ROMs + // from a certain Linux company who shall remain unnamed. ;-) + for (i = ISO9660_VOLUME_IDENTIFIER_LENGTH-1; i >= 0; i--) { + if (primary->volume_identifier[i] != 0x20) + break; + } + + // Give a holler if the iso9660 name is already set + if (info.iso9660_volume_name) { + char str[ISO9660_VOLUME_IDENTIFIER_LENGTH+1]; + strncpy(str, primary->volume_identifier, i+1); + str[i+1] = 0; + TRACE(("%s: duplicate iso9660 volume name found, using latter (`%s') " + "instead of former (`%s')\n", kModuleDebugName, str, + info.iso9660_volume_name)); + } + + info.set_iso9660_volume_name(primary->volume_identifier, i+1); + break; + } + + case ISO9660VD_SUPPLEMENTARY: + { + iso9660_supplementary_volume_descriptor *supplementary = (iso9660_supplementary_volume_descriptor*)buffer; + dump_supplementary_volume_descriptor((iso9660_supplementary_volume_descriptor*)supplementary, " ", true); + + // Copy and null terminate the escape sequences + char escapes[ISO9660_ESCAPE_SEQUENCE_LENGTH+1]; + strncpy(escapes, supplementary->escape_sequences, ISO9660_ESCAPE_SEQUENCE_LENGTH); + escapes[ISO9660_ESCAPE_SEQUENCE_LENGTH] = 0; + + // Check for a Joliet VD + if (strstr(escapes, "%/@") || strstr(escapes, "%/C") || strstr(escapes, "%/E")) { + char str[(ISO9660_VOLUME_IDENTIFIER_LENGTH*3/2)+1]; + // Since we're dealing with 16-bit Unicode, each UTF-8 sequence + // will be at most 3 bytes long. So we need 3/2 as many chars as + // we start out with. + char *str_iterator = str; + uint16 ch; + + // Walk thru the unicode volume name, converting to utf8 as we go. + for (int i = 0; + (ch = B_BENDIAN_TO_HOST_INT16(((uint16*)supplementary->volume_identifier)[i])) + && i < ISO9660_VOLUME_IDENTIFIER_LENGTH; + i++) { + // Give a warning if the character is technically illegal + if ( ch <= 0x001F + || ch == 0x002A + || ch == 0x002F + || ch == 0x003A + || ch == 0x003B + || ch == 0x003F + || ch == 0x005C) + { + TRACE(("%s: warning: illegal Joliet character found: 0%4x\n", + kModuleDebugName, ch)); + } + + // Convert to utf-8 + unicode_to_utf8(ch, &str_iterator); + } + *str_iterator = 0; + + // Give a holler if the joliet name is already set + if (info.joliet_volume_name) { + TRACE(("%s: duplicate joliet volume name found, using latter (`%s') " + "instead of former (`%s')\n", kModuleDebugName, str, + info.joliet_volume_name)); + } + + info.set_joliet_volume_name(str, strlen(str)); + } // end "if Joliet VD" + break; + } + + case ISO9660VD_PARTITION: + break; + + case ISO9660VD_TERMINATOR: + exit = true; + break; + + default: + break; + } + } + if (buffer) { + free(buffer); + buffer = NULL; + } + } + + switch (error) { + case B_OK: + if (info.is_valid()) { + result = true; + if (partitionInfo->file_system_short_name) + strcpy(partitionInfo->file_system_short_name, "iso9660"); + if (partitionInfo->file_system_long_name) + strcpy(partitionInfo->file_system_long_name, "iso9660 CD-ROM File System"); + partitionInfo->file_system_flags = B_FS_IS_PERSISTENT; + if (priority) + *priority = 0; + // Copy the volume name of highest precedence + if (partitionInfo->volume_name) { + TRACE(("%s: iso9660 name: `%s'\n", kModuleDebugName, info.iso9660_volume_name)); + TRACE(("%s: joliet name: `%s'\n", kModuleDebugName, info.joliet_volume_name)); + const char *name = info.get_preferred_volume_name(); + int length = strlen(name); + if (length > B_FILE_NAME_LENGTH-1) + length = B_FILE_NAME_LENGTH-1; + strncpy(partitionInfo->volume_name, name, length); + partitionInfo->volume_name[length] = 0; + } + } + break; + + case B_BAD_DATA: + TRACE(("%s: identify: bad signature\n", kModuleDebugName)); + break; + + default: + TRACE(("%s: identify error: 0x%lx\n", kModuleDebugName, + error)); + break; + } + + return result; +} + +static fs_module_info iso9660_fs_module = +{ + // module_info + { + ISO9660_FS_MODULE_NAME, + 0, // better B_KEEP_LOADED? + std_ops + }, + iso9660_fs_identify, +}; + +_EXPORT fs_module_info *modules[] = +{ + &iso9660_fs_module, + NULL +}; +