CLOSE

What is a File System ❔

A file system is a method and data structure that an operating system uses to manage files on a disk or partition. It provides a way to store, organize, retrieve, and manage data. File systems control how data is stored and retrieved. Without a file system, data placed in a storage medium would be one large body of data with no way to tell where one piece of information stops and the next begins.

Key Concepts of File Systems

  1. Files and Directories: A file system organizes data into files, which are collections of data stored in a single unit, and directories (or folders), which are containers for files and other directories.
  2. Metadata: This includes information about files, such as their names, sizes, creation dates, and permissions.
  3. File Allocation: File systems use various methods to allocate space on storage media, such as contiguous, linked, and indexed allocation.
  4. File Access: Methods to read, write, and modify files. File systems provide APIs and system calls for file operations.
  5. File System Types: Examples include FAT (File Allocation Table), NTFS (New Technology File System), ext3/ext4 (third and fourth extended file systems), and others.

ISO 9660 File System

The ISO 9660 file system, commonly known as the ISO file system, is a standard file system for optical disc media, particularly CD-ROMs. It was developed by the International Organization for Standardization (ISO) and published in 1988. It defines how data is structured on the disc, allowing it to be read on different operating systems and hardware.

Key Features of ISO 9660

  1. Standardization: ISO 9660 is a widely recognized international standard (published by the International Organization for Standardization in 1988) ensuring compatibility across different platforms and devices.
  2. Volume Descriptors: These are structures containing metadata about the disc, such as volume identifier, volume size, logical block size, and the location of the root directory.
  3. File and Directory Naming: ISO 9660 has several levels:
    1. Level 1: Limits filenames to 8 characters with a 3-character extension (8.3 format), uppercase only, and directory names to a maximum of 8 levels deep.
    2. Level 2: Allows filenames up to 31 characters, still uppercase only.
    3. Level 3: Allows filenames up to 31 characters and permits multi-extent files, meaning files can be non-contiguous.
  4. Path Tables: These provide a compact way to represent the directory structure for efficient directory traversal.
  5. Extent and Logical Block Addressing (LBA): Data is organized in extents (contiguous blocks of data), and LBAs are used to reference these blocks. The standard LBA size for ISO 9660 is 2048 bytes.
  6. Root Directory: The root directory is defined in the primary volume descriptor and contains directory records for files and subdirectories.

Extensions to ISO 9660

To overcome limitations of the original standard, several extensions have been developed:

  1. Rock Ridge: Adds POSIX file system semantics, such as long filenames, deeper directory structures, symbolic links, and file permissions.
  2. Joliet: Developed by Microsoft, allows Unicode (UTF-16) filenames and longer filenames.
  3. El Torito: Defines a method for making bootable CD-ROMs by specifying boot information and an initial boot image.

File Identifier (File Name) rules in ISO 9660 Filesystem

In the ISO 9660 file system, file identifier rules are quite specific to ensure compatibility across different operating systems. Here are the key rules:

  1. Uppercase Characters: All file names must be in uppercase characters (A-Z).
  2. Length: The file name can have a maximum of 8 characters, and the file extension can have a maximum of 3 characters, following the 8.3 filename convention. This is often referred to as Level 1 of the ISO 9660 standard. Level 2 allows longer names (up to 31 characters), but they still must be uppercase.
  3. Characters Allowed: Only the characters A-Z, 0-9, and the underscore (_) are allowed. Spaces and special characters are not permitted.
  4. File Name and Extension Separation: File names and their extensions are separated by a period (.), for example, FILENAME.TXT.
  5. Directory Depth: The maximum directory depth is limited to 8 levels.
  6. Root Directory: The root directory is always at a predefined location on the disk and has specific properties.

Levels in ISO 9660 Filesystem

In the context of the ISO 9660 standard, "levels" refer to different sets of restrictions and capabilities that determine how file names and directory structures are formatted and stored on an ISO 9660-compliant filesystem. These levels are designed to ensure compatibility with various operating systems and software.

The ISO 9660 standard defines three levels of file system interchange to provide compatibility with various operating systems and applications. Here are the details of the three levels:

Level 1

  • File Name Length: File names can have a maximum of 8 characters, and extensions can have up to 3 characters (8.3 format).
  • Character Set: Only uppercase A-Z, digits 0-9, and the underscore (_) are allowed.
  • Directory Depth: Maximum of 8 directory levels.
  • File Size: Each file can be up to 4 GB in size.
  • File Identifier: Must be uppercase and follow the 8.3 naming convention.
  • Interoperability: Ensures the highest level of compatibility with older systems, such as DOS and early versions of Windows.

Level 2

  • File Name Length: File names can be up to 31 characters long.
  • Character Set: Only uppercase A-Z, digits 0-9, and the underscore (_) are allowed.
  • Directory Depth: Maximum of 8 directory levels.
  • File Size: Each file can be up to 4 GB in size.
  • File Identifier: More flexible with length but still must be uppercase.
  • Interoperability: Maintains good compatibility with most systems, but some very old systems might not support the longer file names.

Level 3

  • File Name Length: Same as Level 2, allowing up to 31 characters.
  • Character Set: Only uppercase A-Z, digits 0-9, and the underscore (_) are allowed.
  • Directory Depth: Maximum of 8 directory levels.
  • File Size: Allows files to be split into multiple extents, each up to 4 GB, which can surpass the 4 GB limit per file by using multiple extents.
  • File Identifier: More flexible with length but still must be uppercase.
  • Interoperability: Provides the least compatibility among the three levels but allows larger files through file extents.
Level 1
+-----------------------------------------+
| File Name Length: 8.3 format            |
| Character Set: A-Z, 0-9, _              |
| Directory Depth: 8 levels               |
| File Size: 4 GB                         |
| Interoperability: Highest               |
+-----------------------------------------+

Level 2
+-----------------------------------------+
| File Name Length: Up to 31 characters   |
| Character Set: A-Z, 0-9, _              |
| Directory Depth: 8 levels               |
| File Size: 4 GB                         |
| Interoperability: Good                  |
+-----------------------------------------+

Level 3
+-----------------------------------------+
| File Name Length: Up to 31 characters   |
| Character Set: A-Z, 0-9, _              |
| Directory Depth: 8 levels               |
| File Size: > 4 GB using extents         |
| Interoperability: Lowest                |
+-----------------------------------------+

Note:

We can explicitly set the level of iso image, by providing parameters in the image creation command. This is done by specifying it explicitly using the -iso-level option in your xorriso command.

For example:

  • Level 1 (basic standard):
    • xorriso -as mkisofs -R -J -iso-level 1 -b stage1.bin -no-emul-boot -boot-load-size 4 -o image.iso iso_dir
      
  • Level 2:
    • xorriso -as mkisofs -R -J -iso-level 2 -b stage1.bin -no-emul-boot -boot-load-size 4 -o image.iso iso_dir
      
  • Level 3
    • xorriso -as mkisofs -R -J -iso-level 3 -b stage1.bin -no-emul-boot -boot-load-size 4 -o image.iso iso_dir
      

ISO 9660 Filesystem Structure

+-------------------------+
|      System Area        | <- Sectors 0-15 (Reserved 
|                         |   for boot code)
|  +-------------------+  |
|  |                   |  | <- Sector 0 (contains 
|  |   Boot Record     |  |  bootloader for bootable 
|  |                   |  |  ISOs)
|  +-------------------+  |
|  | Reserved Sectors  |  | <- Sectors 1-15 (reserved 
|  |                   |  |  for additional boot code)
|  +-------------------+  |
|                         |
+-------------------------+
|  Volume Descriptors     | <- Sector 16
|                         |  (contains volume metadata)
| +---------------------+ |  Start of Volume 
| | Volume Descriptor 1 | |  Descriptors.
| |                     | |  can be of any order.
| +---------------------+ |  But mostly, Primary Volume
| | Volume Descriptor 2 | |  Descriptor is the first
| |                     | |  one. Followed by remaining
| +---------------------+ |
| | Volume Descriptor 3 | |
| |                     | |
| +---------------------+ |
| |  ...                | |
| |                     | |
| +---------------------+ |
| | Volume Descriptor   | | <- Marks the end 
| | Set Terminator      | |    of the volume
| +---------------------+ |    descriptor sequence.
|                         |
+-------------------------+
|      Path Tables        | <- Path Tables (contains
| +---------------------+ |    directory hierarchy
| | Entry 1 (File or    | |    information)
| |      Directory)     | |
| +---------------------+ |
| | Entry 2 (File or    | |
| |      Directory)     | |
| +---------------------+ |
| | Entry n (File or    | |
| |      Directory)     | |
| +---------------------+ |
| List directory struct   |
| structures for quick    |  
| traversal               |  
|                         |  
+-------------------------+
|                         |
| Root Directory Entries  |<- Contains directory
|                         |   entries for the root 
|                         |   directory. All the
|                         |   files or subdirectories
|  +-------------------+  |   of the Root Directory
|  | Directory Entry 1 |  |   listed here.
|  +-------------------+  |
|  | Directory Entry 2 |  |
|  +-------------------+  |
|  |Directory Entry ...|  |
|  +-------------------+  |
|                         |
+-------------------------+
|                         |
|       File Data         | <- Contains the actual file
|                         |    data (raw data of files)
+-------------------------+

+-------------------------------------------------+
|        ISO 9660 File System at Top level        |
+------------------------+------------------------+
| System area (32,768 B) | Unused by ISO 9660     |
| 16 sectors of 2048     |                        |
| bytes each             |                        |
|-------------------------------------------------|
|             			 | Volume descriptor set  |
|      Data area         | Path tables,           |
|                        | directories and files  |
+------------------------+------------------------+

Note: A single sector in ISO file system is of 4KB = 2048 bytes.

image-206.png

1️⃣ System Area (Sectors 0-15):

  • Location: The system area occupies the first 16 sectors (sectors 0-15) of the ISO 9660 filesystem.
  • Size: 16 Sectors, 1 Sector = 2048 bytes = 2KB
    • 16 Sector = 2048 * 16 = 32768 bytes = 32 KB
  • Purpose: It's reserved for boot code and system-specific data. For bootable CDs, this area includes the boot record and bootloader.
  • Boot Record: If the ISO is bootable, the boot record is typically found in the first sector (sector 0).
    • Sector 0: Boot Record (for bootable ISOs)
      • This sector contains the boot code that is executed by the BIOS if the CD is bootable.
    • Sectors 1-15: Reserved for system use
      • These sectors can be used for additional boot code or other system-specific data.

2️⃣ Volume Descriptors (Sector 16):

The sector 0x00 - 0x0F (16 sectors) are reserved for the System Area. The Volume Descriptors can be found starting at sector 0x10 (16). The data area begins with the volume descriptor set, a set of one or more volume descriptors terminated with a volume descriptor set terminator.

 Volume Descriptor Set
+----------------------+
| Volume Descriptor 1  |
|----------------------|
| Volume Descriptor 2  |
|----------------------|
| ....                 |
|----------------------|
| Volume Descriptor #N |
|----------------------|
| Volume Descriptor    |
| set terminator       |
+----------------------+
  • Each Volume Descriptor is 2048 bytes in size, fitting perfectly into a single sector.

The format of the volume descriptor as follows:

OFFSETLENGTH (bytes)FIELD NAMEDESCRIPTION
01TypeVolume Descriptor Type Code.
15IdentifierAlways "CD001"
61VersionVolume Descriptor Version (0x01)
72041DataDepends on the volume descriptor type

Note: The data field of a volume descriptor may be subdivided into several fields, with the exact content depending on the type. Redundant copies of each volume descriptor can also be included in case the first copy of the descriptor becomes corrupt.

Each Volume Descriptor is one sector (2KB)(2048 bytes) long.

Basic Types of Volume Descriptors:

  1. Primary Volume Descriptor (PVD)
  2. Boot Record Volume Descriptor (BRVD)
  3. Supplementary Volume Descriptor (SVD)
  4. Volume Partition Descriptor (VPD)
  5. Volume Descriptor Set Terminator (VDST)

Volume Descriptor Type Codes:

The ISO 9660 file system uses volume descriptor type codes to identify the type of each volume descriptor. Here are the standard type codes:

  1. Boot Record Volume Descriptor (BRVD): Type code 0.
  2. Primary Volume Descriptor (PVD): Type code 1
  3. Supplementary Volume Descriptor (SVD): Type code 2
  4. Volume Partition Descriptor (VPD): Type code 3
  5. Volume Descriptor Set Terminator (VDST): Type code 255
Type CodeDescriptor Name
0Boot Record Volume Descriptor (BRVD)
1Primary Volume Descriptor (PVD)
2Supplementary Volume Descriptor (SVD)
3Volume Partition Descriptor (VPD)
255Volume Descriptor Set Terminator (VDST)

An ISO 9660 compliant disc must contain at least one primary volume descriptor describing the file system and a volume descriptor set terminator for indicating the end of the descriptor sequence.

Ⅰ Primary Volume Descriptor (PVD)

This is a lengthy descriptor, but it contains some very useful information for reading the rest of the file system. It mostly contains information about the volume, characteristics  and metadata, including a root directory records that indicates in which sector (location) the root directory is located. Other fields contain the description or name of the volume, and information about who created it and with which application. 

  • Type Code: 1
  • Purpose: Contains essential information about the volume, such as volume name, volume size, and system identifier. Every ISO 9660 file system must have one PVD.
  • Location: Typically it is present at Logical Block Address (LBA) 16 according to ISO 9660 specification.
  • Describes the primary volume, including metadata like volume size, volume identifier, and pointers to the root directory.
  • Contains metadata about the volume, including the volume identifier, volume size, and pointers to the root directory and path tables.

Structure of the Primary Volume Descriptor (PVD)

The Primary Volume Descriptor is located at Logical Block Address (LBA) 16 and has a specific format, defined by the ISO 9660 standard. Here’s a detailed view of its structure:

OffsetLength (BYTes)Field NameDescription
01Volume Descriptor TypeIndicates the type of volume descriptor (0x01 for Primary Volume Descriptor).
15Standard IdentifierStandard identifier ("CD001"). Always “CD001” for ISO 9660.
61Volume Descriptor VersionAlways 0x01 for the current version of ISO 9660.
71UnusedReserved for future use, should be 0x00.
832System IdentifierSystem identifier (ASCII string, space-padded). Identifies the system that created the volume.
4032Volume IdentifierVolume identifier (ASCII string, space-padded)
728UnusedUnused field (set to 0). Reserved for future use, should be 0x00.
804Volume Space Size (LSB)Volume space size (total number of logical blocks, least significant byte first)
844Volume Space Size (MSB)Volume space size (total number of logical blocks, most significant byte first)
8832UnusedUnused field (set to 0)
1202Volume Set Size (LSB)Volume set size (number of volumes in the set, least significant byte first)
1222Volume Set Size (MSB)Volume set size (number of volumes in the set, most significant byte first)
1242Volume Sequence Number (LSB)Volume sequence number (this volume's number in the set, least significant byte first)
1262Volume Sequence Number (MSB)Volume sequence number (this volume's number in the set, most significant byte first)
1282Logical Block Size (LSB)Logical block size in bytes (least significant byte first)
1302Logical Block Size (MSB)Logical block size in bytes (most significant byte first)
1324Path Table Size (LSB)Path table size in bytes (least significant byte first)
1364Path Table Size (MSB)Path table size in bytes (most significant byte first)
1404Type L Path Table LocationLocation of the type L path table (least significant byte first)
1444Optional Type L Path Table LocationOptional location of the type L path table (least significant byte first)
1484Type M Path Table LocationLocation of the type M path table (most significant byte first)
1524Optional Type M Path Table LocationOptional location of the type M path table (most significant byte first)
15634Directory Record for Root DirectoryDirectory record for the root directory
190128Volume Set IdentifierVolume set identifier (ASCII string, space-padded)
318128Publisher IdentifierPublisher identifier (ASCII string, space-padded)
446128Data Preparer IdentifierData preparer identifier (ASCII string, space-padded)
574128Application IdentifierApplication identifier (ASCII string, space-padded). Identifier for the application used to create the volume.
70237Copyright File IdentifierFile identifier of copyright file (ASCII string)
73937Abstract File IdentifierFile identifier of abstract file (ASCII string)
77637Bibliographic File IdentifierFile identifier of bibliographic file (ASCII string)
81317Volume Creation Date and TimeDate and time of volume creation (format: YYYYMMDDHHMMSSCC). Date and time when the volume was created.
83017Volume Modification Date and TimeDate and time of volume modification (format: YYYYMMDDHHMMSSCC). Date and time when the volume was last modified.
84717Volume Expiration Date and TimeDate and time of volume expiration (format: YYYYMMDDHHMMSSCC). Date and time when the volume expires.
86417Volume Effective Date and TimeDate and time of volume becoming effective (format: YYYYMMDDHHMMSSCC). Date and time when the volume becomes effective.
8811File Structure VersionFile structure version (always 1)
8821UnusedUnused field (set to 0). Reserved for future use, should be 0x00.
883512Application UseApplication use (512 bytes, application-specific). Space for application-specific data.
1395653ReservedReserved for future standardization (set to 0). Reserved for future use.
  • Volume Descriptor Type (Offset 0, Length 1 byte):
    • Indicates the type of volume descriptor. For PVD, this value must be 0x01.
  • Standard Identifier (Offset 1, Length 5 bytes):
    • Identifies the standard used. For ISO 9660, must be "CD001".
  • Volume Descriptor Version (Offset 6, Length 1 byte):
    • Specifies the version of the standard. For ISO 9660, must be 0x01.
  • Unused Field (Offset 7, Length 1 byte):
    • Should be 0.
  • System Identifier (Offset 8, Length 32 bytes):
    • A string identifying the system that created the volume. Usually padded with spaces.
  • Volume Identifier (Offset 40, Length 32 bytes):
    • A string identifying the volume. Usually padded with spaces.
  • Unused Field (Offset 72, Length 8 bytes):
    • Unused should be 0.
  • Volume Space Size (Offset 80, Length 8 bytes):
    • The total number of logical blocks in the volume, recorded in both little-endian and big-endian formats.
  • Unused Field (Offset 88, Length 32 bytes):
    • Unused field, should be 0.
  • Volume Set Size (Offset 120, Length 4 bytes):
    • The number of volumes in the set. For a single volume, this is 1.
  • Volume Sequence Number (Offset 124, Length 4 bytes):
    • The sequence number of this volume in the set. For a single volume, this is 1.
  • Logical Block Size (Offset 128, Length 4 bytes):
    • The size of each logical block, typically 2048 bytes.
  • Path Table Size (Offset 132, Length 8 bytes):
    • The size of the path table in both little and big endian.
  • Location of Type L Path Table (Offset 140, Length 4 bytes):
    • The starting logical block number of the type L path table (little-endian).
    • LBA of path table.
  • Location of Optional Type L Path Table (Offset 144, Length 4 bytes):
    • LBA location of the optional path table. The path table pointed to contains only little-endian values. Zero means that no optional path table exists.
  • Location of Type M Path Table (Offset 148, Length 4 bytes):
    • The starting logical block number of the type M path table (big-endian).
  • Location of Optional Type M Path Table (Offset 152, Length 4 bytes):
    • LBA location of the optional path table. The path table pointed to contains only big-endian values. Zero means that no optional path table exists.
  • Directory Entry for Root Directory (Offset 156, Length 34 bytes):
    • Describes the root directory, including its location and size.
    • Note that this is not an LBA address, it is the actual Directory Record, which contains a single byte Directory Identifier (0x00), hence the fixed 34 byte size.
  • Volume Set Identifier (Offset 190, Length 128 bytes):
    • A string identifying the volume set.
  • Publisher Identifier (Offset 318, Length 128 bytes):
    • A string identifying the publisher.
  • Data Preparer Identifier (Offset 446, Length 128 bytes):
    • A string identifying the entity that prepared the data.
  • Application Identifier (Offset 574, Length 128 bytes):
    • A string identifying the application used to create the volume.
  • Copyright File Identifier (Offset 702, Length 37 bytes):
    • A string identifying the copyright file.
  • Abstract File Identifier (Offset 739, Length 37 bytes):
    • A string identifying the abstract file.
  • Bibliographic File Identifier (Offset 776, Length 37 bytes):
    • A string identifying the bibliographic file.
  • Volume Creation Date and Time (Offset 813, Length 17 bytes):
    • The date and time when the volume was created.
  • Volume Modification Date and Time (Offset 830, Length 17 bytes):
    • The date and time when the volume was last modified.
  • Volume Expiration Date and Time (Offset 847, Length 17 bytes):
    • The date and time when the volume expires.
    • The date and time after which this volume is considered to be obsolete. If not specified, then the volume is never considered to be obsolete.
  • Volume Effective Date and Time (Offset 864, Length 17 bytes):
    • The date and time when the volume becomes effective.
    • The date and time after which the volume may be used. If not specified, the volume may be used immediately.
  • File Structure Version (Offset 881, Length 1 byte):
    • The version of the file structure, always 0x01 for ISO 9660.
  • Unused (Offset 882, Length 1 byte):
    • Unused, always 0x00.
  • Application Use (Offset 883, Length 512 bytes):
    • Space reserved for application-specific data.
    • Contents not defined by ISO 9660.
  • Reserved (Offset 1395, Length 653 bytes):
    • Reserved by ISO for future use.
+---------------------------+
| Volume Descriptor Type    | <- Offset 0, Length 1 byte
+---------------------------+
| Standard Identifier       | <- Offset 1, Length 5 bytes
+---------------------------+
| Volume Descriptor Version | <- Offset 6, Length 1 byte
+---------------------------+
| Unused                    | <- Offset 7, Length 1 byte
+---------------------------+
| System Identifier         | <- Offset 8, Length 32 bytes
+---------------------------+
| Volume Identifier         | <- Offset 40, Length 32 bytes
+---------------------------+
| Unused                    | <- Offset 72, Length 8 bytes
+---------------------------+
| Volume Space Size         | <- Offset 80, Length 8 bytes
+---------------------------+
| Escape Sequences          | <- Offset 88, Length 32 bytes
+---------------------------+
| Volume Set Size           | <- Offset 120, Length 4 bytes
+---------------------------+
| Volume Sequence Number    | <- Offset 124, Length 4 bytes
+---------------------------+
| Logical Block Size        | <- Offset 128, Length 4 bytes
+---------------------------+
| Path Table Size           | <- Offset 132, Length 8 bytes
+---------------------------+
| Location of Type L Path   | <- Offset 140, Length 4 bytes
+---------------------------+
| Location of Type M Path   | <- Offset 144, Length 4 bytes
+---------------------------+
| Directory Entry for Root  | <- Offset 156, Length 34 bytes
+---------------------------+
| Volume Set Identifier     | <- Offset 190, Length 128 bytes
+---------------------------+
| Publisher Identifier      | <- Offset 318, Length 128 bytes
+---------------------------+
| Data Preparer Identifier  | <- Offset 446, Length 128 bytes
+---------------------------+
| Application Identifier    | <- Offset 574, Length 128 bytes
+---------------------------+
| Copyright File Identifier | <- Offset 702, Length 37 bytes
+---------------------------+
| Abstract File Identifier  | <- Offset 739, Length 37 bytes
+---------------------------+
| Bibliographic File        | <- Offset 776, Length 37 bytes
+---------------------------+
| Volume Creation Date      | <- Offset 813, Length 17 bytes
+---------------------------+
| Volume Modification Date  | <- Offset 830, Length 17 bytes
+---------------------------+
| Volume Expiration Date    | <- Offset 847, Length 17 bytes
+---------------------------+
| Volume Effective Date     | <- Offset 864, Length 17 bytes
+---------------------------+
| File Structure Version    | <- Offset 881, Length 1 byte
+---------------------------+
| Unused                    | <- Offset 882, Length 1 byte
+---------------------------+
| Application Use           | <- Offset 883, Length 512 bytes
+---------------------------+
| Reserved                  | <- Offset 1395, Length 653 bytes
+---------------------------+

Offset 156: It is the most important location in PVD, with this we can access the entries of the root directories.

  • The structure at offset 156 in the Primary Volume Descriptor (PVD) contains the root directory entry.
  • Its size is 34 bytes, which starts at offset 156 and ends at offset 189.

Here is the detailed structure of this entry:

Byte OffsetLength (Bytes)FieldDescriptionFormat
01Length of Directory RecordLength of this directory record in bytes.-
11Extended Attribute Record LengthLength of the extended attribute record (usually 0).-
24Location of Extent (LBA) in LSBLogical Block Address of the extent (little-endian).LSB
64Location of Extent (LBA) in MSBLogical Block Address of the extent (big-endian).MSB
104Data Length (LSB)Size of the extent in bytes (little-endian).LSB
144Data Length (MSB)Size of the extent in bytes (big-endian).MSB
187Recording Date and TimeDate and time of recording.-
251File FlagsFlags indicating file properties (e.g., directory, hidden, etc.).-
261File Unit SizeUsed for interleaved files (usually 0).-
271Interleave Gap SizeUsed for interleaved files (usually 0).-
284Volume Sequence Number (SB)Volume sequence number (little-endian).LSB
324Volume Sequence Number (MSB)Volume sequence number (big-endian).MSB
361Length of File IdentifierLength of the file identifier (name).-
37VariableFile IdentifierFile identifier (name). Padded to an even length if necessary.-
Root Directory Entry Structure (34 bytes)
+------------------------------------+
|  0 | Length of Directory Record    | (1 byte)
+------------------------------------+
|  1 | Extended Attribute Record Len | (1 byte)
+------------------------------------+
|  2 | Location of Extent (LBA)      | (4 bytes, little-endian)
+------------------------------------+
|  6 | Location of Extent (LBA)      | (4 bytes, big-endian)
+------------------------------------+
| 10 | Data Length                   | (4 bytes, little-endian)
+------------------------------------+
| 14 | Data Length                   | (4 bytes, big-endian)
+------------------------------------+
| 18 | Recording Date and Time       | (7 bytes)
+------------------------------------+
| 25 | File Flags                    | (1 byte)
+------------------------------------+
| 26 | File Unit Size                | (1 byte)
+------------------------------------+
| 27 | Interleave Gap Size           | (1 byte)
+------------------------------------+
| 28 | Volume Sequence Number        | (2 bytes, little-endian)
+------------------------------------+
| 30 | Volume Sequence Number        | (2 bytes, big-endian)
+------------------------------------+
| 32 | Length of File Identifier     | (1 byte)
+------------------------------------+
| 33 | File Identifier               | (variable length)
+------------------------------------+
  • The Location of Extent (LSB) (LBA) at offset 2:
    • The location of the extent (the starting sector of the root directory entries) is a 4-byte little-endian value starting at byte offset 158 within the PVD.
    • This Location of Extent at offset 2 is for the little-endian system.
    • This points to the root directory entries, which are array of the root directory entry - contains all the files and directories of the root directory.

Ⅱ Boot Record Volume Descriptor (BRVD) (Optional)

  • Type Code: 0
  • Location: Sector LBA 17, if present. It is typically placed after the PVD or at a location specified by the creator of the ISO file.
  • Boot System Identifier: A 32-byte string that identifies the boot system. For example, "EL TORITO SPECIFICATION" for El Torito bootable CDs.
  • Boot Identifier: A 32-byte string that specifies the identifier for the boot image.
  • Boot System Use: Additional data used by the boot system, such as pointers to the actual boot image.
OffsetLengthField NameDescription
01TypeVolume Descriptor Type (0)
15IdentifierStandard Identifier (CD001)
61VersionVolume Descriptor Version (1)
732Boot System IdentifierBoot System Identifier
3932Boot IdentifierBoot Identifier
711977Boot System UseBoot System Use

Example Boot Record Volume Descriptor

A bootable ISO might include a Boot Record Volume Descriptor that looks like this:

  1. Volume Descriptor Type: 0
  2. Standard Identifier: CD001
  3. Volume Descriptor Version: 1
  4. Boot System Identifier: EL TORITO SPECIFICATION
  5. Boot Identifier: BOOT IMAGE
  6. Boot System Use: Data specific to the El Torito specification, pointing to the boot image.

Ⅲ Supplementary Volume Descriptor (Optional):

  • Type Code: 2
  • Location: Follows the Primary Volume Descriptor.

In addition to the primary volume descriptor, supplementary volume descriptors may be present.

The Supplementary Volume Descriptor (SVD) structure is similar to the Primary Volume Descriptor (PVD), but it supports additional features like Unicode file names for the Joliet extension.

OffsetLengthDescription
01Volume Descriptor Type (2)
15Standard Identifier ("CD001")
61Volume Descriptor Version (1)
71Flags (e.g., UTF-16 support)
832System Identifier
4032Volume Identifier
728Unused Field
804Volume Space Size (LSB first)
844Volume Space Size (MSB first)
8832Escape Sequences
1202Volume Set Size (LSB first)
1222Volume Set Size (MSB first)
1242Volume Sequence Number (LSB first)
1262Volume Sequence Number (MSB first)
1282Logical Block Size (LSB first)
1302Logical Block Size (MSB first)
1324Path Table Size (LSB first)
1364Path Table Size (MSB first)
1404Location of Occurrence of L Path Table (LSB)
1444Location of Optional Occurrence of L Path Table (LSB)
1484Location of Occurrence of M Path Table (MSB)
1524Location of Optional Occurrence of M Path Table (MSB)
15634Root Directory Record
190128Volume Set Identifier
318128Publisher Identifier
446128Data Preparer Identifier
574128Application Identifier
70237Copyright File Identifier
73937Abstract File Identifier
77637Bibliographic File Identifier
81317Volume Creation Date and Time
83017Volume Modification Date and Time
84717Volume Expiration Date and Time
86417Volume Effective Date and Time
8811File Structure Version (1)
8821Unused Field
883512Application Use
1395653Reserved

Ⅳ Volume Partition Descriptor (Optional):

  • Type Code: 3
  • Location: Follows the Supplementary Volume Descriptor if present. 

Ⅴ Volume Descriptor Set Terminator (Type Code 255):

  • It marks the end of the volume descriptors.

Location: Always the last volume descriptor in the sequence.

OffsetLengthDescription
01Volume Descriptor Type (255)
15Standard Identifier ("CD001")
61Volume Descriptor Version (1)
72041Reserved

Volume Descriptor Set Layout:

  • The Primary Volume Descriptor is always located at Logical Block 16.
  • The sequence of volume descriptors is terminated by the Volume Descriptor Set Terminator, ensuring the reader recognizes the end of the descriptor list.
  • Supplementary and Partition Descriptors, if present, follow the Primary Volume Descriptor but do not have fixed positions beyond being after Block 16.
     -: Volume Descriptor Set Layout :-
+-----------------------------------------+
| Sector LBA  | Descriptor Type           |
|-------------|---------------------------|
| 16          | Primary Volume Descriptor |
|             |     (Type 1) (PVD)        |
|-----------------------------------------|
| Follows the | Supplementary Volume      |
| PVD         | Descriptor (SVD)          |
|             |   (Type 2) (Optional)     |
|-----------------------------------------|
| Follows the | Volume Partition          |
| SVD         | Descriptor (VPD)          |
|             |   (Type 3) (Optional)     |
|-----------------------------------------|
| No fixed    | Boot Volume Descriptor    |
| Position    | (Type 0) (Optional)       |
|-----------------------------------------|
| ....        | Other Volume Descriptor   |
|             | (if present)              |
|-----------------------------------------|
| Last Entry  | Volume Descriptor Set     |
|             | Terminator (Type 255)     |
+-----------------------------------------+

Typical ISO 9660 ISO:
+-----------------------------------+
| Sector LBA | Descriptor Type      |
|-----------------------------------+
| 16         | Primary Volume       |
|            |     (Type 1)         |
|-----------------------------------|
| 17         | Supplementary Volume |
|            |   (Type 2) (Optional)|
|-----------------------------------|
| 18         | Volume Partition     |
|            |   (Type 3) (Optional)|
|-----------------------------------|
| 19         | Volume Descriptor Set|
|            | Terminator (Type 255)|
+-----------------------------------+


      -: Bootable ISO :-
+-----------------------------------+
| Sector LBA | Descriptor Type      |
|------------|----------------------|
| 16         | Boot Record (Type 0) |
|	         |                      |
|-----------------------------------+
| 17         | Primary Volume       |
|            |     (Type 1)         |
|-----------------------------------|
| 18         | Supplementary Volume |
|            |   (Type 2) (Optional)|
|-----------------------------------|
| 19         | Volume Partition     |
|            |   (Type 3) (Optional)|
|-----------------------------------|
| 20         | Volume Descriptor Set|
|            | Terminator (Type 255)|
+-----------------------------------+

Example Sequence:

A typical ISO 9660 volume might have the following descriptors starting from LBA 16:

  1. Primary Volume Descriptor (PVD) at LBA 16
  2. Supplementary Volume Descriptor (SVD) at LBA 17 (if Joliet extension is used)
  3. Volume Descriptor Set Terminator (VDST) at LBA 18

In a bootable ISO:

  1. Boot Record Volume Descriptor (BRVD) at LBA 16
  2. Primary Volume Descriptor (PVD) at LBA 17
  3. Supplementary Volume Descriptor (SVD) at LBA 18 (if Joliet extension is used)
  4. Volume Descriptor Set Terminator (VDST) at LBA 19

3️⃣ Path Tables:

Contains information about directory hierarchy. It allows the system to efficiently and quickly locate directories without having to read through the entire directory tree.

  • Makes it easier to locate directories. It only consists the Directories.
  • Purpose: The path table provides a compact, easily searchable list of all directories in the file system, along with pointers to their location in  the directory hierarchy.
  • Location: The location of the path table is specified in the Primary Volume Descriptor (PVD).
    • Little-endian:
      • Offset: 140 in PVD.
      • Length: 4 bytes
      • Description: The starting LBA of the little-endian path table.
    • Big-Endian:
      • Offset: 148 in PVD.
      • Length: 4 bytes
      • Description: The starting LBA of the big-endian path table.
  • Structure: The path table consists of a series of path table records, each representing a directory. The Path Table itself is stored in contiguous sectors.

Path Table Location in PVD:

OffsetLengthField NameDescription
1404Location of Path Table (Little-endian)Logical block number of the little-endian path table
1444Optional Location of Path Table (Little-endian)Logical block number of an optional second little-endian path table
1484Location of Path Table (Big-endian)Logical block number of the big-endian path table
1524Optional Location of Path Table (Big-endian)Logical block number of an optional second big-endian path table

In the ISO 9660 filesystem, path tables provide a compact representation of the directory hierarchy, making it easier to locate directories quickly. There are two types of path tables in the ISO 9660 standard: the Type L Path Table (little-endian) and the Type M Path Table (big-endian). Both path tables contain the same information but are encoded in different byte orders to support systems with different endianness.

Structure of a Path Table Entry:

Each entry in a path table describes a directory and includes the following fields:

  1. Directory Identifier Length (1 byte): The length of the directory identifier (name).
  2. Extended Attribute Record Length (1 byte): The length of the extended attribute record (if any).
  3. Location of Extent (4 bytes): The logical block number where the directory's extent begins. This is recorded in little-endian format in the Type L Path Table and in big-endian format in the Type M Path Table.
  4. Parent Directory Number (2 bytes): The number of the parent directory. Directories are numbered sequentially starting from 1 for the root directory.
  5. Directory Identifier (variable length): The name of the directory, padded to an even length with a null byte if necessary.
OffsetLength (byte)Description
01Directory Identifier Length (LEN_DI)
11Extended Attribute Record Length
24Location of Extent (Logical Block Number) (LSB)
62Parent Directory Number (LSB)
8LEN_DIDirectory Identifier (padded to even length)
+-----------------------------+
| Directory Identifier Length |  (1 byte)
+-----------------------------+
| Extended Attribute Length   |  (1 byte)
+-----------------------------+
| Location of Extent          |  (4 bytes)<- LBA 
|                             |     (little-endian)
+-----------------------------+
| Parent Directory Number     |  (2 bytes)
+-----------------------------+
| Directory Identifier        |  (variable length,
|                             |   padded if needed)
+-----------------------------+

Length of Directory Identifier: 5
Extended Attribute Record Length: 0
Directory Location: 0x0010 (16 in decimal)
Parent Directory Number: 1
Directory Identifier: "DIR_A"
Padding: 0
  1. Directory Identifier Length (LEN_DI)
    1. Length of the directory identifier (name) in bytes.
    2. This helps in parsing the entries as the directory names are of variable length.
    3. 1 byte.
  2. Extended Attribute Record Length
    1. Length of the extended attribute record associated with the directory.
    2. Extended attributes may include additional metadata about the directory.
    3. 1 byte.
  3. Location of Extent (Logical Block Number): It refers to the Logical Block Address (LBA) where the directory data begins.
    1. The starting logical block number of the directory extent.
    2. This helps in locating the directory’s content on the disk. The Type L Path Table uses little-endian format, and the Type M Path Table uses big-endian format.
    3. This field is 8 bytes long: 4 bytes, stored in Little-Endian (LSB) format.
    4. The LBA is used by the file system to directly locate the beginning of the file or directory data on the disc.
  4. Parent Directory Number
    1. The number of the parent directory in the path table. A reference to the parent directory. This number corresponds to the sequence number of the parent directory in the path table, allowing the construction of the directory hierarchy.
    2. 2 bytes, stored in Little-Endian (LSB) format.
  5. Directory Identifier
    1. The name of the directory. The actual name of the directory. If the length is odd, it is padded with a null byte to maintain even length alignment.
    2. Variable length (specified by LEN_DI), padded to an even length with a null byte if necessary
/ (root)
├── dir1
├── dir2
│   └── dir3


+------+------+------------+--------+-------------+
| Dir  | Ext  |  Location  | Parent |  Directory  |
| ID   | Attr |  of Extent | Dir    |             |
| Len  | Len  |   (LBA)    | Num    |  Identifier |
+------+------+------------+--------+-------------+
| 0x01 | 0x00 | 0x00000010 | 0x0001 | "/" (Root   |
|      |      |            |        | Directory)  |
+------+------+------------+--------+-------------+
| 0x03 | 0x00 | 0x00000020 | 0x0001 |    "dir1"   |
+------+------+------------+--------+-------------+
| 0x03 | 0x00 | 0x00000030 | 0x0001 |    "dir2"   |
+------+------+------------+--------+-------------+
| 0x05 | 0x00 | 0x00000040 | 0x0003 |    "dir3"   |
+------+------+------------+--------+-------------+

In this example:

  • The root directory ("/") has a Location of Extent at block 16 (0x10).
  • "dir1" and "dir2" are subdirectories of the root, located at blocks 32 (0x20) and 48 (0x30), respectively.
  • "dir3" is a subdirectory of "dir2", located at block 64 (0x00000040).
  • Root Directory (/)
    • Directory Identifier Length: 1
    • Extended Attribute Record Length: 0
    • Location of Extent: Logical Block 16 (0x10) (LBA)
    • Parent Directory Number: 1 (itself)
    • Directory Identifier: 0x00 (root)
  • Directory 1(dir1)
    • Directory Identifier Length: 4
    • Extended Attribute Record Length: 0
    • Location of Extent: Logical Block 32 (0x20)
    • Parent Directory Number: 1 (root)
    • Directory Identifier: "dir1" (padded to even length if necessary)
  • Directory 2 (dir2)
    • Directory Identifier Length: 4
    • Extended Attribute Record Length: 0
    • Location of Extent: Logical Block 48 (0x30)
    • Parent Directory Number: 1 (root)
    • Directory Identifier: "dir2" (padded to even length if necessary)
  • Directory 3 (dir3)
    • Directory Identifier Length: 4
    • Extended Attribute Record Length: 0
    • Location of Extent: Logical Block 48 (0x30)
    • Parent Directory Number: 3 (dir2)
    • Directory Identifier: "dir3" (padded to even length if necessary)

Logical Block Allocation

The path tables themselves are stored in logical blocks in the ISO 9660 filesystem. The Primary Volume Descriptor contains fields specifying the locations of the Type L and Type M Path Tables.

  • Path Table Size: Indicates the size of the path table in bytes.
  • Location of Occurrence of Type L Path Table: The logical block number where the Type L Path Table starts.
  • Location of Occurrence of Type M Path Table: The logical block number where the Type M Path Table starts.

Example of Path Table Locations

If the Primary Volume Descriptor specifies the following:

  • Path Table Size: 1024 bytes
  • Location of Type L Path Table: Logical block 24
  • Location of Type M Path Table: Logical block 25
+-------------------------------+
|   Primary Volume Descriptor   |
|                               |
|  Path Table Size: 1024 bytes  |
|  Location of Type L Path Table| <- LBA 24
|  Location of Type M Path Table| <- LBA 25
+-------------------------------+
|     Type L Path Table         |
|  Entry 1: Root ("/")          |
|  Entry 2: "dir1"              |
|  Entry 3: "dir2"              |
|  Entry 4: "dir3"              |
+-------------------------------+
|     Type M Path Table         |
|  Entry 1: Root ("/")          |
|  Entry 2: "dir1"              |
|  Entry 3: "dir2"              |
|  Entry 4: "dir3"              |
+-------------------------------+

4 Root Directory:

  • Contains directory entries that describe files and subdirectories in the root directory.

4️⃣ Directory Entries (Directory Records):

Directory records (also known as directory entries) in the ISO 9660 file system provide information about files and directories within a directory. Each directory record contains metadata about a file or directory, such as its name, size, and location on the disc.

  • Each directory entry describes a file or directory.
  • Includes metadata like file name, size, and LBA (location of the file on the disk).

Structure of a Directory Record

Each directory record has the following format:

OffsetLength (bytes)Description
01Length of Directory Record
11Extended Attribute Record Length
24Location of Extent, LBA (LSB)
64Location of Extent, LBA (MSB)
104Data Length (LSB)
144Data Length (MSB)
187Recording Date and Time
251File Flags
261File Unit Size
271Interleave Gap Size
282Volume Sequence Number (LSB)
302Volume Sequence Number (MSB)
321Length of File Identifier
33VariableFile Identifier (name)
N1 (optional)Padding Field (0 if Length of File Identifier is even)

-: Detailed Field Descriptions :-

1 Length of Directory Record (Offset 0, Size: 1 byte):

  • It indicates the length of this directory record in bytes.
  • It is 1 byte long field.
  • Value = 0x22 = 34 bytes.

2 Extended Attribute Record Length (Offset 1, Size: 1 byte):

  • The length of the extended attribute record for this file or directory (usually 0).
  • This field is 1 byte long.

3 Location of Extent LSB (Offset 2, Size: 4 bytes): (LBA) for LSB.

  • The starting logical block number of the file or directory extent.
  • The "Location of Extent" field in the directory entry specifies the starting logical block number of the file or directory extent.
  • This field is 4 bytes long.

4 Location of Extent MSB (Offset: 6, Size: 4 bytes):

  • Logical block number where the file/directory data starts (in MSB format).
  • This field is also 4 bytes long.

5 Data Length LSB (Offset: 10, Size: 4 bytes):

  • The length of the file/directory data in bytes in LSB.
  • This field is 4 bytes long.

6 Data Length MSB (Offset: 14, Size: 4 bytes):

  • The length of the file/directory data in bytes in MSB.
  • This field is also 4 bytes long.

7 Recording Date and Time (Offset: 18, Size: 7 bytes):

  • The recording date and time of the file/directory.
  • It is 7 bytes in size.
  • Format: year (since 1900), month, day, hour, minute, second, and time zone offset from GMT.

8 File Flags (Offset: 25, Size: 1 byte):

  • Flags indicating the file type and attributes (e.g., directory, hidden, associated file).
  • This field is 1 byte in size.
  • Bitfield structure:
    • Bit 0: Hidden
      • If set, the entry is a hidden file.
    • Bit 1: Directory
      • If set, the entry is a directory.
    • Bit 2: Associated File
      • If set, the entry is an associated file.
    • Bit 3: Record format specified
      • If set, the file's record is formatted according to the extended attribute record.
    • Bit 4: Permissions specified
      • If set, the file is a protection file.
    • Bit 5: Reserved
    • Bit 6: Reserved
    • Bits 7: Last directory record in the extent.

9 File Unit Size (Offset 16, Size: 1 byte):

  • The size of the file unit for interleaved files (usually 0).
  • This field is 1 byte in size.

10 Interleave Gap Size (Offset 27, Size: 1 byte):

  • The size of the gap between interleaved files (usually 0)
  • This field is 1 byte in size.

11 Volume Sequence Number LSB (Offset 28, Size: 2 bytes):

  • The volume sequence number for this file/directory in LSB.
  • This field is 2 bytes in size.

12 Volume Sequence Number MSB (Offset 30, Size: 2 bytes):

  • The volume sequence number for this file/directory in MSB.
  • This field is 2 bytes in size.

13 Length of File Identifier (Offset 32, Size: 1 byte):

  • The length of the file identifier (name).
  • This field is 1 byte long.

14 File Identifier (Offset 33, Size: Variable):

  • The name of the file or directory.
  • Variable length, padded to an even length with a null byte if necessary.

15 Padding:

  • Padding bytes to ensure the directory record is even in length. If the length of the file identifier is even, a padding byte (0) is added to maintain word alignment.

File Flags:

The File Flags field in an ISO9660 directory record provides important information about the file or directory. These flags are represented as a single byte, where each bit has a specific meaning. Below is a detailed breakdown of the File Flags field:

File Flags Field (1 byte)

BitValueDescription
00x01Hidden file
10x02Directory
20x04Associated file
30x08Record format specified
40x10Permissions specified
50x20Reserved
60x40Reserved
70x80Last directory record in the extent

👀Bitwise Explanation:

  1. Hidden file (Bit 0, 0x01): If set, this file is hidden and should not be displayed in a directory listing by default.
  2. Directory (Bit 1, 0x02): If set, this record points to a directory rather than a file.
  3. Associated file (Bit 2, 0x04): If set, this file is an associated file.
  4. Record format specified (Bit 3, 0x08): If set, the record format is specified.
  5. Permissions specified (Bit 4, 0x10): If set, file permissions are specified.
  6. Reserved (Bit 5 and Bit 6, 0x20 and 0x40): These bits are reserved for future use and should typically be set to 0.
  7. Last directory record in the extent (Bit 7, 0x80): If set, this is the last record in the current extent.

Example of File Flags:

  • A regular file with no special attributes: 0x00
  • A hidden file: 0x01
  • A directory: 0x02
  • A hidden directory: 0x03
  • The last record in the extent: 0x80
  • A hidden directory that is also the last record in the extent: 0x83

-: Directory Record for a file :-

Let's illustrate with an example directory record for a file named "README.TXT":

OffsetLength (bytes)DescriptionExample Value
01Length of Directory Record34
11Extended Attribute Record Length0
24Location of Extent (LSB)0x00000014 (20 in decimal)
64Location of Extent (MSB)0x14000000
104Data Length (LSB)0x00000200 (512 in decimal)
144Data Length (MSB)0x00020000
187Recording Date and Time0x7E070718202120
251File Flags0x00 (Regular file)
261File Unit Size0
271Interleave Gap Size0
282Volume Sequence Number (LSB)0x0001
302Volume Sequence Number (MSB)0x0100
321Length of File Identifier10
33VariableFile Identifier (name)"README.TXT" (ASCII bytes)
431 (optional)Padding Field0

Directory Record for a Directory:

Let's illustrate with an example directory record for a directory named "DOCUMENTS":

OffsetLength (bytes)DescriptionExample Value
01Length of Directory Record34
11Extended Attribute Record Length0
24Location of Extent (LSB)0x00000030 (48 in decimal)
64Location of Extent (MSB)0x30000000
104Data Length (LSB)0x00000400 (1024 in decimal)
144Data Length (MSB)0x00040000
187Recording Date and Time0x7E070718202120
251File Flags0x02 (Directory)
261File Unit Size0
271Interleave Gap Size0
282Volume Sequence Number (LSB)0x0001
302Volume Sequence Number (MSB)0x0100
321Length of File Identifier9
33VariableFile Identifier (name)"DOCUMENTS" (ASCII bytes)
421 (optional)Padding Field0 (since "DOCUMENTS" has an odd length)

Example Directory Record:

Consider a directory with a single file named example.txt:

  • Length of Directory Record: 34
  • Extended Attribute Record Length: 0
  • Location of Extent: 100 (logical block address, LBA)
  • Data Length: 2048 bytes
  • Recording Date and Time: 2023-07-08 12:00:00
  • File Flags: 0 (regular file)
  • File Unit Size: 0
  • Interleave Gap Size: 0
  • Volume Sequence Number: 1
  • Length of File Identifier: 11
  • File Identifier: "example.txt"

The byte-level representation might look like this:

+-----------------------------------------------+
| Length of Directory Record (1 byte)           |
| Extended Attribute Record Length (1 byte)     |
| Location of Extent (8 bytes)                  |
| Data Length (8 bytes)                         |
| Recording Date and Time (7 bytes)             |
| File Flags (1 byte)                           |
| File Unit Size (1 byte)                       |
| Interleave Gap Size (1 byte)                  |
| Volume Sequence Number (4 bytes)              |
| Length of File Identifier (1 byte)            |
| File Identifier (11 bytes)                    |
| Padding (1 byte)                              |
+-----------------------------------------------+

Directory Entry Fields

  • Length of Directory Record: 0x22 (34 in decimal)
  • Extended Attribute Record Length: 0x00
  • Location of Extent: 0x64 (100 in decimal)
    • LSB (4 byte): 0x64 00 00 00, because the first byte address is lower.
    • MSB (4 byte): 0x00 00 00 64
  • Data Length: 0x00 08 00 00 00 00 00 00 (2048 in decimal)
  • Recording Date and Time: 0x17 07 08 0C 00 00 00 (2023-07-08 12:00:00)
  • File Flags: 0x00(regular file)
  • File Unit Size: 0x00
  • Interleave Gap Size: 0x00
  • Volume Sequence Number: 0x01 00 00 00
  • Length of File Identifier: 0x0B (11 in decimal)
  • File Identifier: "example.txt"
  • Padding: 0x00
+--------------------------------------------+
| Length of Directory Record (1 byte)        | 0x22 (34)
| Extended Attribute Record Length (1 byte)  | 0x00
| Location of Extent (8 bytes)               | 0x64 00 00 00 00 00 00 64
| Data Length (8 bytes)                      | 0x00 08 00 00 00 00 00 00
| Recording Date and Time (7 bytes)          | 0x17 07 08 0C 00 00 00
| File Flags (1 byte)                        | 0x00
| File Unit Size (1 byte)                    | 0x00
| Interleave Gap Size (1 byte)               | 0x00
| Volume Sequence Number (4 bytes)           | 0x01 00 00 00
| Length of File Identifier (1 byte)         | 0x0B (11)
| File Identifier (11 bytes)                 | "example.txt"
| Padding (1 byte)                           | 0x00
+--------------------------------------------+

5 File Data:

  • The actual content of the files is stored here, following the directory entries.
  • Location: The file data itself comes after the directory records and is located based on the extent information provided in those directory records.
  • The actual content of the files, stored in contiguous extents.
image-204.png

Reading and Identifying Volume Descriptors:

To identify the Volume Descriptors, you would typically scan the volume descriptors starting from LBA 16 and look for their type code.

-: Algorithm :-

1 Initialize:

  • Start reading from LBA 16.
  • Prepare a buffer to store the data read from each sector. Mostly the memory address.

2 Loop through Volume Descriptors:

  • Read the sector at the current LBA.
  • Each sector is 2048 bytes in the ISO.
  • Identify the type of volume descriptor based on the type code at the beginning of the sector.
    • The type code is the very first byte of the volume descriptor.
  • If the volume descriptor is identified, process it accordingly.
    • If type code is 0, it's a Boot Volume Descriptor (BVD).
    • If type code is 1, it's a Primary Volume Descriptor (PVD).
    • If type code is 2, it's a Supplementary Volume Descriptor (SVD).
    • If type code is 3, it's a Volume Partition Descriptor (VPD).
    • If type code is 255, it's a Volume Descriptor Set Terminator (VDST).
    • If any other type code, it's an unknown volume descriptor.
  • Move to the next LBA.

3 Stop Condition:

  • Increment the LBA to move to the next sector.
  • Repeat the loop until the Volume Descriptor Set Terminator is found.
    • If the Volume Descriptor Set Terminator (type code 255) is encountered, stop the loop.

Pseudocode:

LBA = 16
Buffer = allocate 2048 bytes

while true:
    ReadSector(LBA, Buffer)
    TypeCode = Buffer[0]

    if TypeCode == 0:
        print "Boot Volume Descriptor (BVD) found"
        // Process BVD

    elif TypeCode == 1:
        print "Primary Volume Descriptor (PVD) found"
        // Process PVD

    elif TypeCode == 2:
        print "Supplementary Volume Descriptor (SVD) found"
        // Process SVD

    elif TypeCode == 3:
        print "Volume Partition Descriptor (VPD) found"
        // Process VPD

    elif TypeCode == 255:
        print "Volume Descriptor Set Terminator (VDST) found"
        break

    else:
        print "Unknown Volume Descriptor found"

    LBA += 1

Disk Read Function:

We will be using extended read function since the traditional int 0x13, ah = 0x02 reads the sector of size 512 bytes, However in the ISO 9660 the sector size is 2048 bytes (2 KB). This function assumes sector to be 512 byte long.

The extended BIOS disk function int 0x13, ah = 42h do not make any assumption that a sector size is fixed at 512 bytes. On any kind of drive that supports extended disk BIOS services you can query the drive parameters to determine the disk sector size.

; **************************
; BIOS ReadFromDiskUsingExtendedBIOSFunction
; IN:
; 	- ES:BX: Buffer
;	- EAX: Sector start low-dword
;	- ESI: Sector start high-dword
; 	- ECX: Sector count
; 	- EDX: Sector size in bytes
;
; Registers:
; 	- Conserves all but ES:BX
; **************************
ReadFromDiskUsingExtendedBIOSFunction:
	pushad

	; Set initial 
	mov 	word [DiskPackage.Segment], es
	mov 	word [DiskPackage.Offset], bx
	mov 	dword [DiskPackage.Sector], eax
	mov	dword [DiskPackage.Sector + 4], esi

	.sLoop:
		; Setup Package
		mov 	word [DiskPackage.SectorsToRead], 1

		; Setup INT
		push 	edx
		mov 	al, 0
		mov 	ah, 0x42
		mov 	dl, byte [bPhysicalDriveNum]
		mov 	si, DiskPackage
		int 	0x13

		; It's important we check for offset overflow
		pop 	edx
		mov 	ax, word [DiskPackage.Offset]
		add 	ax, dx
		mov 	word [DiskPackage.Offset], ax
		test 	ax, ax
		jne 	.NoOverflow

	.Overflow:
		; So overflow happened
		add 	word [DiskPackage.Segment], 0x1000
		mov 	word [DiskPackage.Offset], 0x0000

	.NoOverflow:
		; Loop
		inc 	dword [DiskPackage.Sector]
		loop 	.sLoop

	.End:
	; Restore registers
	popad

	; Save position
	push 	eax
	xor 	eax, eax
	mov 	ax, word [DiskPackage.Segment]
	mov 	es, ax
	mov 	bx, word [DiskPackage.Offset]
	pop 	eax
ret

; This is used for the extended read function (int 0x13)
DiskPackage:            db 0x10
		                db 0
	.SectorsToRead      dw 0
	.Offset				dw 0
	.Segment 			dw 0
	.Sector 			dq 0

%endif

Read Volume Descriptors:

As we all know that the volume descriptors starts at sector with LBA 16 of the ISO 9660. The sectors 0-15 are reserved for system.

Since Volume Descriptors starts at sector 16 and all the volume descriptors are at consecutive sectors. For example: If at sector 16 is Primary Volume Descriptor then at sector 17 would be the next Volume Descriptor. However there location is not fixed that is there is no guarantee that Primary Volume Descriptor is at sector 16. Descriptors could be in random order. But their starting sector is fixed which is 16 LBA (Logical Block Address) and Volume Descriptor Terminator marks the end of the list, means it is always located at the end of the list and indicates that list ends here.

  • Start from Sector 16
    • Read the sector at location 0x8000.
    • Verify the ISO disk Identifier CD001, which is present at offset 1 of every volume descriptor and is of size 5 bytes.
    • Do your work the Volume Descriptor like:
      • Print the Volume Descriptor Type, which is at offset 0 of the Volume Descriptor.
    • Check if it is the Volume Descriptor Terminator, whose Type is 0xFF.
      • If it is, then we have parsed all the volume descriptor and we are done.
      • If it is not then, read the next sector for the next volume descriptor and read all steps.
%define VOLUME_DESCRIPTOR_READ_LOCATION 0x8000

Read_volume_descriptors:
	pusha	; save the state
.iterate_descriptors:
	xor eax, eax	; clear out the eax
	mov esi, eax	; clear out the esi
	mov ax, 0x0000
	mov es, ax		; set es to 0x0000
	mov bx, VOLUME_DESCRIPTOR_READ_LOCATION
     
	mov eax, [dwVolumeDescriptorStartingSector]	; starting sector low 32 bit (0-indexed LBA)
	mov esi, 0		; starting sector high 32 bit
     
	mov ecx, 1		; number of sectors to read
	mov edx, 2048	; Sector sizes in bytes (1 sector = 2048 in ISO 9660)
	call ReadFromDiskUsingExtendedBIOSFunction
	
	;; Now at location VOLUME_DESCRIPTOR_READ_LOCATION, we have read the sector.
	
	; Verify the PVD identifier 'CD001',
	; which would be at offset 1 of Volume Descriptor structure.
	mov si, VOLUME_DESCRIPTOR_READ_LOCATION + 1
	mov cx, 5		; 5 characters to check
	mov di, iso_id
	repe cmpsb
	jne .invalid_iso_disk
     
	;; We got the valid iso disk identifier.
	mov byte al, [VOLUME_DESCRIPTOR_READ_LOCATION]
    
	; Print the Volume Descriptor Type
	mov si, sVolumeDescriptorTypeStatement
    call PrintString16BIOS
    call PrintWordNumber
    call PrintNewline
    
    ;; do your logic to read particular descriptor.
    
    jmp .read_next_volume_descriptor
    
    .read_next_volume_descriptor:
    cmp al, 0xFF	; Check for the end of volume descriptor list,
    				; which is Volume Descriptor Set Terminator and whose type (first byte) is 0xFF.
    je .volume_descriptor_terminator
    
    ;; If not end of the list, load next volume descriptor from the next sector.
    add dword [dwVolumeDescriptorStartingSector], 1 ; read next sector i.e next volume descriptor.
    jmp .iterate_descriptor

	;; Volume Descriptor Set Terminator
	.volume_descriptor_terminator:
		mov si, volume_descriptor_terminator_encountered
		call PrintString16BIOS

.done_reading_volume_terminator:
	popa	; restore the state
ret

.invalid_iso_disk:
    ;; We got the Invalid ISO DISK
    ; Print the invalid statement and go to infinite loop
    mov si, invalid_iso_disk
    call PrintString16BIOS
    call PrintNewline
jmp $

dwVolumeDescriptorStartingSector dd 16	;; starting sector where the volume descriptor resides.

invalid_iso_disk: db 'Invalid ISO disk identifier.', 0
volume_descriptor_terminator_encountered: db 'Volume Descriptor Set Terminator Encountered.', 0
sVolumeDescriptorTypeStatement: db 'Volume Descriptor Type = ', 0

Code Explanation:

  • Start: The program begins execution.
  • Save the state: The pusha instruction saves the state of the registers.
  • Initialize Registers: The code clears the eax and esi registers and sets es to 0x0000.
  • Set Read Location: Sets bx to the location where the volume descriptor will be read.
  • Read Volume Descriptor Sector: Reads the sector starting from dwVolumeDescriptorStartingSector.
  • Verify PVD Identifier: Compares the identifier at the specified offset with 'CD001'.
  • Invalid ISO Disk Identifier: If the identifier does not match, it jumps to print an invalid identifier message.
  • Valid ISO Disk Identifier: If the identifier matches, it prints the volume descriptor type.
  • Print Volume Descriptor Type: Prints the volume descriptor type.
  • Read Next Volume Descriptor: Checks if the current descriptor is the last one (type 0xFF), otherwise reads the next descriptor.
  • Volume Descriptor Terminator: If the type is 0xFF, prints the terminator message.
  • Restore the state: The popa instruction restores the state of the registers.
  • End: The program terminates.

Accessing Root Directory

To access the root directory of an ISO 9660 filesystem, you need to follow these steps:

1 Locate the Primary Volume Descriptor (PVD):

  • The PVD contains information about the structure of the filesystem, including the location of the root directory.
  • It is typically located at a predefined LBA, often LBA 16 in many ISO 9660 filesystems.
  • We have already Read and Identified Volume Descriptor in above section.
ISO 9660 Volume
+-----------------------+
| Sector 0              |
| Sector 1              |
| ...                   |
| Sector 16             | <-- Primary Volume Descriptor
|                       |  (PVD) starts here (offset
| ...                   |  32768 bytes)
+-----------------------+

2 Read the Primary Volume Descriptor (PVD):

  • The PVD is located at Logical Block Address (LBA) 16.
  • Read the sector at LBA 16 to get the PVD.
Primary Volume Descriptor (PVD) Structure (2048 bytes)
+-----------------------+
| ...                   |
| Root Directory Entry  | <-- Starts at offset 156 
| (34 bytes)            |     within the PVD
| ...                   |
+-----------------------+

3 Parse the PVD to extract the root directory entry.

  • The PVD contains a root directory entry that describes the location and size of the root directory.
  • The root directory entry starts at byte offset 156 in the PVD.
  • Below is the Root Directory Entry Structure that is available at offset 156 in the PVD.
Root Directory Entry Structure (34 bytes)
+-------------------------------+
| Length of Directory Record    | (1 byte)
+-------------------------------+
| Extended Attribute Record Len | (1 byte)
+-------------------------------+
| Location of Extent            | (4 bytes, little-endian)
+-------------------------------+
| Data Length                   | (4 bytes, little-endian)
+-------------------------------+
| Recording Date and Time       | (7 bytes)
+-------------------------------+
| File Flags                    | (1 byte)
+-------------------------------+
| File Unit Size                | (1 byte)
+-------------------------------+
| Interleave Gap Size           | (1 byte)
+-------------------------------+
| Volume Sequence Number        | (2 bytes, little-endian)
+-------------------------------+
| Length of File Identifier     | (1 byte)
+-------------------------------+
| File Identifier               | (variable length)
+-------------------------------+

 

4 Extract Root Directory Information from Root Directory Entry Structure:

  1. The root directory entry structure provides:
    • Location of Extent:
      • The Logical Block Address (LBA) of the root directory which is at offset 2  in Root Directory Entry structure and at offset 158 in PVD. It is of 4 bytes in size.
      • It points to the sector where the root directory data begins. This sector contains directory entries for all files and subdirectories within the root directory.
    • Data Length:
      • The length of the root directory entry is at offset 0 in the Root Directory Entry Structure and at offset 156 in the PVD itself.
      • It specifies about the length of the Directory Record. It is of 1 bytes in size.
  2. Read the Directory Entries from the location specified in the PVD.
    • Use the LBA and size from the root directory entry to read the directory entries.
    • Each directory entry contains information about files and subdirectories.
    • Traverse the Root Directory:
      • Read and parse directory entries sequentially from the starting sector of the root directory.
      • Each directory entry provides information such as:
        • Length of Directory Record: Specifies the length of the directory entry.
        • Location of Extent: Indicates where the file or subdirectory starts on the disk.
        • Data Length: Specifies the size of the file or subdirectory.
        • File Identifier Length: Length of the File Identifier(name).
        • File Identifier: Provides the name of the file or subdirectory.
Let's visualize the structure of an ISO 9660
filesystem containing the following files and
directories:
Root Directory
         |---- file1
         |---- dir1
         |      |
         |      ---- file2
         |---- dir2

Primary Volume Descriptor (PVD)
+--------------------------------+
| ...                            |
| Byte 156: Root Directory Entry |
|               starts           |
| +----------------------------+ |
| | Length of Directory Record:| |<-- Byte 156
| |      34 bytes              | |
| |----------------------------| |
| | Extended Attribute Record  | |<-- Byte 157
| |  Length: 0 	               | |
| |----------------------------| |
| | Location of Extent: 20(LBA)| |<-- Bytes 158-161
| | ex: (root directory starts | |
| |   at sector 24)            | |
| |----------------------------| |
| | Data Length: 2048 bytes    | |<-- Bytes 166-169
| | (length of root directory  | |
| |   extent)                  | |
| |----------------------------| |
| | ...                        | |
+--------------------------------+

LBA 20: Root Directory (Length 2048 bytes)
| +--------------------------------------+ |
| Directory Entry for itself               |<- Entry 1
| +--------------------------------------+ |
| | Length of Directory Record: 34 bytes | |
| | Extended Attribute Record Length: 0  | |
| | Location of Extent: 20 (LBA)         | |
| |                                      | |
| | Data Length: 1024 bytes              | |
| | Recording Date and Time: ...         | |
| | File Flags: 0x02 (directory)         | |
| | File Identifier: "."                 | |
+------------------------------------------+
| Directory Entry for file1                |<- Entry 2
| +--------------------------------------+ |
| | Length of Directory Record: 34 bytes | |
| | Extended Attribute Record Length: 0  | |
| | Location of Extent: 21 (LBA)         | |
| |      (file 1 starts at sector 30)    | |
| | Data Length: 1024 bytes              | |
| | Recording Date and Time: ...         | |
| | File Flags: 0x00 (regular file)      | |
| | File Identifier: "file1"             | |
| +--------------------------------------+ |
| Directory Entry for dir1                 |<- Entry 3
| +--------------------------------------+ |
| | Length of Directory Record: 34 bytes | |
| | Extended Attribute Record Length: 0  | |
| | Location of Extent: 22 (LBA)         | |
| |      (dir1 starts at sector 50)      | |
| | Data Length: 2048 bytes              | |
| | Recording Date and Time: ...         | |
| | File Flags: 0x02 (directory)         | |
| | File Identifier: "dir1"              | |
| +--------------------------------------+ |
| Directory Entry for dir2                 |<- Entry 4
| +--------------------------------------+ |
| | Length of Directory Record: 34 bytes | |
| | Extended Attribute Record Length: 0  | |
| | Location of Extent: 23 (LBA)         | |
| |      (dir2 starts at sector 70)      | |
| | Data Length: 2048 bytes              | |
| | Recording Date and Time: ...         | |
| | File Flags: 0x02 (directory)         | |
| | File Identifier: "dir2"              | |
| +--------------------------------------+ |
| ...                                      |
+------------------------------------------+

LBA 22: dir1 Directory (2048 bytes)
+------------------------------------------+
| Directory Entry for dir1  (Directory)    |<- Entry 1
| +--------------------------------------+ |
| | Length of Directory Record: 34 bytes | |
| | Extended Attribute Record Length: 0  | |
| | Location of Extent: 22 (LBA)         | |
| |      (dir1 starts at LBA sector 22)  | |
| | Data Length: 2048 bytes              | |
| | Recording Date and Time: ...         | |
| | File Flags: 0x02 (directory)         | |
| | File Identifier: "dir1"              | |
+------------------------------------------+
| Directory Entry for file2                |<- Entry 2
| +--------------------------------------+ |
| | Length of Directory Record: 34 bytes | |
| | Extended Attribute Record Length: 0  | |
| | Location of Extent: 24 (LBA)         | |
| |      (file2 starts at LBA sector 24) | |
| | Data Length: 1024 bytes              | |
| | Recording Date and Time: ...         | |
| | File Flags: 0x00 (regular file)      | |
| | File Identifier: "file2"             | |
| +--------------------------------------+ |
| ...                                      |
+------------------------------------------+

LBA 23
+------------------------------------------+
| Directory Entry for dir2 Entries         |<- Entry 2
| +--------------------------------------+ |
| | Length of Directory Record: 34 bytes | |
| | Extended Attribute Record Length: 0  | |
| | Location of Extent: 23 (LBA)         | |
| |      (dir2 starts at LBA sector 23)  | |
| | Data Length: 2048 bytes              | |
| | Recording Date and Time: ...         | |
| | File Flags: 0x02 (directory)         | |
| | File Identifier: "dir2"              | |
| +--------------------------------------+ |
| ...                                      |
+------------------------------------------+

|--------------------------|
| Root Directory Entries(s)|
|--------------------------|
| Entry 1                  |
| Length: 34               |
| Location: 1000           |
| Data Length: 2048        |<--(1 sector)
| File Flags: 00 (File)    |
| File Identifier: FILE1   |
|--------------------------|
| Entry 2                  |
| Length: 34               |
| Location: 1050           |
| Data Length: 4096        |
| File Flags: 00 (File)    |
| File Identifier: FILE2   |
|--------------------------|
| Entry 3                  |
| Length: 34               |
| Location: 1100           |
| Data Length: 1024        |
| File Flags: 02(Directory)|
| File Identifier: DIR1    |
|--------------------------|
.
.
.
|--------------------------|
| Entry 4                  |
| Length: 0(End of Entries)|
|--------------------------|
       |
       V

Code for Printing Root Directory Entries:

%define VOLUME_DESCRIPTOR_READ_LOCATION 0x8000

Read_volume_descriptors:
	pusha	; save the state
.iterate_descriptors:
	xor eax, eax	; clear out the eax
	mov esi, eax	; clear out the esi
	mov ax, 0x0000
	mov es, ax		; set es to 0x0000
	mov bx, VOLUME_DESCRIPTOR_READ_LOCATION
     
	mov eax, [dwVolumeDescriptorStartingSector]	; starting sector low 32 bit (0-indexed LBA)
	mov esi, 0		; starting sector high 32 bit
     
	mov ecx, 1		; number of sectors to read
	mov edx, 2048	; Sector sizes in bytes (1 sector = 2048 in ISO 9660)
	call ReadFromDiskUsingExtendedBIOSFunction
	
	;; Now at location VOLUME_DESCRIPTOR_READ_LOCATION, we have read the sector.
	
	; Verify the PVD identifier 'CD001',
	; which would be at offset 1 of Volume Descriptor structure.
	mov si, VOLUME_DESCRIPTOR_READ_LOCATION + 1
	mov cx, 5		; 5 characters to check
	mov di, iso_id
	repe cmpsb
	jne .invalid_iso_disk
     
	;; We got the valid iso disk identifier.
	mov byte al, [VOLUME_DESCRIPTOR_READ_LOCATION]
    
	; Print the Volume Descriptor Type
	mov si, sVolumeDescriptorTypeStatement
    call PrintString16BIOS
    call PrintWordNumber
    call PrintNewline
    
    ;; do your logic to read particular descriptor.
    ; Check if the descriptor is Primary Volume Descriptor
    cmp al, 0x01	; Check for the Primary Volume Descriptor (PVD)
    jne .read_next_volume_descriptor
    
    ;; Got PVD
    ; Print the PVD statement
    mov si, sGotPVDStatement
    call PrintString16BIOS
    call PrintNewline
    
    ;; Read the Root Directory Entry from the PVD,
    ;; which is at offset 156.
    ;; Root Directory Entry is of 34 bytes.
    mov si, 0x809c	; si = 0x8000 + 156
    mov ax, es:[si + 2] ; Logical Block Address of the extent (first 4 bytes)
    xor edx, edx
    mov dx, ax		; Save LBA

    ;; Read the Root Directory Entry at Location 0x9000
    mov ax, 0x0000
    mov es, ax
    mov bx, 0x9000
    
    mov eax, edx	; starting sector low 32 bit (0-indexed LBA)
    mov esi, 0		; starting sector high 32 bit
     
    mov ecx, 1		; number of sectors to read
    mov edx, 2048	; Sector sizes in bytes (1 sector = 2048 in ISO 9660)
    call ReadFromDiskUsingExtendedBIOSFunction
    
    ;; Read Root Directory Entry and Print its Entries Identifier.
    mov si, 0x9000	; Memory Address where the Root Directory Entries (Record)
    			; is read.
    call Read_Root_Directory_Entry
   jmp .done_reading_volume_descriptor	; We are done reading and printing root directory entries.

    
    .read_next_volume_descriptor:
    cmp al, 0xFF	; Check for the end of volume descriptor list,
    				; which is Volume Descriptor Set Terminator and whose type (first byte) is 0xFF.
    je .volume_descriptor_terminator
    
    ;; If not end of the list, load next volume descriptor from the next sector.
    add dword [dwVolumeDescriptorStartingSector], 1 ; read next sector i.e next volume descriptor.
    jmp .iterate_descriptor

	;; Volume Descriptor Set Terminator
	.volume_descriptor_terminator:
		mov si, volume_descriptor_terminator_encountered
		call PrintString16BIOS

.done_reading_volume_terminator:
	popa	; restore the state
ret

.invalid_iso_disk:
    ;; We got the Invalid ISO DISK
    ; Print the invalid statement and go to infinite loop
    mov si, invalid_iso_disk
    call PrintString16BIOS
    call PrintNewline
jmp $


dwVolumeDescriptorStartingSector dd 16	;; starting sector where the volume descriptor resides.

invalid_iso_disk: db 'Invalid ISO disk identifier.', 0
volume_descriptor_terminator_encountered: db 'Volume Descriptor Set Terminator Encountered.', 0
sVolumeDescriptorTypeStatement: db 'Volume Descriptor Type = ', 0
sGotPVDStatement: db 'Got the PVD.', 0
; **************************
; Reads the Root Directory Entry which should be pointed
; by `SI`. It scans all the entries of the root directory
; and prints the identifier(name) of one and all.
; IN:
; 	- SI Root Directory Entry
; **************************
Read_Root_Directory_Entry:
	pusha		; save the state
.iterate_entry:
	xor ax, ax		; Clear out the ax register
	mov byte al, es:[si]	; it points to the 0 offset in directory entry.
				; At Offset 0 is length of the record.
				; It should be non zero, if zero means no valid record.
	test al, al
	
	jz .reading_done
	
	;; It is valid directory record.

	xor cx, cx
	xor dx, dx
	;mov cx, 10
	mov byte cl, es:[si + 32]	; get the file identifier length.
	mov byte dx, cx
	call PrintWordHex
	call PrintNewline
	mov di, si		; di and si both contains the current directory entry.
	add di, 33		; Add 33 to di which is the starting of
				; file identifier of the current directory entry.
.print_entries_identifier_char:
	mov byte al, es:[di]	; read the first character of current directory
				; entry's file identifier.
	call PrintChar16BIOS
	inc di			; jmp to next char in file identifier.
	loop .print_entries_identifier_char	; loop to print next char.
	call PrintNewline	; '\n'
	add byte si, es:[si]	; jump to next directory entry by adding
				; the length of the current directory entry.
	jmp .iterate_entry
	
.reading_done:
	popa 	; restore the state
ret

Root Directory Entries Format:

Each directory entry in the root directory of an ISO 9660 filesystem contains the following fields:

1 Length of Directory Record: 1 byte

  • Specifies the length of the directory record entry in bytes.

2 Extended Attribute Record Length: 1 byte

  • Specifies the length of the extended attribute record (if present).

3 Location of Extent: 4 bytes

  • Specifies the Logical Block Address (LBA) of the extent (file data or directory contents) associated with this entry.

4 Data Length: 4 bytes

  • Specifies the size of the extent in bytes.

5 Recording Date and Time: 7 bytes

  • Provides information about the creation or modification date and time of the extent.

6 File Flags: 1 byte

  • Flags indicating the properties of the file or directory entry, such as whether it's a directory or a file.
  • 00 = File
  • 02 = Directory

7 File Unit Size: 1 byte

  • Specifies the size of the file units in the extent.

8 Interleave Gap Size: 1 byte

  • Specifies the gap size for interleaved files.

9 Volume Sequence Number: 4 bytes

  • Identifies the volume set sequence number for multi-volume sets.

10 File Identifier Length: 1 byte

  • Specifies the length of the file identifier (file name).

11 File Identifier: Variable length (up to 207 bytes)

  • The actual name of the file or directory. It is padded with zeroes to reach the specified length.

Example:

Offset  Field                      Example Value
--------------------------------------------------
0       Length of Directory Record 34
1       Extended Attr Record Length 0
2-5     Location of Extent          00001234h (LBA 4660)
6-9     Data Length                000012CCh (3164 bytes)
10-16   Recording Date and Time    20040330120000 (YYYYMMDDHHMMSS)
17      File Flags                 00 (Normal file)
18      File Unit Size             0
19      Interleave Gap Size        0
20-23   Volume Sequence Number     00000001h
24      File Identifier Length     9
25-33   File Identifier            README.TXT

Breakdown of Fields:

  • Length of Directory Record: 22 (34 in decimal)
    • The length of this directory record is 34 bytes.
  • Extended Attribute Record Length: 00
    • There is no extended attribute record.
  • Location of Extent (LBA): 13 00 00 00
    • The extent (file or directory) starts at LBA 19 (0x13).
  • Data Length: 20 00 00 00
    • The size of the extent is 32 bytes (0x20).
  • Recording Date and Time: 0B 01 17 19 11 02 2A
    • The recording date and time in BCD format:
      • Year: 0B (11 + 1900 = 1911)
      • Month: 01 (January)
      • Day: 17 (23rd)
      • Hour: 19 (25th hour)
      • Minute: 11 (17th minute)
      • Second: 02 (2nd second)
      • Timezone: 2A (unknown format for timezone)
  • File Flags: 00
    • No special flags (not a directory, not an associated file, etc.).
  • File Unit Size: 00
    • No file unit size (not an interleaved file).
  • Interleave Gap Size: 00
    • No interleave gap size.
  • Volume Sequence Number: 01 00
    • This is the first volume.
  • Length of File Identifier: 01
    • The length of the file identifier is 1 byte.
  • File Identifier: 00
    • The file identifier (filename) is a single byte 00, representing the current directory.

To get the Next Entry (Directory or File):

  • We can get the next entry by adding the length of the first entry to the current pointer.
  1. Initialize an index to the start of the directory entries buffer.
  2. Read the length of the current directory entry.
  3. Process the current directory entry.
  4. Move to the next entry by adding the length of the current entry to the index.
  5. Repeat until you reach the end of the buffer.

-: To get the End of the Directory Entries :-

  • Using the Length of Directory Record:
    • Each directory entry has a length specified in its first byte. When you reach a directory entry with a length of 0, you've reached the end of the entries for that directory.
  • Using the Data Length of the Directory:
    • The root directory entry (and any other directory entry) specifies the total size of the directory (in bytes). By tracking the bytes read, you can determine when you've reached the end of the directory entries.
  • Using the Location and Size of Extent:
    • The PVD provides the starting location and size of the root directory. By combining this with the "Length of Directory Record" for each entry, you can determine when you've read all entries.
Root Directory
         |---- file1
         |---- dir1
         |      |
         |      ---- file2
         |---- dir2

Visualization of Directory Entries:

  • Root Directory Entries:
    • Entry 1: Root Directory itself (.)
      • Length: 34 bytes
    • Entry 2: file1.txt
      • Length: 34 bytes
    • Entry 3: dir1/
      • Length: 34 bytes
    • Entry 4: End of directory (could be padded to reach the end of the sector or end with a length of 0).

Step-by-Step Traversal Example:

  • Read PVD at LBA 16:
    • Locate the Primary Volume Descriptor
    • Find the root directory entry at offset 156 in the PVD
  • Extract Root Directory Entry:
    • Location of Extent: LBA 20, for instance
    • Data Length: 2048 bytes
  • Read Root Directory Entries at LBA 20:
    • Start at the beginning of the directory (LBA 20)
    • Read the first entry:
      • Length: 34 bytes
      • File Identifier: "."
    • Move to the next entry (34 bytes from the start of the first entry):
      • Length: 34 bytes
      • File Identifier: "file1.txt"
    • Move to the next entry (another 34 bytes from the start of the second entry):
      • Length: 34 bytes
      • File Identifier: "dir1/"
    • Continue reading until you reach the end of the data length (2048 bytes) or encounter an entry length of 0.

Load the File from the Root Directory

As we have already parsed the Root Directory Entries, which contains all the files and directories in the root directory of the ISO 9660 filesystem.

In this section, we will load a simple text file from the root directory and try to print its text content.

Step to Load a file from the Root Directory of an ISO 9660 file system:

  1. Read the Primary Volume Descriptor (PVD):
    1. The PVD which is typically located at sector 16 (LBA 16) of the ISO image. This descriptor contains essential information, including the location of the root directory.
  2. Locate the Root Directory:
    1. From the PVD, find the Root Directory Record (Root Directory Entry). This record will provide the starting location (LBA) and size of the root directory.
  3. Read the Root Directory:
    1. Read the sectors that constitute the root directory. Each directory record in the root directory corresponds to a file or subdirectory.
  4. Find the File Record for the text file:
    1. Parse the directories entries to find the file record for the required text file to be read. The directory entry will provide the location (LBA) and size of the file.
  5. Load the File Data:
    1. Using the location and size from the directory entry, read the file's data sectors.

🧾 Scanning for a File in Root Directory

In order to scan for the file from the root directory, we need to first read the Primary Volume Descriptor from the LBA 16 of the ISO image which should have the sector size of 2KB (2048 bytes). We can read this Primary Volume Descriptor at some memory location/ buffer or in a structure. This PVD contains metadata about the volume, including the location of the root directory record.

At offset 156 in the PVD, we have the Root Directory Record, This record contains the location of extent (starting LBA of the root directory) and size (data length) of the root directory.

The directory record is a structured data block that includes metadata about files and directories.

With the starting block and size of the root directory known, we can read the entire root directory into a buffer. The root directory is essentially a list of directory records, each representing a file or subdirectory.

Now all we need to do is to iterate through all the directory records from the buffer, examining each one to see if it matches the desired filename.

Each directory record contains the length of the record, the name of the file or directory, and its starting block and size.

In order to move to the next directory record, we add the length of the current record to the current directory record.

Step-by-Step Guide

  1. Read the Primary Volume Descriptor (PVD) starting at LBA 16.
    1. Read the PVD from sector 16 of the ISO image (2048 bytes).
    2. Parse the PVD to retrieve necessary information.
  2. Locate the Root Directory Record from the PVD.
    1. From the PVD structure, extract the root directory record, which is at offset 156.
    2. Retrieve the extent location (starting block) and data length (size) of the root directory.
  3. Read the Root Directory's extent to find directory entries.
    1. Read the root directory into a buffer or memory.
  4. Iterate through the directory entries to find the desired file.
    1. Initialize a pointer to the start of the buffer.
    2. Loop through the directory records in the buffer:
      1. Check if the length of the record is 0, indicating the end of the directory.
      2. Extract the name from the directory record.
      3. Compare the name with the desired filename.
      4. If the file is found, retrieve its extentLocation (starting block) and dataLength (size).
      5. Break the loop if the file is found.
      6. Move the pointer to the next directory record.
Algorithm ScanForFileISO9660(desiredFileName)
    // Step 1: Initialize Variables
    Define structure PVD (Primary Volume Descriptor)
    Define structure DirectoryRecord
    Set SECTOR_SIZE = 2048

    // Step 2: Read the Primary Volume Descriptor (PVD)
    buffer = ReadSector(16, SECTOR_SIZE)
    pvd = ParsePVD(buffer)

    // Step 3: Extract Root Directory Record from PVD
    rootExtent = pvd.rootDirectoryRecord.extentLocation
    rootSize = pvd.rootDirectoryRecord.dataLength

    // Step 4: Read the Root Directory
    numSectors = Ceil(rootSize / SECTOR_SIZE)
    rootBuffer = ReadSectors(rootExtent, numSectors * SECTOR_SIZE)

    // Step 5: Scan for the Desired File in Root Directory
    pointer = rootBuffer
    while (pointer < rootBuffer + rootSize)
        record = ParseDirectoryRecord(pointer)
        if (record.length == 0)
            break // End of directory

        name = ExtractName(record)
        if (name == desiredFileName)
            fileStart = record.extentLocation
            fileSize = record.dataLength
            Print("File found at block", fileStart, "with size", fileSize)
            return (fileStart, fileSize)

        // Move to the next record
        pointer += record.length

    Print("File not found")
    return NULL
Diagram 1: ISO9660 Structure with PVD
+-----------------------+ Sector 0
|                       |
|   ISO9660 Boot Code   |
|                       |
+-----------------------+
|                       | Sector 1
|       ...             |
|                       |
+-----------------------+
|                       | Sector 16
|    Primary Volume     |
|     Descriptor        |
|                       |
+-----------------------+
|                       |
|       ...             |
|                       |
+-----------------------+
|                       | Sector n
|                       |
|    Other data         |
|                       |
+-----------------------+


Diagram 2: Structure of PVD
+----------------------------+
| Volume Descriptor Type     |
+----------------------------+
| Standard Identifier        |
+----------------------------+
| Volume Identifier          |
+----------------------------+
| Root Directory Record      |
| (Extent Location, Size)    |
+----------------------------+
| ...                        |
+----------------------------+


Diagram 3: Extracting Root Directory Record from PVD.
PVD Structure
+---------------------------+
| Type                      | 
| Identifier                | 
| Version                   | 
| ...                       | 
| Root Directory Record     | ----
+---------------------------+    |
                                 |
               ------------------
              |
              V
+---------------------------+
| Extent Location (Block 20)| 
+---------------------------+
| Data Length (4096 bytes)  |
+---------------------------+
| ...                       |
+---------------------------+
The root directory record indicates the root directory starts at block 20 and is 4096 bytes in size.


Diagram 4: Reading Sectors of Root Directory
+---------------------------+ Block 20
| Root Directory Data       |
|                           |
+---------------------------+ Block 21
| Root Directory Data       |
|                           |
+---------------------------+

Diagram 5: Buffer with Directory Records
+---------------------------+
| Directory Record 1        |
|                           |
|                           |
+---------------------------+
| Directory Record 2        |
|                           |
|                           |
+---------------------------+
| Directory Record 3        |
|                           |
|                           |
+---------------------------+


Directory Record 1, Sample Example
+---------------------------------+
| Length                          |
| Extent Location                 |
| Data Length                     |
| File Name: "README.TXT"         |
| ...                             |
+---------------------------------+
; **************************
; Reads the Root Directory Entry which should be pointed
; by `SI`. It scans all the entries of the root directory
; and prints the identifier(name) of one and all.
; IN:
; 	- SI Root Directory Entry
; LOCAL VARIABLE:
;	VAR1 = [bp - 4] = Address of the Current Directory entry.
;	VAR2 = [bp - 8] = Found file LBA.
; **************************
Find_and_Load_File_from_Root:
	pusha		; save the state
	push bp		; Save old base pointer
	mov bp, sp	; Set new base pointer
	sub sp, 8	; Allocate 8 bytes for local variables
			; 2 local variables of size 4 bytes each
	mov word [bp - 4], si	; store the address of first entry
				; of Root Directory Entries.

	; Print the function job message
	; SI - is needy for the function so push the initial state into the stack
	; and pop after printing the string. Thus storing the initial value.
	push si
	mov si, sFindAndLoadFileStatement
	call PrintString16BIOS
	call PrintNewline
	pop si

.iterate_entry:

	xor ax, ax		; Clear out the ax register
	mov byte al, es:[si]	; it points to the 0 offset in directory entry.
				; At Offset 0 is length of the record.
				; It should be non zero, if zero means no valid record.
	test al, al
	
	jz .didnt_find		; Reached the end of the directory entry list
				; and didnt find the file
	
	;; It is valid directory record.

	; Get the File Flags field (offset 25 from the start of the entry).
	mov byte al, [si + 25]
	; Check if it's a directory
	test al, 0x02	; Check if bit 1 is set
	jnz .is_directory

	;; It is a file
	
	;; Check it's identifier.
	xor cx, cx	; clear out cx
	xor dx, dx	; clear out dx
	mov byte cl, es:[si + 32]	; get the file identifier length,
					; which is at offset 32 of directory_entry_record.
	mov byte dx, cx			; Store the identifier length
					; into the DX.

	;; Check the file identifier
	mov di, si	; DI and SI points to the Directory Entry Record
	add di, 33	; add offset 33, which is the start of the file identifier
			; DI, now points to the file identifier beginning.
	mov cx, 6	; The length of the file identifier.
	lea si, [file_identifier]	; Load SI to the identifier of the required file.
	repe cmpsb	; Keep checking the SI and DI for the match.
			; if the length of the file identifier in both DI and SI matches,
			; then we have the required file, else jump to the next
			; directory entry record.
	jne .onto_next	; Jump to the next directory record if file identifier of
			; particular size mismatch

	;; Found the file

	mov si, [bp-4]	; Store the current Directory Entry Record in the Local variable.

.got_file:
	;; got the file
	; Print the message claiming that we found the file, you searched for.
	push si
	mov si, sFoundTheFile
	call PrintString16BIOS
	call PrintNewline
	pop si

	;; Location of the extent of the File
	; i.e the LBA of the data of the file.
	mov eax, es:[si + 2] ; Logical Block Address of the extent (first 4 bytes)

	;; Save LBA
	;; Store File LBA in local variable BP - 8
	mov dword [bp - 8], eax		; Store the File LBA
					; to second local variable.

	; Read the file at the FILE_LOADING AREA which is
	; 0x0500
	mov ax, 0x0000
	mov es, ax		; Zero the extra segment.
	mov bx, FILE_LOADING_AREA ; 0xB000

	mov byte ax, [si+ 10]	; Data length of the file in LSB
				; It is present at offset 10 of Directory Entry Record.
				; It is of 4 byte in size
				; and data length is in bytes.
	;; Prints the Data length
	; push si
	; mov si, sLengthOfFile
	; call PrintString16BIOS
	; call PrintWordNumber
	; call PrintNewline
	; pop si

	;; Roundoff the size to the 2048 which is the sector size.
	; i.e if the data length is less than 2048, then sector to load should be
	; 1 which is 2048 bytes
	; If the data length is more than 2048, 2049 for instance, one more than 2048
	; then the sector to load will be 2 (2048*2)bytes.
	; Now eax, consists the rounded off value to sector size
	add eax, 2047
	mov ecx, 2048
	div ecx		; division also affect the edx register.
			; so be careful, if you have stored the necessary
			; information in edx, it must have got wiped out.
	; move the sector count to ecx.
	mov ecx, eax

	mov eax, [bp - 8]	; starting sector low 32 bit (0-indexed LBA)
				; fetch from the second local variable.
	mov esi, 0	; starting sector high 32 bit
	; mov ecx, 1	; Here you can explicitly specify the sector count,
			; Otherwise we already calculated the rounded off
			; sector count to sector size based on the data length.
	mov edx, 2048	; Sector sizes in bytes (1 sector = 2048 in ISO 9660)
	call ReadFromDiskUsingExtendedBIOSFunction


	;; Tell user about displaying the content of the file.
	mov si, sPrintingTheFileContent
	call PrintString16BIOS
	call PrintNewline

	;; Print the contents of the file.
	mov si, FILE_LOADING_AREA	; 0xB000
					; Prints the data from the 
					; file loaded area.
	call PrintString16BIOS
	call PrintNewline

	; We are done here reading the contents of the file.
	jmp .reading_done

.is_directory:
	; handle the directory, for the time being, just jump to next entry.
	; Maybe you can recursively search for the file in every directory.
	; Or you can search for particular directory.

;; Update the si to point to next entry.
.onto_next:
	; load the base address of current entry back to si
	mov word si, [bp - 4]

	add byte si, es:[si]	; increment si to point to next entry
				; Increment by adding the size of current entry.
				; at offset 0 of directory entry, we have the size
				; of each directory entry. we can get to the second
				; entry by adding the size of current entry to the
				; current entry address.
	mov word [bp - 4], si	; update the next entry address in the local
				; variable as well.
	jmp .iterate_entry	; onto the next entry.

;; Sorry but we didnt find your file.
.didnt_find:
	mov si, sDidntFindTheFile
	call PrintString16BIOS
	call PrintNewline

.reading_done:
	add sp, 8	; Clear local variables
	mov sp, bp	; Reset Stack pointer
	pop bp		; Restore old base pointer
	popa 	; restore the state
;; End of the Find_and_Load_File_from_Root function
ret



;; Data for the Find_and_Load_File_from_Root function
sLengthOfFileIdentifierStatement: db 'Length of the File Identifier is (in bytes): ', 0
sLengthOfFile: db 'Length of the File (in bytes): ', 0
sUnpaddedSizeStatement: db 'Un-Padded Size of Bootloader (bytes): ', 0
sFindAndLoadFileStatement: db 'Find and Load the file.', 0
sFoundTheFile: db 'Found the file.', 0
sPrintingTheFileContent: db 'Printing the content of the file.', 0
sDidntFindTheFile: db 'Didnt find the file.', 0
file_identifier: db 'AB.TXT', 0

This function works in conjunction with the previous one with some modifications:

This functions expects that we have read the Root Directory Entry in memory and supply that location in SI register.

Create a Bootable ISO

To make an ISO bootable, the bootloader must be placed in the system area. Here's a step-by-step guide to creating a bootable ISO with a custom bootloader.

1 Prepare the Bootloader Code:

  • Assemble the Stage 1 bootloader to fit within the first 16 sectors (512 bytes).

boot.asm:

BITS 16
ORG 0x7C00

start:
    ; Print a message to indicate bootloader is running
    mov si, msg
print:
    lodsb
    test al, al
    jz hang
    mov ah, 0x0E
    int 0x10
    jmp print

hang:
    cli
    hlt

msg db 'Bootloader started...', 0

TIMES 510-($-$$) db 0
DW 0xAA55
  • Assemble the Bootloader

Use nasm to assemble the bootloader.

nasm -f bin -o boot.bin boot.asm

2 Create the ISO Filesystem:

  • Use a tool like mkisofs to create the ISO image and specify the bootloader.
mkisofs -o bootable.iso -b boot.bin -no-emul-boot -boot-load-size 4 -boot-info-table

Explanation of mkisofs Options

  • -o bootable.iso: Specifies the output file name.
  • -b boot.bin: Specifies the boot image file to be used for making the CD bootable.
  • -no-emul-boot: Indicates that the boot image is a no-emulation El Torito boot image.
  • -boot-load-size 4: Specifies the number of virtual 512-byte sectors to load from the boot image (4 sectors here).
  • -boot-info-table: Adds a boot information table to the boot image.

3 Testing the Bootable ISO

You can use an emulator like QEMU to test the bootable ISO:

qemu-system-x86_64 -cdrom bootable.iso

Complete Source Code:

The complete source code is committed here: https://github.com/The-Jat/The9660Boot/tree/32759d280dade194d333bca8d7c9f8d8017d4980

Utility

A utility to read the ISO 9660 file system.

#define SECTOR_SIZE 2048

// Primary Volume Descriptor (PVD) structure
typedef struct __attribute__((packed)) {
    uint8_t type;                        // Offset 0
    char id[5];                          // Offset 1
    uint8_t version;                     // Offset 6
    uint8_t unused1;                     // Offset 7
    char system_id[32];                  // Offset 8
    char volume_id[32];                  // Offset 40
    uint8_t unused2[8];                  // Offset 72
    uint32_t volume_space_size_le;       // Offset 80 (little-endian)
    uint32_t volume_space_size_be;       // Offset 84 (big-endian)
    uint8_t unused3[32];                 // Offset 88
    uint16_t volume_set_size_le;         // Offset 120 (little-endian)
    uint16_t volume_set_size_be;         // Offset 122 (big-endian)
    uint16_t volume_sequence_number_le;  // Offset 124 (little-endian)
    uint16_t volume_sequence_number_be;  // Offset 126 (big-endian)
    uint16_t logical_block_size_le;      // Offset 128 (little-endian)
    uint16_t logical_block_size_be;      // Offset 130 (big-endian)
    uint32_t path_table_size_le;         // Offset 132 (little-endian)
    uint32_t path_table_size_be;         // Offset 136 (big-endian)
    uint32_t type_l_path_table;          // Offset 140
    uint32_t opt_type_l_path_table;      // Offset 144
    uint32_t type_m_path_table;          // Offset 148
    uint32_t opt_type_m_path_table;      // Offset 152
    uint8_t root_directory_record[34];   // Offset 156
    char volume_set_id[128];             // Offset 190
    char publisher_id[128];              // Offset 318
    char data_preparer_id[128];          // Offset 446
    char application_id[128];            // Offset 574
    char copyright_file_id[37];          // Offset 702
    char abstract_file_id[37];           // Offset 739
    char bibliographic_file_id[37];      // Offset 776
    char creation_date[17];              // Offset 813
    char modification_date[17];          // Offset 830
    char expiration_date[17];            // Offset 847
    char effective_date[17];             // Offset 864
    uint8_t file_structure_version;      // Offset 881
    uint8_t unused4;                     // Offset 882
    uint8_t application_data[512];       // Offset 883
    uint8_t unused5[653];                // Offset 1395
} PVD;

// Directory Record structure
typedef struct __attribute__((packed)) {
    uint8_t length;                      // Offset 0
    uint8_t ext_attr_length;             // Offset 1
    uint32_t extent_location_le;         // Offset 2 (little-endian)
    uint32_t extent_location_be;         // Offset 6 (big-endian)
    uint32_t data_length_le;             // Offset 10 (little-endian)
    uint32_t data_length_be;             // Offset 14 (big-endian)
    uint8_t recording_date[7];           // Offset 18
    uint8_t file_flags;                  // Offset 25
    uint8_t file_unit_size;              // Offset 26
    uint8_t interleave_gap_size;         // Offset 27
    uint16_t volume_sequence_number_le;  // Offset 28 (little-endian)
    uint16_t volume_sequence_number_be;  // Offset 30 (big-endian)
    uint8_t file_id_length;              // Offset 32
    char file_id[];                      // Offset 33
} DirectoryRecord;