#1762 createiso: Recompute .treeinfo checksums for images
Opened 22 days ago by lsedlar. Modified 22 days ago
lsedlar/pungi eltorito-img-checksum  into  master

file modified
+4 -12
@@ -159,15 +159,11 @@ 

  

      script = os.path.join(opts.script_dir, "xorriso-%s.txt" % id(opts))

      with open(script, "w") as f:

-         emit(f, "-indev %s" % opts.boot_iso)

-         emit(f, "-outdev %s" % os.path.join(opts.output_dir, opts.iso_name))

-         emit(f, "-boot_image any replay")

+         for cmd in iso.xorriso_commands(

+             opts.arch, opts.boot_iso, os.path.join(opts.output_dir, opts.iso_name)

+         ):

+             emit(f, " ".join(cmd))

          emit(f, "-volid %s" % opts.volid)

-         # isoinfo -J uses the Joliet tree, and it's used by virt-install

-         emit(f, "-joliet on")

-         # Support long filenames in the Joliet trees. Repodata is particularly

-         # likely to run into this limit.

-         emit(f, "-compliance joliet_long_names")

  

          with open(opts.graft_points) as gp:

              for line in gp:
@@ -178,10 +174,6 @@ 

                  emit(f, "%s %s %s" % (cmd, fs_path, iso_path))

                  emit(f, "-chmod 0%o %s" % (_get_perms(fs_path), iso_path))

  

-         if opts.arch == "ppc64le":

-             # This is needed for the image to be bootable.

-             emit(f, "-as mkisofs -U --")

- 

          emit(f, "-chown_r 0 /")

          emit(f, "-chgrp_r 0 /")

          emit(f, "-end")

file modified
+83 -3
@@ -14,6 +14,7 @@ 

  # along with this program; if not, see <https://gnu.org/licenses/>.

  

  

+ import itertools

  import os

  import random

  import shutil
@@ -23,7 +24,7 @@ 

  import productmd.treeinfo

  from productmd.images import Image

  from kobo.threads import ThreadPool, WorkerThread

- from kobo.shortcuts import run, relative_path

+ from kobo.shortcuts import run, relative_path, compute_file_checksums

  from six.moves import shlex_quote

  

  from pungi.wrappers import iso
@@ -457,7 +458,14 @@ 

  

          try:

              run_createiso_command(

-                 num, compose, bootable, arch, cmd["cmd"], mounts, log_file

+                 num,

+                 compose,

+                 bootable,

+                 arch,

+                 cmd["cmd"],

+                 mounts,

+                 log_file,

+                 cmd["iso_path"],

              )

          except Exception:

              self.fail(compose, cmd, variant, arch)
@@ -538,7 +546,9 @@ 

      return img

  

  

- def run_createiso_command(num, compose, bootable, arch, cmd, mounts, log_file):

+ def run_createiso_command(

+     num, compose, bootable, arch, cmd, mounts, log_file, iso_path

+ ):

      packages = [

          "coreutils",

          "xorriso" if compose.conf.get("createiso_use_xorrisofs") else "genisoimage",
@@ -580,6 +590,76 @@ 

          weight=compose.conf["runroot_weights"].get("createiso"),

      )

  

+     if bootable and compose.conf.get("createiso_use_xorrisofs"):

+         fix_treeinfo_checksums(compose, iso_path, arch)

+ 

+ 

+ def fix_treeinfo_checksums(compose, iso_path, arch):

+     """It is possible for the ISO to contain a .treefile with incorrect

+     checksums. By modifying the ISO (adding files) some of the images may

+     change.

+ 

+     This function fixes that after the fact by looking for incorrect checksums,

+     recalculating them and updating the .treeinfo file. Since the size of the

+     file doesn't change, this seems to not change any images.

+     """

+     modified = False

+     with iso.mount(iso_path, compose._logger) as mountpoint:

+         ti = productmd.TreeInfo()

+         ti.load(os.path.join(mountpoint, ".treeinfo"))

+         for image, (type_, expected) in ti.checksums.checksums.items():

+             checksums = compute_file_checksums(os.path.join(mountpoint, image), [type_])

+             actual = checksums[type_]

+             if actual == expected:

+                 # Everything fine here, skip to next image.

+                 continue

+ 

+             compose.log_debug("%s: %s: checksum mismatch", iso_path, image)

+             # Update treeinfo with correct checksum

+             ti.checksums.checksums[image] = (type_, actual)

+             modified = True

+ 

+     if not modified:

+         compose.log_debug("%s: All checksums match, nothing to do.", iso_path)

+         return

+ 

+     try:

+         tmpdir = compose.mkdtemp(arch, prefix="fix-checksum-")

+         # Write modified .treeinfo

+         ti_path = os.path.join(tmpdir, ".treeinfo")

+         compose.log_debug("Storing modified .treeinfo in %s", ti_path)

+         ti.dump(ti_path)

+         # Write a modified DVD into a temporary path, that is atomically moved

+         # over the original file.

+         fixed_path = os.path.join(tmpdir, "fixed-checksum-dvd.iso")

+         cmd = ["xorriso"]

+         cmd.extend(

+             itertools.chain.from_iterable(

+                 iso.xorriso_commands(arch, iso_path, fixed_path)

+             )

+         )

+         cmd.extend(["-map", ti_path, ".treeinfo"])

+         run(

+             cmd,

+             logfile=compose.paths.log.log_file(

+                 arch, "checksum-fix_generate_%s" % os.path.basename(iso_path)

+             ),

+         )

+         # The modified ISO no longer has implanted MD5, so that needs to be

+         # fixed again.

+         compose.log_debug("Implanting new MD5 to %s fixed_path")

+         run(

+             iso.get_implantisomd5_cmd(fixed_path, compose.supported),

+             logfile=compose.paths.log.log_file(

+                 arch, "checksum-fix_implantisomd5_%s" % os.path.basename(iso_path)

+             ),

+         )

+         # All done, move the updated image to the final location.

+         compose.log_debug("Updating %s", iso_path)

+         os.rename(fixed_path, iso_path)

+     finally:

+         shutil.rmtree(tmpdir)

+ 

  

  def split_iso(compose, arch, variant, no_split=False, logger=None):

      """

@@ -166,6 +166,7 @@ 

                  log_file=compose.paths.log.log_file(

                      arch, "extraiso-%s" % os.path.basename(iso_path)

                  ),

+                 iso_path=iso_path,

              )

  

          img = add_iso_to_metadata(

file modified
+18
@@ -516,3 +516,21 @@ 

                  util.run_unmount_cmd(["fusermount", "-u", mount_dir], path=mount_dir)

              else:

                  util.run_unmount_cmd(["umount", mount_dir], path=mount_dir)

+ 

+ 

+ def xorriso_commands(arch, input, output):

+     """List of xorriso commands to modify a bootable image."""

+     commands = [

+         ("-indev", input),

+         ("-outdev", output),

+         # isoinfo -J uses the Joliet tree, and it's used by virt-install

+         ("-joliet", "on"),

+         # Support long filenames in the Joliet trees. Repodata is particularly

+         # likely to run into this limit.

+         ("-compliance", "joliet_long_names"),

+         ("-boot_image", "any", "replay"),

+     ]

+     if arch == "ppc64le":

+         # This is needed for the image to be bootable.

+         commands.append(("-as", "mkisofs", "-U", "--"))

+     return commands

@@ -134,6 +134,7 @@ 

                  ),

              ],

          )

+         iso_path = os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso")

          self.assertEqual(

              rcc.call_args_list,

              [
@@ -152,6 +153,7 @@ 

                      log_file=os.path.join(

                          self.topdir, "logs/x86_64/extraiso-my.iso.x86_64.log"

                      ),

+                     iso_path=iso_path,

                  )

              ],

          )
@@ -162,7 +164,7 @@ 

                      compose,

                      server,

                      "x86_64",

-                     os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso"),

+                     iso_path,

                      True,

                      additional_variants=["Client"],

                  )
@@ -209,6 +211,7 @@ 

                  ),

              ],

          )

+         iso_path = os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso")

          self.assertEqual(

              rcc.call_args_list,

              [
@@ -227,6 +230,7 @@ 

                      log_file=os.path.join(

                          self.topdir, "logs/x86_64/extraiso-my.iso.x86_64.log"

                      ),

+                     iso_path=iso_path,

                  )

              ],

          )
@@ -237,7 +241,7 @@ 

                      compose,

                      server,

                      "x86_64",

-                     os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso"),

+                     iso_path,

                      True,

                      additional_variants=["Client"],

                  )
@@ -282,6 +286,7 @@ 

                  ),

              ],

          )

+         iso_path = os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso")

          self.assertEqual(

              rcc.call_args_list,

              [
@@ -300,6 +305,7 @@ 

                      log_file=os.path.join(

                          self.topdir, "logs/x86_64/extraiso-my.iso.x86_64.log"

                      ),

+                     iso_path=iso_path,

                  )

              ],

          )
@@ -310,7 +316,7 @@ 

                      compose,

                      server,

                      "x86_64",

-                     os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso"),

+                     iso_path,

                      True,

                      additional_variants=["Client"],

                  )
@@ -357,6 +363,7 @@ 

                  ),

              ],

          )

+         iso_path = os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso")

          self.assertEqual(

              rcc.call_args_list,

              [
@@ -375,6 +382,7 @@ 

                      log_file=os.path.join(

                          self.topdir, "logs/x86_64/extraiso-my.iso.x86_64.log"

                      ),

+                     iso_path=iso_path,

                  )

              ],

          )
@@ -385,7 +393,7 @@ 

                      compose,

                      server,

                      "x86_64",

-                     os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso"),

+                     iso_path,

                      False,

                      additional_variants=["Client"],

                  )
@@ -427,6 +435,7 @@ 

                  ),

              ],

          )

+         iso_path = os.path.join(self.topdir, "compose/Server/source/iso/my.iso")

          self.assertEqual(

              rcc.call_args_list,

              [
@@ -445,6 +454,7 @@ 

                      log_file=os.path.join(

                          self.topdir, "logs/src/extraiso-my.iso.src.log"

                      ),

+                     iso_path=iso_path,

                  )

              ],

          )
@@ -455,7 +465,7 @@ 

                      compose,

                      server,

                      "src",

-                     os.path.join(self.topdir, "compose/Server/source/iso/my.iso"),

+                     iso_path,

                      False,

                      additional_variants=["Client"],

                  )

Running xorriso to modify an ISO image can update content of included images such as images/eltorito.img, unless we explicitly update the image, which is undesirable (https://pagure.io/pungi/issue/1647).

However, when the file is changed, the checksum changes and .treeinfo no longer matches.

This patch implements a workaround: once the DVD is written, it looks for incorrect checksums, recalculates them and updates the .treeinfo on the DVD. Since only the checksum is changing and the size of the file remains the same, this seems to help fix the issue.

An additional step for implanting MD5 is needed again, as that gets erased by the workaround.

rebased onto b30c661

22 days ago