From 2df6d9b91415241b9d16cd96dd78f9b15e4e8e26 Mon Sep 17 00:00:00 2001
From: Julia Kreger <juliaashleykreger@gmail.com>
Date: Thu, 29 Aug 2024 17:17:20 -0700
Subject: [PATCH] 4k Block device support

Adds support for the block device size to be asserted for the purpose of
writing out new images, which may be critical for operators with hardware
which requires logical blocks which are 4096 bytes long.

Change-Id: I5c16a042eacfbb94a905b93a0eb9fbc73de0a890
---
 .../block_device/level0/localloop.py          | 20 ++++++++++---
 .../block_device/level1/partitioning.py       |  7 ++++-
 .../block_device/tests/test_gpt.py            |  2 +-
 .../elements/block-device-efi-4k/README.rst   | 19 ++++++++++++
 .../block-device-default.yaml                 | 30 +++++++++++++++++++
 .../block-device-efi-4k/element-provides      |  1 +
 .../environment.d/15-block-device.bash        | 10 +++++++
 doc/source/user_guide/building_an_image.rst   | 16 ++++++++++
 ...block-device-support-8397c2b6122d864c.yaml | 10 +++++++
 9 files changed, 109 insertions(+), 6 deletions(-)
 create mode 100644 diskimage_builder/elements/block-device-efi-4k/README.rst
 create mode 100644 diskimage_builder/elements/block-device-efi-4k/block-device-default.yaml
 create mode 100644 diskimage_builder/elements/block-device-efi-4k/element-provides
 create mode 100644 diskimage_builder/elements/block-device-efi-4k/environment.d/15-block-device.bash
 create mode 100644 releasenotes/notes/add-4k-block-device-support-8397c2b6122d864c.yaml

diff --git a/diskimage_builder/block_device/level0/localloop.py b/diskimage_builder/block_device/level0/localloop.py
index d5bd2c2e6..aaf8b5560 100644
--- a/diskimage_builder/block_device/level0/localloop.py
+++ b/diskimage_builder/block_device/level0/localloop.py
@@ -38,10 +38,16 @@ def image_delete(filename):
     os.remove(filename)
 
 
-def loopdev_attach(filename):
+def loopdev_attach(filename, block_size):
+    if str(block_size) not in ['512', '4096']:
+        logger.warning("Block device size is set to %s, only 512 and "
+                       "4096 has been tested.", block_size)
     logger.info("loopdev attach")
-    logger.debug("Calling [sudo losetup --show -f %s]", filename)
-    block_device = exec_sudo(["losetup", "--show", "-f", filename])
+    log_msg = ("Calling [sudo losetup --sector-size %s --show -f %s]"
+               % (str(block_size), filename))
+    logger.debug(log_msg)
+    block_device = exec_sudo(["losetup", "--sector-size", str(block_size),
+                              "--show", "-f", filename])
     # [:-1]: Cut of the newline
     block_device = block_device[:-1]
     logger.info("New block device [%s]", block_device)
@@ -85,6 +91,12 @@ class LocalLoopNode(NodeBase):
             self.image_dir = config['directory']
         else:
             self.image_dir = default_config['image-dir']
+        if 'DIB_BLOCK_SIZE' in os.environ:
+            self.block_size = os.environ['DIB_BLOCK_SIZE']
+        elif 'block_size' in config:
+            self.block_size = config['block_size']
+        else:
+            self.block_size = 512
         self.filename = os.path.join(self.image_dir, self.name + ".raw")
 
     def get_edges(self):
@@ -98,7 +110,7 @@ class LocalLoopNode(NodeBase):
         self.add_rollback(image_delete, self.filename)
         image_create(self.filename, self.size)
 
-        block_device = loopdev_attach(self.filename)
+        block_device = loopdev_attach(self.filename, self.block_size)
         self.add_rollback(loopdev_detach, block_device)
 
         if 'blockdev' not in self.state:
diff --git a/diskimage_builder/block_device/level1/partitioning.py b/diskimage_builder/block_device/level1/partitioning.py
index 31ef8e54c..d3ea2a637 100644
--- a/diskimage_builder/block_device/level1/partitioning.py
+++ b/diskimage_builder/block_device/level1/partitioning.py
@@ -95,6 +95,8 @@ class Partitioning(PluginBase):
 
     def _create_mbr(self):
         """Create partitions with MBR"""
+        # NOTE(TheJulia): This is funcitonally incompatible with block/sector
+        # sizing other than 512 bytes.
         with MBR(self.image_path, self.disk_size, self.align) as part_impl:
             for part_cfg in self.partitions:
                 part_name = part_cfg.get_name()
@@ -127,7 +129,10 @@ class Partitioning(PluginBase):
     def _create_gpt(self):
         """Create partitions with GPT"""
 
-        cmd = ['sgdisk', self.image_path]
+        # Use the loopback via device_path, as using the file means the
+        # partitioning is exposed to sector sizing of the OS, not of the
+        # underlying "device" provided by the loopback.
+        cmd = ['sgdisk', self.device_path]
 
         # This padding gives us a little room for rounding so we don't
         # go over the end of the disk
diff --git a/diskimage_builder/block_device/tests/test_gpt.py b/diskimage_builder/block_device/tests/test_gpt.py
index 5c4c80bee..cba3fe87b 100644
--- a/diskimage_builder/block_device/tests/test_gpt.py
+++ b/diskimage_builder/block_device/tests/test_gpt.py
@@ -63,7 +63,7 @@ class TestGPT(tc.TestGraphGeneration):
                 node.create()
 
         # check the parted call looks right
-        parted_cmd = ['sgdisk', self.image_path,
+        parted_cmd = ['sgdisk', '/dev/loopX',
                       '-n', '1:0:+8M', '-t', '1:EF00', '-c', '1:ESP',
                       '-n', '2:0:+8M', '-t', '2:EF02', '-c', '2:BSP',
                       '-n', '3:0:+1006M', '-t', '3:8300', '-c', '3:Root Part']
diff --git a/diskimage_builder/elements/block-device-efi-4k/README.rst b/diskimage_builder/elements/block-device-efi-4k/README.rst
new file mode 100644
index 000000000..215d30735
--- /dev/null
+++ b/diskimage_builder/elements/block-device-efi-4k/README.rst
@@ -0,0 +1,19 @@
+================
+Block Device EFI
+================
+
+This provides a block-device configuration for the ``vm`` element to
+get a single-partition disk suitable for EFI booting on a block device
+which uses a native 4KiB sector size. This is important because GPT
+partitioning relies on sector boundry placement and the GPT disk partition
+table always starts on the second sector of the disk.
+
+Note on x86 this provides the extra `BIOS boot partition
+<https://en.wikipedia.org/wiki/BIOS_boot_partition>`__ and a EFI boot
+partition for maximum compatability.
+
+This element requires ``mkfs.vfat`` command to be available on the build
+system, usually included in the dosfstools OS package.
+
+Furthermore, the sector size created by this element will not be compatible
+with devices using 512 byte sectors.
diff --git a/diskimage_builder/elements/block-device-efi-4k/block-device-default.yaml b/diskimage_builder/elements/block-device-efi-4k/block-device-default.yaml
new file mode 100644
index 000000000..61fa67cfe
--- /dev/null
+++ b/diskimage_builder/elements/block-device-efi-4k/block-device-default.yaml
@@ -0,0 +1,30 @@
+- local_loop:
+    name: image0
+    block_size: 4096
+- partitioning:
+    base: image0
+    label: gpt
+    partitions:
+      - name: ESP
+        type: 'EF00'
+        size: 500MiB
+        mkfs:
+          type: vfat
+          mount:
+            mount_point: /boot/efi
+            fstab:
+              options: "defaults"
+              fsck-passno: 2
+      - name: BSP
+        type: 'EF02'
+        size: 8MiB
+      - name: root
+        type: '8300'
+        size: 100%
+        mkfs:
+          type: ext4
+          mount:
+            mount_point: /
+            fstab:
+              options: "defaults"
+              fsck-passno: 1
diff --git a/diskimage_builder/elements/block-device-efi-4k/element-provides b/diskimage_builder/elements/block-device-efi-4k/element-provides
new file mode 100644
index 000000000..c0180a2cb
--- /dev/null
+++ b/diskimage_builder/elements/block-device-efi-4k/element-provides
@@ -0,0 +1 @@
+block-device
\ No newline at end of file
diff --git a/diskimage_builder/elements/block-device-efi-4k/environment.d/15-block-device.bash b/diskimage_builder/elements/block-device-efi-4k/environment.d/15-block-device.bash
new file mode 100644
index 000000000..ad8a26745
--- /dev/null
+++ b/diskimage_builder/elements/block-device-efi-4k/environment.d/15-block-device.bash
@@ -0,0 +1,10 @@
+#
+# Arch gate
+#
+
+if [[ "ppc64 ppc64le ppc64el" =~ "$ARCH" ]]; then
+    echo "block-device-efi is not supported on Power; use block-device-gpt or block-device-mbr"
+    exit 1
+fi
+
+export DIB_BLOCK_DEVICE=efi
diff --git a/doc/source/user_guide/building_an_image.rst b/doc/source/user_guide/building_an_image.rst
index ac629e779..c7c3e8780 100644
--- a/doc/source/user_guide/building_an_image.rst
+++ b/doc/source/user_guide/building_an_image.rst
@@ -216,6 +216,22 @@ size
 directory
   (optional) The directory where the image is created.
 
+block_size
+  (optional) Defaults to 512 bytes. Usable to set a different logical block
+  size, or in loopback context sector size, which will govern how partitioning
+  and filesystem utilities will interact with the device and ultimately the
+  block layout on disk.
+  Examples: 512, 4096.
+  This option is critical if you have block devices which natively operate
+  with 4KiB block sizes and need to craft an image to use boot from those
+  devices using a GPT partition table. This setting can also be asserted
+  using a DIB_BLOCK_SIZE environment variable which may be useful for
+  users who need to craft similar, but different block size images without
+  the need to separately maintain different block device YAML documents.
+  Please keep in mind, with larger block sizes, total disk image sizes
+  *and* partition sizes on the disk image will need to be perfectly
+  divisible by the block size being asserted.
+
 Example:
 
 .. code-block:: yaml
diff --git a/releasenotes/notes/add-4k-block-device-support-8397c2b6122d864c.yaml b/releasenotes/notes/add-4k-block-device-support-8397c2b6122d864c.yaml
new file mode 100644
index 000000000..5c218a8d4
--- /dev/null
+++ b/releasenotes/notes/add-4k-block-device-support-8397c2b6122d864c.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Adds the ability for diskimage-builder to create images with different
+    block sizes. By default, this remains at the default of 512 bytes,
+    but some newer devices require 4096 bytes to be used, which impacts
+    the overall layout rendering 512 byte images incompatible. This setting
+    can also be asserted and overridden using the ``DIB_BLOCK_SIZE``
+    environment variable, but alternatively exists as a new ``block_size``
+    parameter for ``local_loop`` section in block device YAML documents.