diff --git a/build-tests/x86/rawhide/test-image-erofs/appliance.kiwi b/build-tests/x86/rawhide/test-image-erofs/appliance.kiwi
new file mode 100644
index 00000000000..28945ab0bb9
--- /dev/null
+++ b/build-tests/x86/rawhide/test-image-erofs/appliance.kiwi
@@ -0,0 +1,40 @@
+
+
+
+
+ Marcus Schaefer
+ marcus.schaefer@suse.com
+ Fedora Appliance, Testing erofs filesystem image
+
+
+ 2.0.0
+ dnf5
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build-tests/x86/rawhide/test-image-live-disk/appliance.kiwi b/build-tests/x86/rawhide/test-image-live-disk/appliance.kiwi
index e9039c726bb..397668030cc 100644
--- a/build-tests/x86/rawhide/test-image-live-disk/appliance.kiwi
+++ b/build-tests/x86/rawhide/test-image-live-disk/appliance.kiwi
@@ -11,6 +11,7 @@
+
@@ -24,6 +25,11 @@
UTC
false
+
+
+
+
+
@@ -71,7 +77,7 @@
-
+
diff --git a/build-tests/x86/tumbleweed/test-image-live/appliance.kiwi b/build-tests/x86/tumbleweed/test-image-live/appliance.kiwi
index f713e7c57d4..c41a35f068d 100644
--- a/build-tests/x86/tumbleweed/test-image-live/appliance.kiwi
+++ b/build-tests/x86/tumbleweed/test-image-live/appliance.kiwi
@@ -14,6 +14,7 @@
+
1.42.3
@@ -31,6 +32,11 @@
+
+
+
+
+
@@ -50,7 +56,7 @@
-
+
@@ -58,6 +64,7 @@
+
diff --git a/build-tests/x86/tumbleweed/test-image-live/config.sh b/build-tests/x86/tumbleweed/test-image-live/config.sh
index 1bd6f33a453..607183c193e 100644
--- a/build-tests/x86/tumbleweed/test-image-live/config.sh
+++ b/build-tests/x86/tumbleweed/test-image-live/config.sh
@@ -1,39 +1,26 @@
#!/bin/bash
-#================
-# FILE : config.sh
-#----------------
-# PROJECT : OpenSuSE KIWI Image System
-# COPYRIGHT : (c) 2006 SUSE LINUX Products GmbH. All rights reserved
-# :
-# AUTHOR : Marcus Schaefer
-# :
-# BELONGS TO : Operating System images
-# :
-# DESCRIPTION : configuration script for SUSE based
-# : operating systems
-#----------------
-#======================================
-# Functions...
-#--------------------------------------
-test -f /.kconfig && . /.kconfig
-test -f /.profile && . /.profile
+
+set -ex
+
+declare kiwi_profiles=${kiwi_profiles}
+declare kiwi_iname=${kiwi_iname}
#======================================
# Greeting...
#--------------------------------------
echo "Configure image: [$kiwi_iname]..."
-#======================================
-# Setup baseproduct link
-#--------------------------------------
-suseSetupProduct
-
#======================================
# Activate services
#--------------------------------------
-suseInsertService sshd
+systemctl enable sshd
#======================================
-# Setup default target, multi-user
+# Include erofs module
#--------------------------------------
-baseSetRunlevel 3
+for profile in ${kiwi_profiles//,/ }; do
+ if [ "${profile}" = "EroFS" ]; then
+ # remove from blacklist
+ rm -f /usr/lib/modprobe.d/60-blacklist_fs-erofs.conf
+ fi
+done
diff --git a/kiwi/builder/filesystem.py b/kiwi/builder/filesystem.py
index 1bc53a67479..34b6b42d0f4 100644
--- a/kiwi/builder/filesystem.py
+++ b/kiwi/builder/filesystem.py
@@ -89,7 +89,7 @@ def __init__(
self.blocksize = xml_state.build_type.get_target_blocksize()
self.filesystem_setup = FileSystemSetup(xml_state, root_dir)
self.filesystems_no_device_node = [
- 'squashfs'
+ 'squashfs', 'erofs'
]
self.luks = xml_state.get_luks_credentials()
self.result = Result(xml_state)
diff --git a/kiwi/builder/live.py b/kiwi/builder/live.py
index 9be04d0a14d..c4dc7e988c0 100644
--- a/kiwi/builder/live.py
+++ b/kiwi/builder/live.py
@@ -246,7 +246,7 @@ def create(self) -> Result:
filesystem_setup = FileSystemSetup(
self.xml_state, self.root_dir
)
- if root_filesystem != 'squashfs':
+ if root_filesystem not in ['squashfs', 'erofs']:
# Create a filesystem image of the specified type
# and put it into a SquashFS container
root_image = Temporary().new_file()
@@ -302,12 +302,15 @@ def create(self) -> Result:
else:
# Put the root filesystem into SquashFS directly
with FileSystem.new(
- name='squashfs',
+ name=root_filesystem,
device_provider=DeviceProvider(),
root_dir=self.root_dir + os.sep,
custom_args={
'compression':
self.xml_state.build_type.get_squashfscompression()
+ } if root_filesystem == 'squashfs' else {
+ 'compression':
+ self.xml_state.build_type.get_erofscompression()
}
) as live_container_image:
container_image = Temporary().new_file()
@@ -316,6 +319,12 @@ def create(self) -> Result:
)
Path.create(self.media_dir.name + '/LiveOS')
os.chmod(container_image.name, 0o644)
+ # Note: we keep the filename of the read-only image as it is
+ # even if another read-only filesystem not matching this
+ # filename is used. This is because the following filename
+ # is also used in the initrd code for the kiwi-live and
+ # dmsquash dracut modules. The name can be overwritten
+ # with the rd.live.squashimg boot option though.
shutil.copy(
container_image.name,
self.media_dir.name + '/LiveOS/squashfs.img'
diff --git a/kiwi/defaults.py b/kiwi/defaults.py
index 0deb1707c5f..7539f5c91a9 100644
--- a/kiwi/defaults.py
+++ b/kiwi/defaults.py
@@ -1523,7 +1523,7 @@ def get_filesystem_image_types():
"""
return [
'ext2', 'ext3', 'ext4', 'btrfs', 'squashfs',
- 'xfs', 'fat16', 'fat32'
+ 'xfs', 'fat16', 'fat32', 'erofs'
]
@staticmethod
diff --git a/kiwi/filesystem/__init__.py b/kiwi/filesystem/__init__.py
index 067048cc32f..3adfbc3d977 100644
--- a/kiwi/filesystem/__init__.py
+++ b/kiwi/filesystem/__init__.py
@@ -54,7 +54,8 @@ def new(
'fat16': 'Fat16',
'fat32': 'Fat32',
'squashfs': 'SquashFs',
- 'swap': 'Swap'
+ 'swap': 'Swap',
+ 'erofs': 'EroFs'
}
try:
filesystem = importlib.import_module(
diff --git a/kiwi/filesystem/erofs.py b/kiwi/filesystem/erofs.py
new file mode 100644
index 00000000000..1858d1b4064
--- /dev/null
+++ b/kiwi/filesystem/erofs.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2024 SUSE Software Solutions Germany GmbH. All rights reserved.
+#
+# This file is part of kiwi.
+#
+# kiwi 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 3 of the License, or
+# (at your option) any later version.
+#
+# kiwi 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 kiwi. If not, see
+#
+from typing import List
+
+# project
+from kiwi.filesystem.base import FileSystemBase
+from kiwi.command import Command
+
+
+class FileSystemEroFs(FileSystemBase):
+ """
+ **Implements creation of erofs filesystem**
+ """
+ def create_on_file(
+ self, filename, label: str = None, exclude: List[str] = None
+ ):
+ """
+ Create erofs filesystem from data tree
+
+ :param string filename: result file path name
+ :param string label: volume label
+ :param list exclude: list of exclude dirs/files
+ """
+ self.filename = filename
+ exclude_options = []
+ compression = self.custom_args.get('compression')
+ if compression:
+ self.custom_args['create_options'].append('-z')
+ self.custom_args['create_options'].append(compression)
+
+ if exclude:
+ for item in exclude:
+ exclude_options.append(f'--exclude-regex={item}')
+
+ if label:
+ self.custom_args['create_options'].append('-L')
+ self.custom_args['create_options'].append(label)
+
+ Command.run(
+ [
+ 'mkfs.erofs'
+ ] + self.custom_args['create_options'] + exclude_options + [
+ self.filename, self.root_dir
+ ]
+ )
diff --git a/kiwi/schema/kiwi.rnc b/kiwi/schema/kiwi.rnc
index cb39f22eaf8..7aa763fde03 100644
--- a/kiwi/schema/kiwi.rnc
+++ b/kiwi/schema/kiwi.rnc
@@ -1671,7 +1671,7 @@ div {
k.type.filesystem.attribute =
## Specifies the root filesystem type
attribute filesystem {
- "btrfs" | "ext2" | "ext3" | "ext4" | "squashfs" | "xfs"
+ "btrfs" | "ext2" | "ext3" | "ext4" | "squashfs" | "erofs" | "xfs"
}
>> sch:pattern [ id = "filesystem" is-a = "image_type"
sch:param [ name = "attr" value = "filesystem" ]
@@ -1682,6 +1682,13 @@ div {
sch:param [ name = "attr" value = "filesystem" ]
sch:param [ name = "types" value = "oem" ]
]
+ k.type.erofscompression.attribute =
+ ## Specifies the compression type for erofs
+ attribute erofscompression { text }
+ >> sch:pattern [ id = "erofscompression" is-a = "image_type"
+ sch:param [ name = "attr" value = "erofscompression" ]
+ sch:param [ name = "types" value = "oem pxe kis iso erofs" ]
+ ]
k.type.squashfscompression.attribute =
## Specifies the compression type for mksquashfs
attribute squashfscompression {
@@ -1958,7 +1965,7 @@ div {
## Specifies the image type
attribute image {
"btrfs" | "cpio" | "docker" | "ext2" | "ext3" |
- "ext4" | "iso" | "oem" | "pxe" | "kis" | "squashfs" | "tbz" |
+ "ext4" | "iso" | "oem" | "pxe" | "kis" | "squashfs" | "erofs" | "tbz" |
"xfs" | "oci" | "appx" | "enclave"
}
>> sch:pattern [
@@ -2284,6 +2291,7 @@ div {
k.type.fsmountoptions.attribute? &
k.type.fscreateoptions.attribute? &
k.type.squashfscompression.attribute? &
+ k.type.erofscompression.attribute? &
k.type.gcelicense.attribute? &
k.type.hybridpersistent.attribute? &
k.type.hybridpersistent_filesystem.attribute? &
diff --git a/kiwi/schema/kiwi.rng b/kiwi/schema/kiwi.rng
index 9988bac5f4c..8857a1fc595 100644
--- a/kiwi/schema/kiwi.rng
+++ b/kiwi/schema/kiwi.rng
@@ -2435,6 +2435,7 @@ structure
ext3
ext4
squashfs
+ erofs
xfs
@@ -2447,6 +2448,15 @@ structure
+
+
+ Specifies the compression type for erofs
+
+
+
+
+
+
Specifies the compression type for mksquashfs
@@ -2822,6 +2832,7 @@ initrd architecture.
pxe
kis
squashfs
+ erofs
tbz
xfs
oci
@@ -3310,6 +3321,9 @@ kiwi-ng result bundle ...
+
+
+
diff --git a/kiwi/xml_parse.py b/kiwi/xml_parse.py
index 7a7bae03205..83c97e8daa6 100644
--- a/kiwi/xml_parse.py
+++ b/kiwi/xml_parse.py
@@ -3094,7 +3094,7 @@ class type_(GeneratedsSuper):
"""The Image Type of the Logical Extend"""
subclass = None
superclass = None
- def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=None, bootpartition=None, bootpartsize=None, efipartsize=None, efifatimagesize=None, eficsm=None, efiparttable=None, dosparttable_extended_layout=None, bootprofile=None, btrfs_quota_groups=None, btrfs_root_is_snapshot=None, btrfs_root_is_subvolume=None, btrfs_set_default_volume=None, btrfs_root_is_readonly_snapshot=None, compressed=None, devicepersistency=None, editbootconfig=None, editbootinstall=None, filesystem=None, flags=None, enclave_format=None, format=None, formatoptions=None, fsmountoptions=None, fscreateoptions=None, squashfscompression=None, gcelicense=None, hybridpersistent=None, hybridpersistent_filesystem=None, gpt_hybrid_mbr=None, force_mbr=None, initrd_system=None, image=None, metadata_path=None, installboot=None, install_continue_on_timeout=None, installprovidefailsafe=None, installiso=None, installstick=None, installpxe=None, mediacheck=None, kernelcmdline=None, luks=None, luks_version=None, luksOS=None, luks_randomize=None, luks_pbkdf=None, mdraid=None, overlayroot=None, overlayroot_write_partition=None, overlayroot_readonly_partsize=None, verity_blocks=None, embed_verity_metadata=None, standalone_integrity=None, embed_integrity_metadata=None, integrity_legacy_hmac=None, integrity_metadata_key_description=None, integrity_keyfile=None, primary=None, ramonly=None, rootfs_label=None, spare_part=None, spare_part_mountpoint=None, spare_part_fs=None, spare_part_fs_attributes=None, spare_part_is_last=None, target_blocksize=None, target_removable=None, selinux_policy=None, vga=None, vhdfixedtag=None, volid=None, application_id=None, wwid_wait_timeout=None, derived_from=None, delta_root=None, ensure_empty_tmpdirs=None, xen_server=None, publisher=None, disk_start_sector=None, root_clone=None, boot_clone=None, bundle_format=None, bootloader=None, containerconfig=None, machine=None, oemconfig=None, size=None, systemdisk=None, partitions=None, vagrantconfig=None, installmedia=None, luksformat=None):
+ def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=None, bootpartition=None, bootpartsize=None, efipartsize=None, efifatimagesize=None, eficsm=None, efiparttable=None, dosparttable_extended_layout=None, bootprofile=None, btrfs_quota_groups=None, btrfs_root_is_snapshot=None, btrfs_root_is_subvolume=None, btrfs_set_default_volume=None, btrfs_root_is_readonly_snapshot=None, compressed=None, devicepersistency=None, editbootconfig=None, editbootinstall=None, filesystem=None, flags=None, enclave_format=None, format=None, formatoptions=None, fsmountoptions=None, fscreateoptions=None, squashfscompression=None, erofscompression=None, gcelicense=None, hybridpersistent=None, hybridpersistent_filesystem=None, gpt_hybrid_mbr=None, force_mbr=None, initrd_system=None, image=None, metadata_path=None, installboot=None, install_continue_on_timeout=None, installprovidefailsafe=None, installiso=None, installstick=None, installpxe=None, mediacheck=None, kernelcmdline=None, luks=None, luks_version=None, luksOS=None, luks_randomize=None, luks_pbkdf=None, mdraid=None, overlayroot=None, overlayroot_write_partition=None, overlayroot_readonly_partsize=None, verity_blocks=None, embed_verity_metadata=None, standalone_integrity=None, embed_integrity_metadata=None, integrity_legacy_hmac=None, integrity_metadata_key_description=None, integrity_keyfile=None, primary=None, ramonly=None, rootfs_label=None, spare_part=None, spare_part_mountpoint=None, spare_part_fs=None, spare_part_fs_attributes=None, spare_part_is_last=None, target_blocksize=None, target_removable=None, selinux_policy=None, vga=None, vhdfixedtag=None, volid=None, application_id=None, wwid_wait_timeout=None, derived_from=None, delta_root=None, ensure_empty_tmpdirs=None, xen_server=None, publisher=None, disk_start_sector=None, root_clone=None, boot_clone=None, bundle_format=None, bootloader=None, containerconfig=None, machine=None, oemconfig=None, size=None, systemdisk=None, partitions=None, vagrantconfig=None, installmedia=None, luksformat=None):
self.original_tagname_ = None
self.boot = _cast(None, boot)
self.bootfilesystem = _cast(None, bootfilesystem)
@@ -3125,6 +3125,7 @@ def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=Non
self.fsmountoptions = _cast(None, fsmountoptions)
self.fscreateoptions = _cast(None, fscreateoptions)
self.squashfscompression = _cast(None, squashfscompression)
+ self.erofscompression = _cast(None, erofscompression)
self.gcelicense = _cast(None, gcelicense)
self.hybridpersistent = _cast(bool, hybridpersistent)
self.hybridpersistent_filesystem = _cast(None, hybridpersistent_filesystem)
@@ -3341,6 +3342,8 @@ def get_fscreateoptions(self): return self.fscreateoptions
def set_fscreateoptions(self, fscreateoptions): self.fscreateoptions = fscreateoptions
def get_squashfscompression(self): return self.squashfscompression
def set_squashfscompression(self, squashfscompression): self.squashfscompression = squashfscompression
+ def get_erofscompression(self): return self.erofscompression
+ def set_erofscompression(self, erofscompression): self.erofscompression = erofscompression
def get_gcelicense(self): return self.gcelicense
def set_gcelicense(self, gcelicense): self.gcelicense = gcelicense
def get_hybridpersistent(self): return self.hybridpersistent
@@ -3629,6 +3632,9 @@ def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='
if self.squashfscompression is not None and 'squashfscompression' not in already_processed:
already_processed.add('squashfscompression')
outfile.write(' squashfscompression=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.squashfscompression), input_name='squashfscompression')), ))
+ if self.erofscompression is not None and 'erofscompression' not in already_processed:
+ already_processed.add('erofscompression')
+ outfile.write(' erofscompression=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.erofscompression), input_name='erofscompression')), ))
if self.gcelicense is not None and 'gcelicense' not in already_processed:
already_processed.add('gcelicense')
outfile.write(' gcelicense=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.gcelicense), input_name='gcelicense')), ))
@@ -4018,6 +4024,10 @@ def buildAttributes(self, node, attrs, already_processed):
already_processed.add('squashfscompression')
self.squashfscompression = value
self.squashfscompression = ' '.join(self.squashfscompression.split())
+ value = find_attr_value_('erofscompression', node)
+ if value is not None and 'erofscompression' not in already_processed:
+ already_processed.add('erofscompression')
+ self.erofscompression = value
value = find_attr_value_('gcelicense', node)
if value is not None and 'gcelicense' not in already_processed:
already_processed.add('gcelicense')
diff --git a/package/python-kiwi-spec-template b/package/python-kiwi-spec-template
index 40f0425c728..e4561ecd974 100644
--- a/package/python-kiwi-spec-template
+++ b/package/python-kiwi-spec-template
@@ -291,10 +291,17 @@ Provides: kiwi-filesystem:ext3
Provides: kiwi-filesystem:ext4
Provides: kiwi-filesystem:squashfs
Provides: kiwi-filesystem:xfs
+%if ! (0%{?suse_version} && 0%{?suse_version} < 1600)
+Provides: kiwi-filesystem:erofs
+Provides: kiwi-image:erofs
+%endif
%endif
Requires: dosfstools
Requires: e2fsprogs
Requires: xfsprogs
+%if ! (0%{?suse_version} && 0%{?suse_version} < 1600)
+Requires: erofs-utils
+%endif
%if 0%{?suse_version}
Requires: btrfsprogs
%else
diff --git a/test/unit/filesystem/erofs_test.py b/test/unit/filesystem/erofs_test.py
new file mode 100644
index 00000000000..bc91f232051
--- /dev/null
+++ b/test/unit/filesystem/erofs_test.py
@@ -0,0 +1,43 @@
+from unittest.mock import patch
+
+import unittest.mock as mock
+
+from kiwi.defaults import Defaults
+from kiwi.filesystem.erofs import FileSystemEroFs
+
+
+class TestFileSystemEroFs:
+ @patch('os.path.exists')
+ def setup(self, mock_exists):
+ mock_exists.return_value = True
+ self.erofs = FileSystemEroFs(
+ mock.Mock(), 'root_dir',
+ custom_args={'compression': 'zstd,level=21'}
+ )
+
+ @patch('os.path.exists')
+ def setup_method(self, cls, mock_exists):
+ self.setup()
+
+ @patch('kiwi.filesystem.erofs.Command.run')
+ def test_create_on_file(self, mock_command):
+ Defaults.set_platform_name('x86_64')
+ self.erofs.create_on_file('myimage', 'label')
+ mock_command.assert_called_once_with(
+ [
+ 'mkfs.erofs', '-z', 'zstd,level=21',
+ '-L', 'label', 'myimage', 'root_dir'
+ ]
+ )
+
+ @patch('kiwi.filesystem.erofs.Command.run')
+ def test_create_on_file_exclude_data(self, mock_command):
+ Defaults.set_platform_name('x86_64')
+ self.erofs.create_on_file('myimage', 'label', ['foo'])
+ mock_command.assert_called_once_with(
+ [
+ 'mkfs.erofs', '-z', 'zstd,level=21',
+ '-L', 'label', '--exclude-regex=foo',
+ 'myimage', 'root_dir'
+ ]
+ )