clalancette / pycdlib

Python library to read and write ISOs
GNU Lesser General Public License v2.1
150 stars 38 forks source link

Error Creating El Torito ISO #57

Closed EGuthrieWasTaken closed 1 year ago

EGuthrieWasTaken commented 3 years ago

Hey clalancette, I am having an issue creating an El Torito ISO from a folder. Namely, I am attempting to generate a bootable OpenCore ISO using a release from the acidanthera/OpenCorePkg repository. However, when I execute the code I am met with the could not find path error. You can reproduce the error by running the following code:

gh repo clone EGuthrieWasTaken/macOS-KVM
cd macOS-KVM
pip3 install -r requirements.txt
python3 download_opencore.py

This command will pull the latest version of OpenCore and generate an ISO with its contents. You can see the contents after they are placed in the bootloader directory, and you can view download_opencore.py to see what I did to produce this error. Note that the error is in the function make_bootable_iso(), but it is called by download_opencore().

clalancette commented 3 years ago

The issue here is that you always have to make the directory before adding a file to it. In this case, you haven't yet created the /BOOT directory, so you can't add files to it yet.

Admittedly, the error message could be better in that case. I'll leave this open to track improving the error message when the directory doesn't exist.

EGuthrieWasTaken commented 3 years ago

Thank you for the reply, but I'm afraid I still don't quite understand. Take a look at the relevant section of code:

def make_bootable_iso(name, path="./", boot_file='BOOT.efi'):
    """
    Generates a bootable ISO using the folder and boot file specified.

    :param path:        The qualified path to the folder to make the ISO from. Defaults to the
                        working directory.
    :type path:         ``str``
    :param boot_file:   The relative path (relative to ```path```) to the boot file to boot the ISO
                        with. Defaults to ```path```/BOOT.efi
    :type boot_file:    ``str``
    :return:            Nothing.
    :rtype:             ``None``
    """

    # Generate qualified path to boot file.
    if path[-1] == "/":
        path = path + "/"
    if boot_file[0] == "/":
        boot_file = boot_file[1:]

    # Get location of all files within path.
    dir_list = []
    file_list = []
    for root, directories, files in os.walk(path):
        for d in directories:
            dir_list.append(os.path.join(root, d).replace(path[:-1], ''))
        for f in files:
            file_list.append(os.path.join(root, f))
    file_list.sort()
    dir_list.sort()

    # Add all directories and files in folder to ISO.
    iso = PyCdlib()
    iso.new(
        joliet=3,
        vol_ident=name
    )
    for d in dir_list:
        iso.add_directory(joliet_path=d)
    for f in file_list:
        iso.add_file(f, joliet_path=f.replace(path[:-1], ''))

    # Make ISO bootable.
    bootstr = b'boot\n'
    iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT/BOOTx64.efi')
    iso.add_eltorito(
        '/BOOT/BOOTx64.efi',
        efi=True,
        platform_id=2
    )

    # Write and close ISO file.
    iso.write(name + ".iso")
    iso.close()

The section titled "Get location of all files within path" generates 2 lists: the first list contains all directories present in the specified folder path, and the second list contains all files present in the specified folder path. After this, the section of code titled "Add all directories and files in folder to ISO" first creates all directories from path, and second adds all files from path. After this, the El Torito file is added, and finally the file is written.

With all this in mind, my question is what do you mean the directory was not created? If I stop the execution of the program early, I can see that the directory structure is present. How would you modify this section of code?

clalancette commented 3 years ago

Oh, I see. The problem is this line:

        iso.add_directory(joliet_path=d)

This is creating the directory only in Joliet, not in the main ISO9660 part of the disc. You need to do:

iso.add_directory(d, joliet_path=d)

See https://clalancette.github.io/pycdlib/example-creating-joliet-iso.html for more information.

EGuthrieWasTaken commented 3 years ago

I made the edit you suggested, but now I'm met with the error ISO9660 directory names at interchange level 1 cannot exceed 8 characters. I've printed the tree of the folder I'm trying to convert to ISO below:

.
├── BOOT
│   └── BOOTx64.efi
└── OC
    ├── ACPI
    ├── Bootstrap
    │   └── Bootstrap.efi
    ├── Drivers
    │   ├── AudioDxe.efi
    │   ├── CrScreenshotDxe.efi
    │   ├── HiiDatabase.efi
    │   ├── NvmExpressDxe.efi
    │   ├── OpenCanopy.efi
    │   ├── OpenRuntime.efi
    │   ├── OpenUsbKbDxe.efi
    │   ├── Ps2KeyboardDxe.efi
    │   ├── Ps2MouseDxe.efi
    │   ├── UsbMouseDxe.efi
    │   ├── VBoxHfs.efi
    │   └── XhciDxe.efi
    ├── Kexts
    ├── OpenCore.efi
    ├── Resources
    │   ├── Audio
    │   ├── Font
    │   ├── Image
    │   └── Label
    └── Tools
        ├── BootKicker.efi
        ├── ChipTune.efi
        ├── CleanNvram.efi
        ├── GopStop.efi
        ├── HdaCodecDump.efi
        ├── KeyTester.efi
        ├── MmapDump.efi
        ├── OpenControl.efi
        ├── OpenShell.efi
        ├── ResetSystem.efi
        ├── RtcRw.efi
        └── VerifyMsrE2.efi

12 directories, 27 files
clalancette commented 3 years ago

Right, that's because of directories like 'Resources', which is longer than 8 characters. The easiest way to fix that is to create the ISO in interchange level 3, so something like:

    iso.new(
        interchange_level=3,
        joliet=3,
        vol_ident=name
    )
EGuthrieWasTaken commented 3 years ago

Okay. I'm sorry to keep bothering you with new problems, but now it says ISO9660 filenames must consist of characters A-Z, 0-9, and _.

Is it possible that I could use joliet for the files that don't meet the ISO9660 standard, and then both for the few files that do? Is that what I want?

clalancette commented 3 years ago

So ISO9660 filenames have a lot restrictions, as you've found out.

What might make more sense is for you to use one of the "facades"; see https://clalancette.github.io/pycdlib/example-using-facade.html for an example. In this case I'll suggest using the "RockRidge" facade; that will take care of making the filenames that you are used to into RockRidge compliant ones. Note that this will not add Joliet entries to your ISO, but unless you are aiming to use this on Windows that won't really matter.

clalancette commented 1 year ago

There's been no updates to this in 2 years, so I'm going to go ahead and close it out. Feel free to reopen if you are still having problems.