Repository: osbuild/osbuild
Pull request #2008 (2025-02-10 17:33:07)

stages: replace "logger" binary in the dracut chroot
This commit replaces the `/usr/bin/logger` binary in the dracut chroot with a bind mount to `/usr/bin/true` to silence the spam that we get from dracut during initramfs generation:
```
logger: socket /dev/log: No such file or directory
```

Unfortunately I could not find a nicer way, it seems it is not possible to simply pass `sysloglvl=0` via the commandline or an environment.

Closes: https://github.com/osbuild/osbuild/issues/1976

supakeen: If this is something that (might be) applicable to more stages then this should probably live in the runner(s) instead of in the stage? :)
mvo5: > If this is something that (might be) applicable to more stages then this should probably live in the runner(s) instead of in the stage? :)

So far I only found the dracut stage that has this issue. 
mvo5: @supakeen Sorry, force pushed and added some more background what the issue is to the comments and commit message. I had hoped I could include something in the runner or in the Chroot.chroot.run() helpers but its a bit vexed, the tl;dr is that the `devtmpfs` mount inside the dracut chroot creates /dev/log -> /run/systemd/journal/dev-log which causes the spam. But without the `devtmpfs` we run into the issue that got fixed with PR #1846 so there is no great option here it seems.

Patch:
diff --git a/Schutzfile b/Schutzfile
index aa91c409..d1451e36 100644
--- a/Schutzfile
+++ b/Schutzfile
@@ -50,7 +50,7 @@
           {
             "title": "fedora-updates",
             "name": "fedora-updates",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-x86_64-updates-released-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-x86_64-updates-released-20250201"
           }
         ],
         "aarch64": [
@@ -62,7 +62,7 @@
           {
             "title": "fedora-updates",
             "name": "fedora-updates",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-aarch64-updates-released-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-aarch64-updates-released-20250201"
           }
         ]
       }
@@ -81,7 +81,7 @@
           {
             "title": "fedora-updates",
             "name": "fedora-updates",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f41/f41-x86_64-updates-released-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f41/f41-x86_64-updates-released-20250201"
           }
         ],
         "aarch64": [
@@ -93,7 +93,7 @@
           {
             "title": "fedora-updates",
             "name": "fedora-updates",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f41/f41-aarch64-updates-released-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f41/f41-aarch64-updates-released-20250201"
           }
         ]
       }
@@ -107,34 +107,34 @@
           {
             "title": "baseos",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-baseos-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-baseos-20250201"
           },
           {
             "title": "appstream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-appstream-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-appstream-20250201"
           },
           {
             "title": "crb",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-crb-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-crb-20250201"
           }
         ],
         "aarch64": [
           {
             "title": "baseos",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-baseos-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-baseos-20250201"
           },
           {
             "title": "appstream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-appstream-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-appstream-20250201"
           },
           {
             "title": "crb",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-crb-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-crb-20250201"
           }
         ]
       }
@@ -148,34 +148,34 @@
           {
             "title": "baseos",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-x86_64-baseos-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-x86_64-baseos-20250201"
           },
           {
             "title": "appstream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-x86_64-appstream-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-x86_64-appstream-20250201"
           },
           {
             "title": "crb",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-x86_64-crb-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-x86_64-crb-20250201"
           }
         ],
         "aarch64": [
           {
             "title": "baseos",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-aarch64-baseos-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-aarch64-baseos-20250201"
           },
           {
             "title": "appstream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-aarch64-appstream-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-aarch64-appstream-20250201"
           },
           {
             "title": "crb",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-aarch64-crb-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el10/cs10-aarch64-crb-20250201"
           }
         ]
       }
@@ -189,34 +189,34 @@
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-baseos-n9.5-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-baseos-n9.5-20250201"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-appstream-n9.5-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-appstream-n9.5-20250201"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-crb-n9.5-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-crb-n9.5-20250201"
           }
         ],
         "aarch64": [
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-baseos-n9.5-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-baseos-n9.5-20250201"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-appstream-n9.5-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-appstream-n9.5-20250201"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-crb-n9.5-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-crb-n9.5-20250201"
           }
         ]
       }
@@ -230,34 +230,34 @@
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-baseos-n9.6-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-baseos-n9.6-20250201"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-appstream-n9.6-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-appstream-n9.6-20250201"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-crb-n9.6-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-crb-n9.6-20250201"
           }
         ],
         "aarch64": [
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-baseos-n9.6-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-baseos-n9.6-20250201"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-appstream-n9.6-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-appstream-n9.6-20250201"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-crb-n9.6-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-crb-n9.6-20250201"
           }
         ]
       }
@@ -271,34 +271,34 @@
           {
             "title": "RHEL-10-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-x86_64-baseos-n10.0-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-x86_64-baseos-n10.0-20250201"
           },
           {
             "title": "RHEL-10-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-x86_64-appstream-n10.0-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-x86_64-appstream-n10.0-20250201"
           },
           {
             "title": "RHEL-10-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-x86_64-crb-n10.0-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-x86_64-crb-n10.0-20250201"
           }
         ],
         "aarch64": [
           {
             "title": "RHEL-10-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-aarch64-baseos-n10.0-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-aarch64-baseos-n10.0-20250201"
           },
           {
             "title": "RHEL-10-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-aarch64-appstream-n10.0-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-aarch64-appstream-n10.0-20250201"
           },
           {
             "title": "RHEL-10-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-aarch64-crb-n10.0-20250101"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el10/el10-aarch64-crb-n10.0-20250201"
           }
         ]
       }
diff --git a/osbuild.spec b/osbuild.spec
index 27c3634e..a711950e 100644
--- a/osbuild.spec
+++ b/osbuild.spec
@@ -1,7 +1,7 @@
 %global         forgeurl https://github.com/osbuild/osbuild
 %global         selinuxtype targeted
 
-Version:        141
+Version:        142
 
 %forgemeta
 
diff --git a/osbuild/__init__.py b/osbuild/__init__.py
index 2b31c644..d2c0488d 100644
--- a/osbuild/__init__.py
+++ b/osbuild/__init__.py
@@ -10,7 +10,7 @@ independent of osbuild but used across the osbuild codebase.
 
 from .pipeline import Manifest, Pipeline, Stage
 
-__version__ = "141"
+__version__ = "142"
 
 __all__ = [
     "Manifest",
diff --git a/setup.py b/setup.py
index baf2781f..91cb2f60 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ import setuptools
 
 setuptools.setup(
     name="osbuild",
-    version="141",
+    version="142",
     description="A build system for OS images",
     packages=[
         "osbuild",
diff --git a/stages/org.osbuild.coreos.live-artifacts.mono b/stages/org.osbuild.coreos.live-artifacts.mono
index ca5891cc..f3f7178f 100755
--- a/stages/org.osbuild.coreos.live-artifacts.mono
+++ b/stages/org.osbuild.coreos.live-artifacts.mono
@@ -83,13 +83,14 @@ def mkinitrd_pipe(tmproot, destf, compress=True):
     files = subprocess.check_output(['find', '.', '-mindepth', '1', '-print0'],
                                     cwd=tmproot)
     file_list = files.split(b'\0')
-    # If there's a root.squashfs, it _must_ be the first file in the cpio
+    # If there's a root.[squash|ero]fs, it _must_ be the first file in the cpio
     # archive, since the dracut 20live module assumes its contents are at
     # a fixed offset in the archive.
-    squashfs = b'./root.squashfs'
-    if squashfs in file_list:
-        file_list.remove(squashfs)
-        file_list.insert(0, squashfs)
+    for filename in [b'./root.squashfs', b'./root.erofs']:
+        if filename in file_list:
+            file_list.remove(filename)
+            file_list.insert(0, filename)
+            break
     cpioproc = subprocess.Popen(['cpio', '-o', '-H', 'newc', '-R', 'root:root',
                                  '--quiet', '--reproducible', '--force-local', '--null',
                                  '-D', tmproot], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
@@ -544,14 +545,13 @@ boot
     return "images/efiboot.img"
 
 
-def mksquashfs_metal(paths, workdir, img_metal, loop_client):
+def mkrootfs_metal(paths, workdir, img_metal, fstype, fsoptions, loop_client):
     """
-    Mounts a copy of the metal image and modifies it accordingly to create a (squashfs) rootfs from its contents for the
-    live ISO.
+    Mounts a copy of the metal image and modifies it accordingly to create a (fstype) rootfs from its contents for the
+    live ISO. fstype must be squashfs or erofs.
 
     Returns the bls entry kernel arguments for the ISO bootloader.
     """
-    squashfs_compression = 'zstd'
     basearch = os.uname().machine
     tmp_squashfs_dir = os.path.join(workdir, 'tmp-squashfs-dir')
     os.mkdir(tmp_squashfs_dir)
@@ -601,17 +601,23 @@ def mksquashfs_metal(paths, workdir, img_metal, loop_client):
             print(f"Kernel binary linked: {tmp_squashfs_dir}/boot/{kernel_binary_basename}")
             print(f"Kernel HMAC linked: {tmp_squashfs_dir}/boot/{kernel_hmac_basename}")
             # Generate root squashfs
-            print(f'Compressing squashfs with {squashfs_compression}')
+            print(f'Creating {fstype} with {fsoptions}')
 
             # Note the filename must be exactly "root.squashfs" because the 20live
             # dracut module makes assumptions about the length of the name in sysroot.mount
             # this matches the set of flags we implicitly passed when doing this
             # through libguestfs' mksquashfs command
-            subprocess.check_call(['mksquashfs', tmp_squashfs_dir,
-                                   paths["initrd-rootfs/root.squashfs"],
-                                   '-root-becomes', tmp_squashfs_dir,
-                                   '-wildcards', '-no-recovery',
-                                   '-comp', squashfs_compression])
+            if fstype == "erofs":
+                subprocess.check_call(['mkfs.erofs',
+                                       *fsoptions.split(' '),
+                                       paths["initrd-rootfs/root.erofs"],
+                                       tmp_squashfs_dir])
+            else:
+                subprocess.check_call(['mksquashfs', tmp_squashfs_dir,
+                                       paths["initrd-rootfs/root.squashfs"],
+                                       '-root-becomes', tmp_squashfs_dir,
+                                       '-wildcards', '-no-recovery',
+                                       *fsoptions.split(' ')])
 
             # while it's mounted here, also get the kargs
             blsentry = ensure_glob(os.path.join(tmp_squashfs_dir, 'boot/loader/entries/*.conf'), n=1)[0]
@@ -776,7 +782,7 @@ def mk_osmet_files(deployed_tree, img_metal, img_metal4k, loop_client, paths, os
                 chroot.run(cmd, check=True)
 
 
-def mk_paths(workdir):
+def mk_paths(workdir, fstype):
     """
     Returns a dictionary with all the paths under workdir needed throughout multiple functions in the stage.
     """
@@ -808,7 +814,7 @@ def mk_paths(workdir):
         "initrd/etc/coreos-live-want-rootfs": "",
         "initrd/etc/coreos-live-initramfs": "",
         "initrd-rootfs": "",
-        "initrd-rootfs/root.squashfs": "",
+        f"initrd-rootfs/root.{fstype}": ""
     }
     for key in paths:
         paths[key] = os.path.join(workdir, key)
@@ -864,7 +870,13 @@ def main(workdir, tree, inputs, options, loop_client):
     os_release = osrelease.parse_files(os.path.join(deployed_tree, 'etc', 'os-release'))
     version = os_release['OSTREE_VERSION']
 
-    paths = mk_paths(workdir)
+    # Check whether live-rootfs options set
+    with open(os.path.join(deployed_tree, "usr/share/coreos-assembler", "image.json"), "r", encoding="utf8") as f:
+        parsed = json.load(f)
+        fstype = parsed.get("live-rootfs-fstype", "squashfs")
+        fsoptions = parsed.get("live-rootfs-fsoptions", "-comp zstd" if fstype == "squashfs" else "--quiet")
+
+    paths = mk_paths(workdir, fstype)
     mk_workdirs(paths)
 
     # Find the directory under `/usr/lib/modules/<kver>` where the
@@ -905,19 +917,26 @@ def main(workdir, tree, inputs, options, loop_client):
 
     mk_osmet_files(deployed_tree, img_metal, img_metal4k, loop_client, paths, os_release)
 
-    blsentry_kargs = mksquashfs_metal(paths, workdir, img_metal, loop_client)
+    blsentry_kargs = mkrootfs_metal(paths, workdir, img_metal, fstype, fsoptions, loop_client)
 
     # Generate rootfs image
     # The rootfs must be uncompressed because the ISO mounts root.squashfs
     # directly from the middle of the file
     extend_initramfs(initramfs=paths["iso/images/pxeboot/rootfs.img"],
                      tree=paths["initrd-rootfs"], compress=False)
-    # Check that the root.squashfs magic number is in the offset hardcoded
+    # Check that the root.[squash|ero]fs magic number is in the offset hardcoded
     # in sysroot.mount in 20live/live-generator
+    offset = 124
+    magic = b'hsqs'  # squashfs magic
+    if fstype == "erofs":
+        # The erofs's superblock starts at an absolute offset 1024 bytes
+        # https://erofs.docs.kernel.org/en/latest/core_ondisk.html#superblock
+        offset += 1024
+        magic = bytes.fromhex("e2e1f5e0")
     with open(paths["iso/images/pxeboot/rootfs.img"], 'rb') as fh:
-        fh.seek(124)
-        if fh.read(4) != b'hsqs':
-            raise ValueError("root.squashfs not at expected offset in rootfs image")
+        fh.seek(offset)
+        if fh.read(4) != magic:
+            raise ValueError(f"root.{fstype} not at expected offset in rootfs image")
     # Save stream hash of rootfs for verifying out-of-band fetches
     os.makedirs(os.path.dirname(paths["initrd/etc/coreos-live-want-rootfs"]), exist_ok=True)
     make_stream_hash(paths["iso/images/pxeboot/rootfs.img"],
diff --git a/stages/org.osbuild.dracut b/stages/org.osbuild.dracut
index 05a56682..451ef42d 100755
--- a/stages/org.osbuild.dracut
+++ b/stages/org.osbuild.dracut
@@ -1,4 +1,8 @@
 #!/usr/bin/python3
+import contextlib
+import os
+import os.path
+import subprocess
 import sys
 
 import osbuild.api
@@ -10,6 +14,22 @@ def yesno(name: str, value: bool) -> str:
     return f"--{prefix}{name}"
 
 
+@contextlib.contextmanager
+def bind_mount_logger(tree: str) -> None:
+    logger_tree_path = os.path.join(tree, "usr/bin/logger")
+    if not os.path.exists(logger_tree_path):
+        # no logger -> nothing to do
+        yield
+        return
+    # make logger a "noop"
+    true_tree_path = os.path.join(tree, "usr/bin/true")
+    subprocess.run(["mount", "--rbind", true_tree_path, logger_tree_path], check=True)
+    try:
+        yield
+    finally:
+        subprocess.run(["umount", logger_tree_path], check=False)
+
+
 # pylint: disable=too-many-branches
 def main(tree, options):
     kernels = options["kernel"]
@@ -82,8 +102,15 @@ def main(tree, options):
         if initoverlayfs:
             initfs_bin = "/usr/bin/initoverlayfs-install"
 
-        with Chroot(tree) as chroot:
-            chroot.run([initfs_bin, "--no-hostonly", "--kver", kver] + opts, check=True)
+        # the chroot.run() call below will mount /dev with "devtmpfs", this
+        # is needed, see pr #1846 but it means we have likely have a
+        #   /dev/log -> /run/systemd/journal/dev-log
+        # symlink and dracut will try to log on it but end up writing
+        # 5k of "logger: socket /dev/log: No such file or directory"
+        # messages. See also issue #1976
+        with bind_mount_logger(tree):
+            with Chroot(tree) as chroot:
+                chroot.run([initfs_bin, "--no-hostonly", "--kver", kver] + opts, check=True)
 
     return 0
 
diff --git a/tools/osbuild-image-info b/tools/osbuild-image-info
index 983b86d5..11a9c3bb 100755
--- a/tools/osbuild-image-info
+++ b/tools/osbuild-image-info
@@ -1118,9 +1118,13 @@ def read_selinux_ctx_mismatch(tree, is_ostree):
         setfiles_pattern = r"Would\s+relabel\s+(?P<filename>.+)\s+from\s+(?P<actual>.+)\s+to\s+(?P<expected>.+)"
         setfiles_re = re.compile(setfiles_pattern)
 
+        # skip messages about old compiled fcontext format
+        binary_fcontext_skip = rf"{tree}/etc/selinux/targeted/contexts/files/file_contexts.*\.bin:\s+Old compiled fcontext format, skipping"
+        binary_fcontext_skip_re = re.compile(binary_fcontext_skip)
+
         for line in output.splitlines():
             line = line.strip()
-            if not line:
+            if not line or binary_fcontext_skip_re.match(line):
                 continue
             match = setfiles_re.match(line)
             # do not silently ignore changes of 'setfiles' output


Test patch:
diff --git a/stages/test/test_coreos_live_artifacts_mono.py b/stages/test/test_coreos_live_artifacts_mono.py
index f97c3fc5..06f6c407 100644
--- a/stages/test/test_coreos_live_artifacts_mono.py
+++ b/stages/test/test_coreos_live_artifacts_mono.py
@@ -59,6 +59,7 @@ def test_align_initrd(tmp_path, stage_module, test_case):
     {"treefiles": ["file_b", "file_c", "root.squashfs", "file_1"], "compress": False},
     {"treefiles": ["file_b", "file_c", "file_1"], "compress": True},
     {"treefiles": ["file_b", "file_c", "root.squashfs", "file_1"], "compress": True},
+    {"treefiles": ["file_b", "file_c", "root.erofs", "file_1"], "compress": True},
 ])
 def test_extend_initramfs(tmp_path, stage_module, test_case):
     img_path = tmp_path / "img"
@@ -88,6 +89,9 @@ def test_extend_initramfs(tmp_path, stage_module, test_case):
     if "root.squashfs" in treefiles:
         # root.squashfs must be first
         assert files_in_archive[0] == "root.squashfs"
+    if "root.erofs" in treefiles:
+        # root.erofs must be first
+        assert files_in_archive[0] == "root.erofs"
 
 
 def test_make_stream_hash(tmp_path, stage_module):
diff --git a/stages/test/test_dracut.py b/stages/test/test_dracut.py
index 99530589..4037b50a 100644
--- a/stages/test/test_dracut.py
+++ b/stages/test/test_dracut.py
@@ -29,3 +29,31 @@ def test_dracut_with_initoverlayfs(mocked_run, tmp_path, stage_module, with_init
     run_argv = args[0]
     assert run_argv[0] == "chroot"
     assert run_argv[2] == expected_argv2
+
+
+@patch("subprocess.run")
+def test_dracut_logger(mocked_run, tmp_path, stage_module):
+    fake_logger_path = tmp_path / "usr/bin/logger"
+    fake_logger_path.parent.mkdir(parents=True)
+    fake_logger_path.write_text("")
+    fake_true_path = tmp_path / "usr/bin/true"
+    fake_true_path.write_text("")
+
+    options = {
+        "kernel": [
+            "5.14.0-247.el9.x86_64",
+        ]
+    }
+    stage_module.main(tmp_path.as_posix(), options)
+    assert len(mocked_run.call_args_list) == 9
+    args, kwargs = mocked_run.call_args_list[0]  # bind-mount is the 1th call
+    assert kwargs.get("check") is True
+    run_argv = args[0]
+    assert run_argv == [
+        "mount", "--rbind",
+        fake_true_path.as_posix(), fake_logger_path.as_posix(),
+    ]
+    args, kwargs = mocked_run.call_args_list[8]  # umount is the 9th call
+    assert kwargs.get("check") is False
+    run_argv = args[0]
+    assert run_argv == ["umount", fake_logger_path.as_posix()]
diff --git a/stages/test/test_ovf.py b/stages/test/test_ovf.py
new file mode 100644
index 00000000..72d978e4
--- /dev/null
+++ b/stages/test/test_ovf.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python3
+
+import pytest
+
+from osbuild import testutil
+
+STAGE_NAME = "org.osbuild.ovf"
+
+
+# Prepare dataset containing good and bad API call parameters
+@pytest.mark.parametrize("test_data, expected_err", [
+    # Bad API parameters
+    ({}, "'vmdk' is a required property"),
+    ({"vmdk": 123}, "123 is not of type 'string'"),
+    ({"vmdk": "imagename"}, "'imagename' does not match '[a-zA-Z0-9+_.-]+.vmdk'"),
+    # Good API parameters
+    ({"vmdk": "imagename.vmdk"}, ""),
+])
+# This test validates only API calls using correct and incorrect queries
+def test_schema_validation_ovf(stage_schema, test_data, expected_err):
+    test_input = {
+        "type": STAGE_NAME,
+        "devices": {
+            "device": {
+                "path": "some-path",
+            },
+        },
+        "options": {
+        }
+    }
+    test_input["options"].update(test_data)
+    res = stage_schema.validate(test_input)
+
+    if expected_err == "":
+        assert res.valid is True, f"err: {[e.as_dict() for e in res.errors]}"
+    else:
+        assert res.valid is False
+        testutil.assert_jsonschema_error_contains(res, expected_err, expected_num_errs=1)
+
+
+@pytest.mark.skipif(not testutil.has_executable("qemu-img"), reason="need qemu-img")
+def test_ovf_default_template(tmp_path, stage_module):
+    faked_vmdk_path = tmp_path / "some-image.vmdk"
+    faked_vmdk_path.write_bytes(b"1234")
+
+    opts = {
+        "vmdk": faked_vmdk_path,
+    }
+    stage_module.main(opts, tmp_path)
+
+    expected_template_path = tmp_path / "some-image.ovf"
+    assert "other26xLinux64Guest" in expected_template_path.read_text()
diff --git a/tools/test/test_osbuild_image_info.py b/tools/test/test_osbuild_image_info.py
index 61b0de56..07b7b3c8 100644
--- a/tools/test/test_osbuild_image_info.py
+++ b/tools/test/test_osbuild_image_info.py
@@ -224,3 +224,116 @@ def test_analyse_iso_fail_no_tarball(tmp_path):
             subprocess.CalledProcessError,
             match=r"^Command '\['tar', '--selinux', '--xattrs', '--acls', '-x', '--auto-compress', '-f', '/tmp/\w+/liveimg.tar.gz"):
         osbuild_image_info.analyse_iso(image_path)
+
+
+@pytest.mark.parametrize("subprocess_output,expected_report", [
+    pytest.param(
+        """Would relabel {tmp_path}/etc/shells from unconfined_u:object_r:etc_t:s0 to system_u:object_r:etc_t:s0
+Would relabel {tmp_path}/etc/ld.so.cache from unconfined_u:object_r:ld_so_cache_t:s0 to system_u:object_r:ld_so_cache_t:s0
+Would relabel {tmp_path}/etc/alternatives/roff.7.gz from unconfined_u:object_r:etc_t:s0 to system_u:object_r:etc_t:s0
+Would relabel {tmp_path}/var/lib/selinux/targeted/active from unconfined_u:object_r:semanage_store_t:s0 to system_u:object_r:semanage_store_t:s0
+Would relabel {tmp_path}/var/lib/alternatives/roff.7.gz from unconfined_u:object_r:rpm_var_lib_t:s0 to system_u:object_r:rpm_var_lib_t:s0
+""",
+        [
+            {
+                "filename": "/etc/alternatives/roff.7.gz",
+                "actual": "unconfined_u:object_r:etc_t:s0",
+                "expected": "system_u:object_r:etc_t:s0",
+            },
+            {
+                "filename": "/etc/ld.so.cache",
+                "actual": "unconfined_u:object_r:ld_so_cache_t:s0",
+                "expected": "system_u:object_r:ld_so_cache_t:s0",
+            },
+            {
+                "filename": "/etc/shells",
+                "actual": "unconfined_u:object_r:etc_t:s0",
+                "expected": "system_u:object_r:etc_t:s0",
+            },
+            {
+                "filename": "/var/lib/alternatives/roff.7.gz",
+                "actual": "unconfined_u:object_r:rpm_var_lib_t:s0",
+                "expected": "system_u:object_r:rpm_var_lib_t:s0",
+            },
+            {
+                "filename": "/var/lib/selinux/targeted/active",
+                "actual": "unconfined_u:object_r:semanage_store_t:s0",
+                "expected": "system_u:object_r:semanage_store_t:s0",
+            },
+        ],
+        id="happy case",
+    ),
+    pytest.param(
+        "",
+        [],
+        id="empty",
+    ),
+    pytest.param(
+        """{tmp_path}/etc/selinux/targeted/contexts/files/file_contexts.bin:  Old compiled fcontext format, skipping
+{tmp_path}/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin:  Old compiled fcontext format, skipping
+""",
+        [],
+        id="only lines to skip",
+    ),
+    pytest.param(
+        """{tmp_path}/etc/selinux/targeted/contexts/files/file_contexts.bin:  Old compiled fcontext format, skipping
+{tmp_path}/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin:  Old compiled fcontext format, skipping
+
+Would relabel {tmp_path}/etc/shells from unconfined_u:object_r:etc_t:s0 to system_u:object_r:etc_t:s0
+Would relabel {tmp_path}/etc/ld.so.cache from unconfined_u:object_r:ld_so_cache_t:s0 to system_u:object_r:ld_so_cache_t:s0
+Would relabel {tmp_path}/etc/alternatives/roff.7.gz from unconfined_u:object_r:etc_t:s0 to system_u:object_r:etc_t:s0
+Would relabel {tmp_path}/var/lib/selinux/targeted/active from unconfined_u:object_r:semanage_store_t:s0 to system_u:object_r:semanage_store_t:s0
+Would relabel {tmp_path}/var/lib/alternatives/roff.7.gz from unconfined_u:object_r:rpm_var_lib_t:s0 to system_u:object_r:rpm_var_lib_t:s0
+""",
+        [
+            {
+                "filename": "/etc/alternatives/roff.7.gz",
+                "actual": "unconfined_u:object_r:etc_t:s0",
+                "expected": "system_u:object_r:etc_t:s0",
+            },
+            {
+                "filename": "/etc/ld.so.cache",
+                "actual": "unconfined_u:object_r:ld_so_cache_t:s0",
+                "expected": "system_u:object_r:ld_so_cache_t:s0",
+            },
+            {
+                "filename": "/etc/shells",
+                "actual": "unconfined_u:object_r:etc_t:s0",
+                "expected": "system_u:object_r:etc_t:s0",
+            },
+            {
+                "filename": "/var/lib/alternatives/roff.7.gz",
+                "actual": "unconfined_u:object_r:rpm_var_lib_t:s0",
+                "expected": "system_u:object_r:rpm_var_lib_t:s0",
+            },
+            {
+                "filename": "/var/lib/selinux/targeted/active",
+                "actual": "unconfined_u:object_r:semanage_store_t:s0",
+                "expected": "system_u:object_r:semanage_store_t:s0",
+            },
+        ],
+        id="valid lines mixed with lines to skip",
+    )
+])
+def test_read_selinux_ctx_mismatch(tmp_path, subprocess_output, expected_report):
+    """
+    Test the read_selinux_ctx_mismatch function
+    """
+    policy_dir = tmp_path / "etc/selinux/targeted/policy"
+    policy_dir.mkdir(parents=True)
+    policy_file = policy_dir / "policy.33"
+    policy_file.touch()
+
+    with patch("subprocess.check_output") as subprocess_check_output:
+        subprocess_check_output.return_value = subprocess_output.format(tmp_path=tmp_path)
+        report = osbuild_image_info.read_selinux_ctx_mismatch(tmp_path.as_posix(), False)
+
+        assert subprocess_check_output.call_count == 1
+        assert subprocess_check_output.call_args[0][0] == [
+            "setfiles", "-r", tmp_path.as_posix(),
+            "-nvF",
+            "-c", os.fspath(tmp_path / "etc/selinux/targeted/policy/policy.33"),
+            os.fspath(tmp_path / "etc/selinux/targeted/contexts/files/file_contexts"),
+            tmp_path.as_posix(),
+        ]
+        assert report == expected_report