1. Overview

An ISO file is a bit-by-bit copy of the contents of a CD, DVD, or Blu-ray disc. These files are commonly used for archival and distribution purposes, and we can burn them to optical discs or mount them in read-only mode.

In this tutorial, we’ll learn how to create a non-bootable ISO file from a directory in Linux. This process is more complex than simply copying a folder within a Linux filesystem due to the specific limitations of the ISO filesystems, which we’ll explore in detail.

2. ISO Filesystems

ISO9660 is the basic standard for creating ISO images, but it has significant limitations that are too strict for today’s file usage. To overcome these limitations, Joliet and Rock Ridge are extensions to the ISO9660 standard. We can combine both extensions in the same filesystem to ensure maximum compatibility.

In addition, UDF (Universal Disk Format) is a newer and more flexible alternative, especially for larger and more complex file structures. It’s not an extension of ISO9660, but a completely separate filesystem. It’s the only option when we have files larger than 4GB.

This table provides a comparison of the capabilities and limitations of each filesystem:

Feature

Joliet

Rock Ridge

UDF

File Names

64 chars

255 chars

255 chars

Max File Size

4 GB

4 GB

16.000.000 GB

Directory Length

64 chars

255 chars

255 chars

Path Length

240 chars

1024 chars

1024 chars

Character Set

Unicode

Full Unix character set

Unicode

File Permissions

No

Yes

Yes

Owner and Group

No

Yes

Yes

Symbolic Links

No

Yes

Yes

Max Directory Nesting

6 levels

Unlimited
(system dependent)

Unlimited
(system dependent)

Ideal For

Multi-OS scenarios

Unix-like environments
(Linux, macOS, BSD)

Modern media (DVD, Blu-ray)
and modern OS only

However, this table provides only general information. There are different versions of the same standards, and some devices can read one version but not another. For example, some older DVD players can support UDF version 1.02 but not newer versions like 2.50 or 2.60.

If we try to read a disc or ISO image with Joliet and/or Rock Ridge on a system that doesn’t support these extensions, the system falls back to the basic ISO9660 standard, thus preserving access to the files. On the other hand, if we try to read a UDF disc or ISO image on a system that doesn’t support it, we won’t be able to access the files.

3. genisoimage Examples

We’ll use genisoimage, also known as mkisofs, to create ISO images using ISO9660, Joliet, Rock Ridge, and UDF, and test their respective limits.

The following script will help us to test the contents of the ISO images we’re about to create. Basically, it mounts the given ISO file as a loop device and lists all files within it with their full paths relative to the root of the ISO:

#!/bin/bash

ISO_FILE=$1

# Create a temporary directory to mount the ISO
TEMP_DIR=$(mktemp -d)

# Mount the ISO file as read-only
sudo mount -o loop,ro "$ISO_FILE" "$TEMP_DIR"

# List all files in the ISO with their full paths
find "$TEMP_DIR" -type f | sed "s|^$TEMP_DIR/||"

# Unmount the ISO file
sudo umount "$TEMP_DIR"

# Remove the temporary directory
rmdir "$TEMP_DIR"

Let’s save it as list_iso_files.sh and give it execute permissions.

3.1. Challenging the Constraints of Each Filesystem

This Bash script generates files and directories that challenge the constraints of each ISO image filesystem, allowing us to see where each one fails or succeeds:

#!/bin/bash

# Create directories for test files
mkdir -p testfiles-ISO
mkdir -p testfiles-Joliet
mkdir -p testfiles-RockRidge
mkdir -p testfiles-UDF

# Create example files for ISO9660 standard
echo 'This is a test file for ISO9660.' > 'testfiles-ISO/file1.txt'
echo 'Another test file for ISO9660.' > 'testfiles-ISO/file2.txt'

# Create an example file for Joliet with characteristics not supported by ISO9660
echo 'Hello World' > 'testfiles-Joliet/longfilenamewithspecialcharacters_!@#$%^&*().txt'

# Create an example file for Rock Ridge with a 70 character long name not supported by Joliet
long_name='a_very_long_filename_that_is_exactly_seventy_characters_long_12345.txt'
echo 'Hello World' > "testfiles-RockRidge/$long_name"

# Create a 10-level nested directory structure for Rock Ridge not supported by Joliet
mkdir -p 'testfiles-RockRidge/nested/nested/nested/nested/nested/nested/nested/nested/nested/nested'
echo 'Hello World' > 'testfiles-RockRidge/nested/nested/nested/nested/nested/nested/nested/nested/nested/nested/file.txt'

# Create a large (4.5 GiB) file for UDF not supported by either Rock Ridge or Joliet.
fallocate -l 4831838208 'testfiles-UDF/largefile.img'

After running the script, we should see these files:

testing files for ISO images

We’re ready to create our ISO images.

3.2. Creating ISO With ISO9660 Only

This creates an ISO image using the ISO9660 standard:

$ genisoimage -o iso9660_only.iso -input-charset utf-8 testfiles-ISO

Let’s break it down:

  • -o iso9660_only.iso → Specifies the name of the output ISO file
  • -input-charset utf-8 → Input files use the UTF-8 character encoding, which is the standard on Linux
  • testfiles-ISO → Creates an ISO file from the contents of the testfiles-ISO directory

It worked as expected:

$ ./list_iso_files.sh iso9660_only.iso 
file1.txt
file2.txt

Now let’s try to include the testfiles-Joliet directory:

$ genisoimage -o iso9660_only_fail_joliet.iso -input-charset utf-8 testfiles-Joliet

This time the contents of the ISO are different from the input directory:

$ ./list_iso_files.sh iso9660_only_fail_joliet.iso 
longfile.txt

In short, genisoimage renamed longfilenamewithspecialcharacters_!@#$%^&*().txt to longfile.txt without asking us for confirmation and without any warning. That’s why it’s crucial to be aware of the limits of each ISO filesystem.

It’s worth noting that there are three different levels of the ISO9660 format, and genisoimage uses the level 1 by default. Without going into detail, it’s important to remember that if we have files larger than 2GB, we need to use the -iso-level 3 option, as indicated in the Description section of the mkisofs man page. This is also true for Joliet and Rock Ridge.

However, regardless of the ISO9660 level, genisoimage needs to rename files with long names and special characters.

3.3. Creating ISO With Joliet Extension

Let’s try to overcome the limitations we just saw by using the -J flag, which creates Joliet directory entries in addition to the normal ISO9660 filenames:

$ genisoimage -o iso9660_joliet.iso -J -input-charset utf-8 testfiles-Joliet

$ ./list_iso_files.sh iso9660_joliet.iso 
longfilenamewithspecialcharacters_!@#$%^&_().txt

This time the result is much closer to what we want. However, genisoimage needed to replace the asterisk * character in the filename with the underscore _ character because there are restrictions on the characters permitted in Joliet filenames. Here are the disallowed characters:

  • Control characters → codes between 0x00 and 0x1F
  • Asterisk → *
  • Forward slash → /
  • Colon → :
  • Semicolon → ;
  • Question mark → ?
  • Backslash → *\*

Another limitation of Joliet is the number of nested directories. If they are more than six, genisoimage ignores them:

$ genisoimage -o iso9660_joliet_fail_rockridge.iso -J -input-charset utf-8 testfiles-RockRidge
[...]
genisoimage: Directories too deep for
'testfiles-RockRidge/nested/nested/nested/nested/nested/nested/nested' (7)
max is 6; ignored - continuing.
[...]

$ ./list_iso_files.sh iso9660_joliet_fail_rockridge.iso
a_very_long_filename_that_is_exactly_seventy_characters_long_123

Not only did genisoimage not include the file in the nested directories, but it also truncated the name of the file in the root to the first 64 characters allowed by Joliet, removing the extension. This is a very disappointing result.

One positive aspect of Joliet, however, is its cross-platform support. The two sample ISOs created here look the same on macOS 14.5, Windows 11, and Linux Mint 21. In addition, in all three operating systems, mounting ISO files is as simple as double-clicking on them.

3.4. Creating ISO With Rock Ridge Extension

Rock Ridge is primarily intended for Unix-like operating systems such as Linux and recent versions of macOS and overcomes the limitations of Joliet. We can enable it with the -rock (-R) or -rational-rock (-r) flag. Without going into detail, -rational-rock is fine in most cases:

$ genisoimage -o iso9660_with_rockridge_1.iso -rational-rock -input-charset utf-8 testfiles-Joliet
$ ./list_iso_files.sh iso9660_with_rockridge_1.iso 
longfilenamewithspecialcharacters_!@#$%^&*().txt

$ genisoimage -o iso9660_with_rockridge_2.iso -rational-rock -input-charset utf-8 testfiles-RockRidge
$ ./list_iso_files.sh iso9660_with_rockridge_2.iso 
a_very_long_filename_that_is_exactly_seventy_characters_long_12345.txt
nested/nested/nested/nested/nested/nested/nested/nested/nested/nested/file.txt

As these results show, if we want to create ISO images for Linux and macOS, Rock Ridge gives us good results. However, no version of Windows supports Rock Ridge.

3.5. Creating Hybrid ISO

For maximum compatibility, we can combine Joliet and Rock Ridge in the same ISO:

$ genisoimage -o iso9660_hybrid.iso -J -rational-rock -input-charset utf-8 testfiles-RockRidge
$ ./list_iso_files.sh iso9660_hybrid.iso 
a_very_long_filename_that_is_exactly_seventy_characters_long_12345.txt
nested/nested/nested/nested/nested/nested/nested/nested/nested/nested/file.txt

In this case, Linux and macOS 14.5 read Rock Ridge as we’ve seen before. In the case of Windows 11, however, we have a positive surprise, because while there are still some Joliet limitations, such as the 64-character filename constraint, there is no longer the six-nested directory restriction.

3.6. ISO With UDF Filesystem

The UDF filesystem is primarily intended for DVD and Blu-Ray discs and modern operating systems only. It’s the only option if we have files larger than 4GB. We can enable it with the -UDF or -udf flag. Without going into detail, in most cases -udf is fine:

$ genisoimage -o iso9660_with_udf.iso -allow-limited-size -udf -input-charset utf-8 testfiles-UDF
$ ./list_iso_files.sh iso9660_with_udf.iso 
largefile.img

It works, but we have to be careful. The -allow-limited-size flag is mandatory when we create an ISO with a UDF filesystem containing files larger than 2GB. The man page warns us that we really need to be sure that the ISO file will be read with UDF instead of ISO9660, otherwise, we’ll get an inconsistent filesystem.

4. Conclusion

In this article, we explored how to create non-bootable ISO files from directories in Linux. We began by understanding ISO filesystems, including ISO9660 and its extensions, Joliet and Rock Ridge, and the UDF filesystem. Each has unique strengths and limitations, especially in terms of file and directory naming conventions, maximum file sizes, and cross-platform compatibility.

We then demonstrated how to use the genisoimage tool to create ISO files. We tested different configurations, emphasizing the importance of selecting the appropriate filesystem for specific use cases. By examining the limitations of ISO9660, Joliet, Rock Ridge, and UDF, we gained insight into their practical applications.

Rock Ridge is ideal for Unix-like systems, Joliet offers cross-platform support, and UDF is essential for large files. Combining Joliet and Rock Ridge can provide broad compatibility, ensuring that our ISO files are versatile and functional in different environments.