2019-10-05 00:37:15 +00:00
|
|
|
#!/usr/bin/python3
|
|
|
|
#
|
|
|
|
# Copyright (C) 2019 Red Hat, Inc.
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
import argparse
|
|
|
|
import logging as log
|
|
|
|
import os
|
|
|
|
import shutil
|
|
|
|
import subprocess
|
|
|
|
import tempfile
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from pylorax.mount import IsoMountpoint
|
|
|
|
from pylorax.treebuilder import udev_escape
|
|
|
|
|
|
|
|
# Constants for efimode (MACBOOT implies EFIBOOT)
|
|
|
|
NO_EFI = 0
|
|
|
|
EFIBOOT = 1
|
|
|
|
MACBOOT = 2
|
|
|
|
|
|
|
|
class Tool():
|
|
|
|
"""A class to check for executables and required files"""
|
|
|
|
tools = []
|
|
|
|
paths = {}
|
|
|
|
requirements = []
|
|
|
|
arches = []
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
# If there are arches listed it must be running on one of them
|
|
|
|
if self.arches and os.uname().machine not in self.arches:
|
|
|
|
log.debug("%s is not supported by this tool", os.uname().machine)
|
|
|
|
raise RuntimeError("%s not supported" % os.uname().machine)
|
|
|
|
|
|
|
|
# Check the system to see if the tools are available and record their paths
|
|
|
|
for e in self.tools:
|
|
|
|
self.paths[e] = shutil.which(e)
|
|
|
|
if not self.paths[e]:
|
|
|
|
log.debug("No executable %s found in PATH", e)
|
|
|
|
raise RuntimeError("Missing executable")
|
|
|
|
|
|
|
|
# Check to make sure all the required files are available
|
|
|
|
for f in self.requirements:
|
|
|
|
if not os.path.exists(f):
|
|
|
|
log.debug("Missing file %s", f)
|
|
|
|
raise RuntimeError("Missing requirement %s" % f)
|
|
|
|
|
|
|
|
|
|
|
|
class MkefibootTool(Tool):
|
|
|
|
"""Create the efiboot.img needed for EFI booting"""
|
|
|
|
tools = ["mkefiboot"]
|
|
|
|
|
|
|
|
def run(self, isodir, tmpdir, product="Fedora"):
|
|
|
|
cmd = ["mkefiboot", "--label=ANACONDA"]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--debug")
|
|
|
|
cmd.extend([os.path.join(tmpdir, "EFI/BOOT"), os.path.join(tmpdir, "images/efiboot.img")])
|
|
|
|
log.debug(" ".join(cmd))
|
|
|
|
try:
|
|
|
|
subprocess.check_output(cmd)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
log.error(str(e))
|
|
|
|
raise RuntimeError("Running mkefiboot")
|
|
|
|
|
|
|
|
|
|
|
|
class MkmacbootTool(MkefibootTool):
|
|
|
|
"""Create the macboot.img needed to EFI boot on Mac hardware"""
|
|
|
|
tools = ["mkefiboot"]
|
|
|
|
# NOTE: Order is important
|
|
|
|
requirements = ["/usr/share/pixmaps/bootloader/fedora.icns",
|
|
|
|
"/usr/share/pixmaps/bootloader/fedora-media.vol"]
|
|
|
|
|
|
|
|
def run(self, isodir, tmpdir, product="Fedora"):
|
|
|
|
cmd = ["mkefiboot", "--label", "ANACONDA",
|
|
|
|
"--apple", "--icon", self.requirements[0],
|
|
|
|
"--diskname", self.requirements[1],
|
|
|
|
"--product", product]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--debug")
|
|
|
|
cmd.extend([os.path.join(tmpdir, "EFI/BOOT"), os.path.join(tmpdir, "images/macboot.img")])
|
|
|
|
log.debug(" ".join(cmd))
|
|
|
|
try:
|
|
|
|
subprocess.check_output(cmd)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
log.error(str(e))
|
|
|
|
raise RuntimeError("Running mkefiboot --apple")
|
|
|
|
|
|
|
|
# images/macboot.img always exists with images/efiboot.img, already in grafts list
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
class MakeISOTool(Tool):
|
|
|
|
"""Class to hold details for specific iso creation tools"""
|
|
|
|
def check_file_sizes(self, grafts):
|
|
|
|
"""Return True any file exceeds 4GiB"""
|
|
|
|
for src, _ in grafts:
|
|
|
|
if os.path.isdir(src):
|
|
|
|
for top, dirs, files in os.walk(src):
|
|
|
|
for f in files + dirs:
|
|
|
|
if os.stat(os.path.join(top,f)).st_size >= 4*1024**3:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
if os.stat(src).st_size >= 4*1024**3:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _exec(self, cmd, grafts, output_iso, efimode=NO_EFI, implantmd5=True):
|
|
|
|
"""Add the grafts and run the command and then implant the md5 checksums"""
|
|
|
|
cmd.append("-graft-points")
|
|
|
|
|
|
|
|
for src, dest in grafts:
|
|
|
|
cmd.append("%s=%s" % (dest, src))
|
|
|
|
|
|
|
|
log.debug(" ".join(cmd))
|
|
|
|
try:
|
|
|
|
subprocess.check_output(cmd)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
log.error(str(e))
|
|
|
|
raise RuntimeError("New ISO creation failed")
|
|
|
|
|
|
|
|
if efimode > NO_EFI:
|
|
|
|
cmd = ["isohybrid", "--uefi"]
|
|
|
|
if efimode == MACBOOT:
|
|
|
|
cmd.append("--mac")
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
|
|
|
cmd.append(output_iso)
|
|
|
|
|
|
|
|
log.debug(" ".join(cmd))
|
|
|
|
try:
|
|
|
|
subprocess.check_output(cmd)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
log.error(str(e))
|
|
|
|
raise RuntimeError("Hybrid ISO creation failed")
|
|
|
|
|
|
|
|
if not implantmd5:
|
|
|
|
return
|
|
|
|
|
|
|
|
cmd = ["implantisomd5", output_iso]
|
|
|
|
log.debug(" ".join(cmd))
|
|
|
|
try:
|
|
|
|
subprocess.check_output(cmd)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
log.error(str(e))
|
|
|
|
raise RuntimeError("implantisomd5 failed")
|
|
|
|
|
|
|
|
|
|
|
|
class Mkisofs_aarch64(MakeISOTool):
|
|
|
|
"""Use the mkisofs tool to create the final iso (aarch64)"""
|
|
|
|
tools = ["mkisofs", "implantisomd5"]
|
|
|
|
arches = ["aarch64", "arm"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
|
|
|
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-joliet-long", "-T"]
|
2019-10-05 00:37:15 +00:00
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
|
|
|
if efimode > NO_EFI:
|
|
|
|
cmd.extend(["-eltorito-alt-boot", "-e", "images/efiboot.img", "-no-emul-boot"])
|
|
|
|
if efimode > EFIBOOT:
|
|
|
|
cmd.extend(["-eltorito-alt-boot", "-e", "images/macboot.img", "-no-emul-boot"])
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.append("-allow-limited-size")
|
|
|
|
|
|
|
|
# Create the iso and implant the md5 checksums
|
|
|
|
self._exec(cmd, grafts, output_iso, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Mkisofs_ppc(MakeISOTool):
|
|
|
|
"""Use the mkisofs tool to create the final iso (ppc)"""
|
2021-12-07 19:19:48 +00:00
|
|
|
tools = ["mkisofs", "implantisomd5"]
|
2019-10-05 00:37:15 +00:00
|
|
|
requirements = ["/usr/share/lorax/templates.d/99-generic/config_files/ppc/mapping"]
|
|
|
|
arches = ["ppc"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
|
|
|
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-joliet-long", "-U", "-T", "-part", "-hfs", "-r", "-l",
|
|
|
|
"-sysid", "PPC", "-chrp-boot", "-no-desktop", "-allow-multidot",
|
2019-10-05 00:37:15 +00:00
|
|
|
"-map", self.requirements[0], "-hfs-bless", "boot/grub/powerpc-ieee1275"]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.append("-allow-limited-size")
|
|
|
|
|
|
|
|
# Create the iso and implant the md5 checksums
|
|
|
|
self._exec(cmd, grafts, output_iso, efimode, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Mkisofs_ppc64le(MakeISOTool):
|
|
|
|
"""Use the mkisofs tool to create the final iso (ppc64le)"""
|
2021-12-07 19:19:48 +00:00
|
|
|
tools = ["mkisofs", "implantisomd5"]
|
2019-10-05 00:37:15 +00:00
|
|
|
requirements = ["/usr/share/lorax/templates.d/99-generic/config_files/ppc/mapping"]
|
|
|
|
arches = ["ppc64le"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
|
|
|
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-joliet-long", "-T", "-part", "-hfs", "-r", "-l", "-sysid",
|
|
|
|
"PPC", "-chrp-boot", "-no-desktop", "-allow-multidot",
|
2019-10-05 00:37:15 +00:00
|
|
|
"-map", self.requirements[0]]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.append("-allow-limited-size")
|
|
|
|
|
|
|
|
# Create the iso and implant the md5 checksums
|
|
|
|
self._exec(cmd, grafts, output_iso, efimode, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Mkisofs_s390(MakeISOTool):
|
|
|
|
"""Use the mkisofs tool to create the final iso (s390)"""
|
2021-12-07 19:19:48 +00:00
|
|
|
tools = ["mkisofs", "implantisomd5"]
|
2019-10-05 00:37:15 +00:00
|
|
|
requirements = []
|
|
|
|
arches = ["s390", "s390x"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
|
|
|
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-joliet-long",
|
2019-10-05 00:37:15 +00:00
|
|
|
"-b", "images/cdboot.img",
|
|
|
|
"-c", "images/boot.cat",
|
|
|
|
"-boot-load-size", "4", "-no-emul-boot"]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.append("-allow-limited-size")
|
|
|
|
|
|
|
|
# Create the iso and implant the md5 checksums
|
|
|
|
self._exec(cmd, grafts, output_iso, efimode, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Mkisofs_x86_64(MakeISOTool):
|
|
|
|
"""Use the mkisofs tool to create the final iso (x86_64)"""
|
2021-12-07 19:19:48 +00:00
|
|
|
tools = ["mkisofs", "isohybrid", "implantisomd5"]
|
2019-10-05 00:37:15 +00:00
|
|
|
requirements = []
|
|
|
|
arches = ["x86_64", "i386"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
|
|
|
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-joliet-long",
|
2019-10-05 00:37:15 +00:00
|
|
|
"-b", "isolinux/isolinux.bin",
|
|
|
|
"-c", "isolinux/boot.cat",
|
|
|
|
"-boot-load-size", "4", "-boot-info-table", "-no-emul-boot"]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
|
|
|
if efimode > NO_EFI:
|
|
|
|
cmd.extend(["-eltorito-alt-boot", "-e", "images/efiboot.img", "-no-emul-boot"])
|
|
|
|
if efimode > EFIBOOT:
|
|
|
|
cmd.extend(["-eltorito-alt-boot", "-e", "images/macboot.img", "-no-emul-boot"])
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.append("-allow-limited-size")
|
|
|
|
|
|
|
|
# Create the iso, implant the md5 checksums, and create hybrid iso
|
|
|
|
self._exec(cmd, grafts, output_iso, efimode, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Xorrisofs_aarch64(MakeISOTool):
|
|
|
|
"""Use the xorrisofs tool to create the final iso (aarch64)"""
|
|
|
|
tools = ["xorrisofs", "implantisomd5"]
|
|
|
|
requirements = []
|
|
|
|
arches = ["aarch64", "arm"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode):
|
|
|
|
cmd = ["xorrisofs", "-o", output_iso,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-R", "-J", "-V", volume_name, "-joliet-long"]
|
2019-10-05 00:37:15 +00:00
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
Fix EFI booting for ISOs generated by `mkksiso`
If the original ISO contains both `efiboot.img` and `macboot.img`,
the current `xorrisofs` invocation does not configure `efiboot.img`
to be bootable, only `macboot.img`.
This fix uses `>=` to compare `efimode` against `EFIBOOT`, since
`MACBOOT` implies `EFIBOOT` as well.
This is similar to the logic for `mkisofs`.
Fixes #1046.
Generated image confirmed to work, when comparing the reports:
`xorriso -indev $PATH_TO_ISO -report_el_torito cmd`
```
❯ diff -u ~/xorriso-report-{orig,new}.log
--- /home/michel/xorriso-report-orig.log 2020-07-15 20:01:01.405289018 -0700
+++ /home/michel/xorriso-report-new.log 2020-07-15 20:01:19.183555423 -0700
@@ -1,6 +1,6 @@
--volid 'Fedora-S-dvd-x86_64-32'
--volume_date uuid '2020042219432600'
--boot_image isolinux system_area=--interval:imported_iso:0s-15s:zero_mbrpt,zero_gpt:'/home/michel/Downloads/Fedora-Server-netinst-x86_64-32-1.6.iso'
+-volid 'Fedora-32-btrfs'
+-volume_date uuid '2020071602410900'
+-boot_image isolinux system_area=--interval:imported_iso:0s-15s:zero_mbrpt,zero_gpt:'/home/michel/src/github/michel-slm/luks-kickstarts/iso/Fedora-32-WS-20200715-3.iso'
-boot_image any partition_cyl_align=on
-boot_image any partition_offset=0
-boot_image any partition_hd_cyl=64
@@ -14,12 +14,6 @@
-boot_image any load_size=2048
-boot_image any boot_info_table=on
-boot_image any next
--boot_image any efi_path='/images/efiboot.img'
--boot_image any platform_id=0xef
--boot_image any emul_type=no_emulation
--boot_image any load_size=11462656
--boot_image isolinux partition_entry=gpt_basdat
--boot_image any next
-boot_image any efi_path='/images/macboot.img'
-boot_image any platform_id=0xef
-boot_image any emul_type=no_emulation
```
```
❯ diff -u ~/xorriso-report-{orig,new5}.log
--- /home/michel/xorriso-report-orig.log 2020-07-15 20:01:01.405289018 -0700
+++ /home/michel/xorriso-report-new5.log 2020-07-15 20:30:27.571644770 -0700
@@ -1,6 +1,6 @@
--volid 'Fedora-S-dvd-x86_64-32'
--volume_date uuid '2020042219432600'
--boot_image isolinux system_area=--interval:imported_iso:0s-15s:zero_mbrpt,zero_gpt:'/home/michel/Downloads/Fedora-Server-netinst-x86_64-32-1.6.iso'
+-volid 'Fedora-32-btrfs'
+-volume_date uuid '2020071603283100'
+-boot_image isolinux system_area=--interval:imported_iso:0s-15s:zero_mbrpt,zero_gpt:'./iso/Fedora-32-WS-20200715-5.iso'
-boot_image any partition_cyl_align=on
-boot_image any partition_offset=0
-boot_image any partition_hd_cyl=64
@@ -17,7 +17,7 @@
-boot_image any efi_path='/images/efiboot.img'
-boot_image any platform_id=0xef
-boot_image any emul_type=no_emulation
--boot_image any load_size=11462656
+-boot_image any load_size=11460608
-boot_image isolinux partition_entry=gpt_basdat
-boot_image any next
-boot_image any efi_path='/images/macboot.img'
```
Signed-off-by: Michel Alexandre Salim <michel@michel-slm.name>
2020-07-16 03:37:01 +00:00
|
|
|
if efimode >= EFIBOOT:
|
2019-10-05 00:37:15 +00:00
|
|
|
cmd.extend(["-eltorito-alt-boot", "-e", "images/efiboot.img", "-no-emul-boot"])
|
|
|
|
if efimode == MACBOOT:
|
|
|
|
cmd.extend(["-eltorito-alt-boot", "-e", "images/macboot.img", "-no-emul-boot"])
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.extend(["-iso-level", "3"])
|
|
|
|
|
|
|
|
# Create the iso and implant the md5 checksums
|
|
|
|
self._exec(cmd, grafts, output_iso, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Xorrisofs_ppc64le(MakeISOTool):
|
|
|
|
"""Use the xorrisofs tool to create the final iso (ppc64)"""
|
|
|
|
tools = ["xorrisofs", "implantisomd5"]
|
|
|
|
requirements = []
|
|
|
|
arches = ["ppc64le"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
|
|
|
cmd = ["xorrisofs", "-o", output_iso,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-R", "-J", "-V", volume_name, "-joliet-long",
|
2019-10-05 00:37:15 +00:00
|
|
|
"-U", "-r", "-l", "-sysid", "PPC",
|
|
|
|
"-A", volume_name, "-chrp-boot"]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.extend(["-iso-level", "3"])
|
|
|
|
|
|
|
|
# Create the iso and implant the md5 checksums
|
|
|
|
self._exec(cmd, grafts, output_iso, efimode=NO_EFI, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Xorrisofs_s390(MakeISOTool):
|
|
|
|
"""Use the xorrisofs tool to create the final iso (s390)"""
|
|
|
|
tools = ["xorrisofs", "implantisomd5"]
|
|
|
|
requirements = []
|
|
|
|
arches = ["s390", "s390x"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
|
|
|
cmd = ["xorrisofs", "-o", output_iso,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-R", "-J", "-V", volume_name, "-joliet-long",
|
2019-10-05 00:37:15 +00:00
|
|
|
"-b", "images/cdboot.img", "-c", "images/boot.cat",
|
|
|
|
"-boot-load-size", "4", "-no-emul-boot"]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.extend(["-iso-level", "3"])
|
|
|
|
|
|
|
|
# Create the iso and implant the md5 checksums
|
|
|
|
self._exec(cmd, grafts, output_iso, efimode=NO_EFI, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Xorrisofs_x86_64(MakeISOTool):
|
|
|
|
"""Use the xorrisofs tool to create the final iso"""
|
|
|
|
tools = ["xorrisofs", "implantisomd5"]
|
|
|
|
requirements = ["/usr/share/syslinux/isohdpfx.bin"]
|
|
|
|
arches = ["x86_64", "i386"]
|
|
|
|
|
|
|
|
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
|
|
|
cmd = ["xorrisofs", "-o", output_iso,
|
2021-12-06 23:48:37 +00:00
|
|
|
"-R", "-J", "-V", volume_name, "-joliet-long",
|
2019-10-05 00:37:15 +00:00
|
|
|
"-isohybrid-mbr", self.requirements[0],
|
|
|
|
"-b", "isolinux/isolinux.bin",
|
|
|
|
"-c", "isolinux/boot.cat",
|
|
|
|
"-boot-load-size", "4", "-boot-info-table", "-no-emul-boot"]
|
|
|
|
if log.root.level < log.INFO:
|
|
|
|
cmd.append("--verbose")
|
Fix EFI booting for ISOs generated by `mkksiso`
If the original ISO contains both `efiboot.img` and `macboot.img`,
the current `xorrisofs` invocation does not configure `efiboot.img`
to be bootable, only `macboot.img`.
This fix uses `>=` to compare `efimode` against `EFIBOOT`, since
`MACBOOT` implies `EFIBOOT` as well.
This is similar to the logic for `mkisofs`.
Fixes #1046.
Generated image confirmed to work, when comparing the reports:
`xorriso -indev $PATH_TO_ISO -report_el_torito cmd`
```
❯ diff -u ~/xorriso-report-{orig,new}.log
--- /home/michel/xorriso-report-orig.log 2020-07-15 20:01:01.405289018 -0700
+++ /home/michel/xorriso-report-new.log 2020-07-15 20:01:19.183555423 -0700
@@ -1,6 +1,6 @@
--volid 'Fedora-S-dvd-x86_64-32'
--volume_date uuid '2020042219432600'
--boot_image isolinux system_area=--interval:imported_iso:0s-15s:zero_mbrpt,zero_gpt:'/home/michel/Downloads/Fedora-Server-netinst-x86_64-32-1.6.iso'
+-volid 'Fedora-32-btrfs'
+-volume_date uuid '2020071602410900'
+-boot_image isolinux system_area=--interval:imported_iso:0s-15s:zero_mbrpt,zero_gpt:'/home/michel/src/github/michel-slm/luks-kickstarts/iso/Fedora-32-WS-20200715-3.iso'
-boot_image any partition_cyl_align=on
-boot_image any partition_offset=0
-boot_image any partition_hd_cyl=64
@@ -14,12 +14,6 @@
-boot_image any load_size=2048
-boot_image any boot_info_table=on
-boot_image any next
--boot_image any efi_path='/images/efiboot.img'
--boot_image any platform_id=0xef
--boot_image any emul_type=no_emulation
--boot_image any load_size=11462656
--boot_image isolinux partition_entry=gpt_basdat
--boot_image any next
-boot_image any efi_path='/images/macboot.img'
-boot_image any platform_id=0xef
-boot_image any emul_type=no_emulation
```
```
❯ diff -u ~/xorriso-report-{orig,new5}.log
--- /home/michel/xorriso-report-orig.log 2020-07-15 20:01:01.405289018 -0700
+++ /home/michel/xorriso-report-new5.log 2020-07-15 20:30:27.571644770 -0700
@@ -1,6 +1,6 @@
--volid 'Fedora-S-dvd-x86_64-32'
--volume_date uuid '2020042219432600'
--boot_image isolinux system_area=--interval:imported_iso:0s-15s:zero_mbrpt,zero_gpt:'/home/michel/Downloads/Fedora-Server-netinst-x86_64-32-1.6.iso'
+-volid 'Fedora-32-btrfs'
+-volume_date uuid '2020071603283100'
+-boot_image isolinux system_area=--interval:imported_iso:0s-15s:zero_mbrpt,zero_gpt:'./iso/Fedora-32-WS-20200715-5.iso'
-boot_image any partition_cyl_align=on
-boot_image any partition_offset=0
-boot_image any partition_hd_cyl=64
@@ -17,7 +17,7 @@
-boot_image any efi_path='/images/efiboot.img'
-boot_image any platform_id=0xef
-boot_image any emul_type=no_emulation
--boot_image any load_size=11462656
+-boot_image any load_size=11460608
-boot_image isolinux partition_entry=gpt_basdat
-boot_image any next
-boot_image any efi_path='/images/macboot.img'
```
Signed-off-by: Michel Alexandre Salim <michel@michel-slm.name>
2020-07-16 03:37:01 +00:00
|
|
|
if efimode >= EFIBOOT:
|
2019-10-05 00:37:15 +00:00
|
|
|
cmd.extend(["-eltorito-alt-boot", "-e", "images/efiboot.img", "-no-emul-boot", "-isohybrid-gpt-basdat"])
|
|
|
|
if efimode == MACBOOT:
|
|
|
|
cmd.extend(["-eltorito-alt-boot", "-e", "images/macboot.img", "-no-emul-boot", "-isohybrid-gpt-hfsplus"])
|
|
|
|
|
|
|
|
if self.check_file_sizes(grafts):
|
|
|
|
cmd.extend(["-iso-level", "3"])
|
|
|
|
|
|
|
|
# Create the iso and implant the md5 checksums
|
|
|
|
self._exec(cmd, grafts, output_iso, efimode=NO_EFI, implantmd5=True)
|
|
|
|
|
|
|
|
|
|
|
|
# xorrisofs based classes
|
|
|
|
XorrisofsTools = [Xorrisofs_aarch64, Xorrisofs_ppc64le, Xorrisofs_s390, Xorrisofs_x86_64]
|
|
|
|
|
|
|
|
# mkisofs based classes
|
|
|
|
MkisofsTools = [Mkisofs_aarch64, Mkisofs_ppc, Mkisofs_ppc64le, Mkisofs_s390, Mkisofs_x86_64]
|
|
|
|
|
|
|
|
|
|
|
|
class MakeKickstartISO():
|
2019-12-03 15:26:30 +00:00
|
|
|
def __init__(self, ks, input_iso, output_iso, add_paths, cmdline="", volid=None):
|
2019-10-05 00:37:15 +00:00
|
|
|
self.ks = ks
|
|
|
|
self.input_iso = input_iso
|
|
|
|
self.output_iso = output_iso
|
|
|
|
self.add_paths = add_paths
|
|
|
|
self.isotool = None
|
|
|
|
self._cmdline = cmdline
|
|
|
|
self.label = ""
|
|
|
|
self.iso = None
|
|
|
|
self.mkmacboot = None
|
|
|
|
self.efimode = NO_EFI
|
2019-11-12 00:31:56 +00:00
|
|
|
self.grafts = []
|
2019-10-05 00:37:15 +00:00
|
|
|
|
|
|
|
errors = False
|
|
|
|
for f in [ks, input_iso] + add_paths:
|
|
|
|
if not os.path.exists(f):
|
|
|
|
log.error("%s is missing", f)
|
|
|
|
errors = True
|
|
|
|
|
|
|
|
if errors:
|
|
|
|
raise RuntimeError("Missing Files")
|
|
|
|
|
|
|
|
# Mount the iso so we can gather information from it
|
|
|
|
try:
|
|
|
|
self.iso = IsoMountpoint(self.input_iso)
|
2019-12-03 15:26:30 +00:00
|
|
|
self.label = self.iso.label if volid is None else volid
|
2021-12-07 18:52:02 +00:00
|
|
|
if not self.label:
|
|
|
|
raise RuntimeError("No volume id found, cannot create iso.")
|
2021-12-07 22:13:11 +00:00
|
|
|
|
|
|
|
# If the iso contains a .discinfo file check the arch against the host's
|
|
|
|
if os.path.exists(os.path.join(self.iso.mount_dir, ".discinfo")):
|
|
|
|
with open(os.path.join(self.iso.mount_dir, ".discinfo")) as f:
|
|
|
|
discinfo = [l.strip() for l in f.readlines()]
|
|
|
|
|
|
|
|
log.info("iso arch = %s", discinfo[2])
|
|
|
|
if os.uname().machine != discinfo[2]:
|
|
|
|
raise RuntimeError("iso arch does not match the host arch.")
|
|
|
|
|
2019-10-05 00:37:15 +00:00
|
|
|
log.info("Volume Id = %s", self.label)
|
|
|
|
|
|
|
|
if os.path.exists(os.path.join(self.iso.mount_dir, "images/efiboot.img")):
|
|
|
|
self.efimode = EFIBOOT
|
|
|
|
self.mkefiboot = MkefibootTool()
|
|
|
|
if os.path.exists(os.path.join(self.iso.mount_dir, "images/macboot.img")):
|
|
|
|
self.efimode = MACBOOT
|
|
|
|
self.mkmacboot = MkmacbootTool()
|
|
|
|
|
|
|
|
for t in XorrisofsTools + MkisofsTools:
|
|
|
|
try:
|
|
|
|
self.isotool = t()
|
|
|
|
break
|
|
|
|
except RuntimeError:
|
|
|
|
continue
|
|
|
|
if not self.isotool:
|
|
|
|
raise RuntimeError("No suitable iso creation tool found.")
|
|
|
|
|
|
|
|
log.info("Using %s to create the new iso", self.isotool.tools[0])
|
|
|
|
except:
|
|
|
|
if self.iso:
|
|
|
|
self.iso.umount()
|
|
|
|
raise
|
|
|
|
|
|
|
|
@property
|
|
|
|
def escaped_label(self):
|
|
|
|
"""Return the escaped iso volume label"""
|
|
|
|
return udev_escape(self.label)
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
if self.iso:
|
|
|
|
self.iso.umount()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def add_args(self,):
|
|
|
|
"""Return the arguments to add to the config file kernel cmdline"""
|
|
|
|
return "inst.ks=hd:LABEL=%s:/%s %s" % (self.escaped_label, os.path.basename(self.ks), self._cmdline)
|
|
|
|
|
|
|
|
def remove_generated_files(self, grafts):
|
|
|
|
"""Remove the files generated by the iso creation tools"""
|
|
|
|
catalog_files = ["boot.cat", "boot.catalog", "TRANS.TBL"]
|
|
|
|
for src, _ in grafts:
|
|
|
|
if not os.path.isdir(src):
|
|
|
|
continue
|
|
|
|
for f in catalog_files:
|
|
|
|
if os.path.exists(os.path.join(src, f)):
|
|
|
|
os.unlink(os.path.join(src, f))
|
|
|
|
|
|
|
|
def run_mkefiboot(self, isodir, tmpdir):
|
2021-12-07 00:13:27 +00:00
|
|
|
if self.efimode == NO_EFI:
|
|
|
|
return
|
|
|
|
|
2019-11-12 00:31:56 +00:00
|
|
|
self.mkefiboot.run(isodir, tmpdir)
|
2019-10-05 00:37:15 +00:00
|
|
|
if self.efimode == MACBOOT:
|
2019-11-12 00:31:56 +00:00
|
|
|
self.mkmacboot.run(isodir, tmpdir)
|
2019-10-05 00:37:15 +00:00
|
|
|
|
|
|
|
def edit_configs(self, isodir, tmpdir):
|
|
|
|
"""Find and edit any configuration files
|
|
|
|
|
|
|
|
Add the inst.ks= argument plus extra cmdline arguments
|
|
|
|
"""
|
|
|
|
# Note that some of these may not exist, depending on the arch being used
|
2019-11-12 00:31:56 +00:00
|
|
|
self._edit_isolinux(isodir, tmpdir)
|
|
|
|
self._edit_efi(isodir, tmpdir)
|
|
|
|
self._edit_ppc(isodir, tmpdir)
|
|
|
|
self._edit_s390(isodir, tmpdir)
|
2019-10-05 00:37:15 +00:00
|
|
|
|
|
|
|
def _edit_isolinux(self, isodir, tmpdir):
|
|
|
|
"""Copy the isolinux.cfg file and add the cmdline args"""
|
|
|
|
|
|
|
|
orig_cfg = os.path.join(isodir, "isolinux/isolinux.cfg")
|
|
|
|
if not os.path.exists(orig_cfg):
|
|
|
|
log.warning("No isolinux/isolinux.cfg file found")
|
|
|
|
return []
|
|
|
|
|
|
|
|
# Edit the config file
|
|
|
|
with open(orig_cfg, "r") as in_fp:
|
|
|
|
with open(os.path.join(tmpdir, "isolinux/isolinux.cfg"), "w") as out_fp:
|
2019-12-03 15:26:30 +00:00
|
|
|
escaped_iso_label = udev_escape(self.iso.label)
|
2019-10-05 00:37:15 +00:00
|
|
|
for line in in_fp:
|
2019-12-03 15:26:30 +00:00
|
|
|
if escaped_iso_label in line:
|
|
|
|
line = line.replace(escaped_iso_label, self.escaped_label)
|
2019-10-05 00:37:15 +00:00
|
|
|
out_fp.write(line.rstrip("\n"))
|
|
|
|
if "append" in line:
|
|
|
|
out_fp.write(" "+self.add_args)
|
|
|
|
out_fp.write("\n")
|
|
|
|
|
|
|
|
def _edit_efi(self, isodir, tmpdir):
|
|
|
|
"""Copy the efi config files and add the cmdline args"""
|
|
|
|
# At least one of these must be present
|
|
|
|
efi_cfgs = ["EFI/BOOT/grub.cfg", "EFI/BOOT/BOOT.conf"]
|
|
|
|
|
|
|
|
if not os.path.exists(os.path.join(isodir, "EFI")):
|
|
|
|
log.warning("No EFI directory file found")
|
|
|
|
return []
|
|
|
|
|
|
|
|
found_cfg = False
|
|
|
|
for cfg in efi_cfgs:
|
|
|
|
orig_cfg = os.path.join(isodir, cfg)
|
|
|
|
if not os.path.exists(orig_cfg):
|
|
|
|
continue
|
|
|
|
|
|
|
|
dest_cfg = os.path.join(tmpdir, cfg)
|
|
|
|
with open(orig_cfg, "r") as in_fp:
|
|
|
|
with open(dest_cfg, "w") as out_fp:
|
2019-12-03 15:26:30 +00:00
|
|
|
escaped_iso_label = udev_escape(self.iso.label)
|
2019-10-05 00:37:15 +00:00
|
|
|
for line in in_fp:
|
2019-12-03 15:26:30 +00:00
|
|
|
if escaped_iso_label in line:
|
|
|
|
line = line.replace(escaped_iso_label, self.escaped_label)
|
|
|
|
if line.strip().startswith("search"):
|
|
|
|
line = line.replace(self.iso.label, self.label)
|
2019-10-05 00:37:15 +00:00
|
|
|
out_fp.write(line.rstrip("\n"))
|
|
|
|
# Some start with linux (aarch64), others with linuxefi (x86_64)
|
|
|
|
if line.strip().startswith("linux"):
|
|
|
|
out_fp.write(" "+self.add_args)
|
|
|
|
out_fp.write("\n")
|
|
|
|
found_cfg = True
|
|
|
|
|
|
|
|
if not found_cfg:
|
|
|
|
raise RuntimeError("ISO is missing the EFI config files")
|
|
|
|
|
|
|
|
def _edit_ppc(self, isodir, tmpdir):
|
|
|
|
"""Edit the boot/grub/grub.cfg file, adding the kickstart and extra arguments"""
|
|
|
|
orig_cfg = os.path.join(isodir, "boot/grub/grub.cfg")
|
|
|
|
if not os.path.exists(orig_cfg):
|
|
|
|
log.warning("No boot/grub/grub.cfg file found")
|
|
|
|
return []
|
|
|
|
|
|
|
|
# Edit the config file
|
|
|
|
with open(orig_cfg, "r") as in_fp:
|
|
|
|
with open(os.path.join(tmpdir, "boot/grub/grub.cfg"), "w") as out_fp:
|
2019-12-03 15:26:30 +00:00
|
|
|
escaped_iso_label = udev_escape(self.iso.label)
|
2019-10-05 00:37:15 +00:00
|
|
|
for line in in_fp:
|
2019-12-03 15:26:30 +00:00
|
|
|
if escaped_iso_label in line:
|
|
|
|
line = line.replace(escaped_iso_label, self.escaped_label)
|
2019-10-05 00:37:15 +00:00
|
|
|
out_fp.write(line.rstrip("\n"))
|
|
|
|
if line.strip().startswith("linux "):
|
|
|
|
out_fp.write(" "+self.add_args)
|
|
|
|
out_fp.write("\n")
|
|
|
|
|
|
|
|
def _edit_s390(self, isodir, tmpdir):
|
|
|
|
"""Edit the images/generic.prm file, adding the kickstart and extra arguments"""
|
|
|
|
orig_cfg = os.path.join(isodir, "images/generic.prm")
|
|
|
|
if not os.path.exists(orig_cfg):
|
|
|
|
log.warning("No images/generic.prm file found")
|
|
|
|
return []
|
|
|
|
|
|
|
|
# Append to the config file
|
|
|
|
with open(os.path.join(tmpdir, "images/generic.prm"), "a") as out_fp:
|
|
|
|
out_fp.write(self.add_args+"\n")
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
"""Modify the ISO"""
|
|
|
|
try:
|
|
|
|
# Make a temporary directory to hold modified files
|
|
|
|
with tempfile.TemporaryDirectory(prefix="mkksiso-") as tmpdir:
|
2019-11-12 00:31:56 +00:00
|
|
|
# Copy over the top level directories and populate grafts
|
|
|
|
skip_iso = ["boot.cat", "boot.catalog", "TRANS.TBL"]
|
|
|
|
for f in [f for f in os.listdir(self.iso.mount_dir) if f not in skip_iso]:
|
|
|
|
if os.path.isdir(os.path.join(self.iso.mount_dir, f)):
|
|
|
|
shutil.copytree(os.path.join(self.iso.mount_dir, f), os.path.join(tmpdir, f))
|
|
|
|
else:
|
|
|
|
shutil.copy2(os.path.join(self.iso.mount_dir, f), os.path.join(tmpdir, f))
|
|
|
|
self.grafts.append((os.path.join(tmpdir, f), f))
|
2019-10-05 00:37:15 +00:00
|
|
|
|
|
|
|
# Copy and edit the configuration files
|
2019-11-12 00:31:56 +00:00
|
|
|
self.edit_configs(self.iso.mount_dir, tmpdir)
|
2019-10-05 00:37:15 +00:00
|
|
|
|
|
|
|
# Run the mkefiboot tool on the edited EFI directory, add the new files to the grafts
|
2019-11-12 00:31:56 +00:00
|
|
|
self.run_mkefiboot(self.iso.mount_dir, tmpdir)
|
2019-10-05 00:37:15 +00:00
|
|
|
|
|
|
|
# Add the kickstart to grafts
|
|
|
|
self.grafts.extend([(self.ks, os.path.basename(self.ks))])
|
|
|
|
|
|
|
|
# Add the extra files to grafts
|
|
|
|
self.grafts.extend([(f, os.path.basename(f)) for f in self.add_paths])
|
|
|
|
|
|
|
|
# Remove files that will be regenerated by the iso creation process
|
|
|
|
self.remove_generated_files(self.grafts)
|
|
|
|
|
|
|
|
log.info("grafts = %s", self.grafts)
|
|
|
|
self.isotool.run(tmpdir, self.grafts, self.label, self.output_iso, self.efimode)
|
|
|
|
finally:
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
|
|
|
|
def setup_args():
|
|
|
|
""" Return argparse.Parser object of cmdline."""
|
|
|
|
parser = argparse.ArgumentParser(description="Add a kickstart and files to an iso")
|
|
|
|
|
|
|
|
parser.add_argument("-a", "--add", action="append", dest="add_paths", default=[],
|
|
|
|
type=os.path.abspath,
|
|
|
|
help="File or directory to add to ISO (may be used multiple times)")
|
2021-06-24 23:14:19 +00:00
|
|
|
parser.add_argument("-c", "--cmdline", dest="cmdline", metavar="CMDLINE", default="",
|
2019-10-05 00:37:15 +00:00
|
|
|
help="Arguments to add to kernel cmdline")
|
|
|
|
parser.add_argument("--debug", action="store_const", const=log.DEBUG,
|
|
|
|
dest="loglevel", default=log.INFO,
|
|
|
|
help="print debugging info")
|
|
|
|
|
|
|
|
parser.add_argument("ks", type=os.path.abspath, help="Kickstart to add to the ISO")
|
|
|
|
parser.add_argument("input_iso", type=os.path.abspath, help="ISO to modify")
|
|
|
|
parser.add_argument("output_iso", type=os.path.abspath, help="Full pathname of iso to be created")
|
2019-12-03 15:26:30 +00:00
|
|
|
parser.add_argument("-V", "--volid", dest="volid", help="Set the ISO volume id, defaults to input's", default=None)
|
2019-10-05 00:37:15 +00:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
args = setup_args()
|
|
|
|
log.basicConfig(format='%(levelname)s:%(message)s', level=args.loglevel)
|
|
|
|
|
|
|
|
if os.getuid() != 0:
|
|
|
|
log.error("You must run this as root, it needs to mount the iso and run mkefiboot")
|
2021-12-06 22:56:50 +00:00
|
|
|
return 1
|
2019-10-05 00:37:15 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
app = MakeKickstartISO(args.ks, args.input_iso, args.output_iso,
|
2019-12-03 15:26:30 +00:00
|
|
|
args.add_paths, args.cmdline, args.volid)
|
2019-10-05 00:37:15 +00:00
|
|
|
app.run()
|
|
|
|
except RuntimeError as e:
|
|
|
|
log.error(str(e))
|
2021-12-06 22:56:50 +00:00
|
|
|
return 1
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
2019-10-05 00:37:15 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2021-12-06 22:56:50 +00:00
|
|
|
sys.exit(main())
|