pungi/pungi_utils/patch_iso.py
Adam Williamson f3dcb036a5 Protect against decoding errors with subprocess text mode
All these are calling subprocess in 'text mode', where it will
try to decode stdout/stderr using the default encoding (utf-8
for us). If it doesn't decode, subprocess will raise an exception
and kobo doesn't handle it, it just passes it along to us, so
things blow up - see https://pagure.io/releng/issue/12474 . To
avoid this, let's set `errors="replace"`, which tells the decoder
to replace invalid data with ? characters. This way we should get
as much of the output as can be read, and no crashes.

We also replace `universal_newlines=True` with `text=True` as
the latter is shorter, clearer, and what Python 3 subprocess
wants us to use, it considers `universal_newlines` to just be
a backwards-compatibility thing - "The universal_newlines argument
is equivalent to text and is provided for backwards compatibility"

Signed-off-by: Adam Williamson <awilliam@redhat.com>
Merges: https://pagure.io/pungi/pull-request/1812
(cherry picked from commit 2d16a3af004f61cf41e4eb2e5e694bb46a5d3cda)
2025-09-29 18:27:13 +03:00

142 lines
4.8 KiB
Python

# -*- coding: utf-8 -*-
# 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; version 2 of the License.
#
# 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 Library 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 <https://gnu.org/licenses/>.
from kobo import shortcuts
import os
import productmd
import shlex
import tempfile
from pungi import util
from pungi.phases.buildinstall import tweak_configs
from pungi.wrappers import iso
def sh(log, cmd, *args, **kwargs):
log.info("Running: %s", " ".join(shlex.quote(x) for x in cmd))
ret, out = shortcuts.run(cmd, *args, text=True, errors="replace", **kwargs)
if out:
log.debug("%s", out)
return ret, out
def get_lorax_dir(default="/usr/share/lorax"):
try:
_, out = shortcuts.run(
["python3", "-c" "import pylorax; print(pylorax.find_templates())"],
text=True,
errors="replace",
)
return out.strip()
except Exception:
return default
def as_bool(arg):
if arg == "true":
return True
elif arg == "false":
return False
else:
return arg
def get_arch(log, iso_dir):
di_path = os.path.join(iso_dir, ".discinfo")
if os.path.exists(di_path):
di = productmd.discinfo.DiscInfo()
di.load(di_path)
log.info("Detected bootable ISO for %s (based on .discinfo)", di.arch)
return di.arch
ti_path = os.path.join(iso_dir, ".treeinfo")
if os.path.exists(ti_path):
ti = productmd.treeinfo.TreeInfo()
ti.load(ti_path)
log.info("Detected bootable ISO for %s (based on .treeinfo)", ti.tree.arch)
return ti.tree.arch
# There is no way to tell the architecture of an ISO file without guessing.
# Let's print a warning and continue with assuming unbootable ISO.
log.warning("Failed to detect arch for ISO, assuming unbootable one.")
log.warning("If this is incorrect, use the --force-arch option.")
return None
def run(log, opts):
# mount source iso
log.info("Mounting %s", opts.source)
target = os.path.abspath(opts.target)
with util.temp_dir(prefix="patch-iso-", dir=opts.work_dir) as work_dir:
with iso.mount(opts.source) as source_iso_dir:
util.copy_all(source_iso_dir, work_dir)
# Make everything writable
for root, dirs, files in os.walk(work_dir):
for name in files:
os.chmod(os.path.join(root, name), 0o640)
for name in dirs:
os.chmod(os.path.join(root, name), 0o755)
# volume id is copied from source iso unless --label is specified
volume_id = opts.volume_id or iso.get_volume_id(opts.source)
# create graft points from mounted source iso + overlay dir
graft_points = iso.get_graft_points(work_dir, [work_dir] + opts.dirs)
# if ks.cfg is detected, patch syslinux + grub to use it
if "ks.cfg" in graft_points:
log.info("Adding ks.cfg to boot configs")
tweak_configs(work_dir, volume_id, graft_points["ks.cfg"], logger=log)
arch = opts.force_arch or get_arch(log, work_dir)
with tempfile.NamedTemporaryFile(prefix="graft-points-") as graft_file:
iso.write_graft_points(
graft_file.name, graft_points, exclude=["*/TRANS.TBL", "*/boot.cat"]
)
# make the target iso bootable if source iso is bootable
boot_args = input_charset = None
if arch:
boot_args = iso.get_boot_options(
arch, os.path.join(get_lorax_dir(), "config_files/ppc")
)
input_charset = "utf-8" if "ppc" not in arch else None
# Create the target ISO
mkisofs_cmd = iso.get_mkisofs_cmd(
target,
None,
volid=volume_id,
exclude=["./lost+found"],
graft_points=graft_file.name,
input_charset=input_charset,
boot_args=boot_args,
)
sh(log, mkisofs_cmd, workdir=work_dir)
# isohybrid support
if arch in ["x86_64", "i386"]:
isohybrid_cmd = iso.get_isohybrid_cmd(target, arch)
sh(log, isohybrid_cmd)
supported = as_bool(
opts.supported or iso.get_checkisomd5_data(opts.source)["Supported ISO"]
)
# implantmd5 + supported bit (use the same as on source iso, unless
# overridden by --supported option)
isomd5sum_cmd = iso.get_implantisomd5_cmd(target, supported)
sh(log, isomd5sum_cmd)