From 9ae6236bed325d20a5bbad6461f326525183f097 Mon Sep 17 00:00:00 2001 From: Vladimir Skvortsov Date: Tue, 14 Jul 2026 09:44:23 +0200 Subject: [PATCH] Support configurable sector size for raw/WIC images Add a --raw-sector-size option, plus tcbuild sector-size: (input) and base-sector-size: (output) keys, so the raw/WIC image path can operate on 4Kn media such as some UFS targets. The value is threaded to every libguestfs open via add_drive_opts(blocksize=...). virt-resize drives libguestfs at 512 bytes and cannot open a 4Kn disk, so for non-512 sectors the output image is grown with libguestfs directly: the file is enlarged and the last (rootfs) partition is extended to fill it, preserving the partition's GPT identity (name, type, GUID and attributes), then reformatted as usual. Default stays 512, so existing 512-byte-sector images are unaffected. Signed-off-by: Vladimir Skvortsov --- tcbuilder/backend/combine.py | 13 +++-- tcbuilder/backend/common.py | 28 ++++++++-- tcbuilder/backend/deploy.py | 75 ++++++++++++++++++++++++--- tcbuilder/backend/images.py | 39 +++++++++----- tcbuilder/backend/tcbuild.schema.yaml | 8 +++ tcbuilder/cli/build.py | 10 +++- tcbuilder/cli/combine.py | 6 ++- tcbuilder/cli/deploy.py | 12 +++-- tcbuilder/cli/images.py | 9 ++-- tcbuilder/cli/tcbuild.template.yaml | 6 +++ 10 files changed, 165 insertions(+), 41 deletions(-) diff --git a/tcbuilder/backend/combine.py b/tcbuilder/backend/combine.py index aa4cf42d..c670d506 100755 --- a/tcbuilder/backend/combine.py +++ b/tcbuilder/backend/combine.py @@ -15,7 +15,7 @@ from tcbuilder.backend.common import \ (set_output_ownership, check_licence_acceptance, run_with_loading_animation, open_disk_image, get_tar_compress_program_options, DOCKER_BUNDLE_TARNAME, TAR_EXT_TO_PROGRAM, - OSTREE_SOTA_DIR_PATH) + OSTREE_SOTA_DIR_PATH, DEFAULT_RAW_SECTOR_SIZE) from tcbuilder.errors import InvalidStateError, InvalidDataError, TorizonCoreBuilderError log = logging.getLogger("torizon." + __name__) @@ -290,7 +290,9 @@ def combine_tezi_image(image_dir, bundle_dir, output_directory, tezi_props, forc combine_single_tezi_image(**combine_params) -def combine_raw_image(image_path, bundle_dir, output_path, rootfs_label, force): +# pylint: disable-next=too-many-positional-arguments,too-many-locals +def combine_raw_image(image_path, bundle_dir, output_path, rootfs_label, force, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """ Combine a container bundle to a raw disk image by copying its contents to the image root filesystem. If the size of the container bundle exceeds the available space in root, @@ -301,6 +303,7 @@ def combine_raw_image(image_path, bundle_dir, output_path, rootfs_label, force): :param output_path: Path where the resulting raw disk image will be created. :param rootfs_label: Filesystem label of the root partition. :param force: Overwrite output file if it already exists. + :param sector_size: Logical sector size of the image, in bytes. """ delete_on_error = True @@ -335,7 +338,8 @@ def combine_raw_image(image_path, bundle_dir, output_path, rootfs_label, force): req_space_kb = get_bundle_size_kb(files_to_add, bundle_dir) - with open_disk_image(output_path, delete_on_error=delete_on_error) as gfs: + with open_disk_image(output_path, delete_on_error=delete_on_error, + sector_size=sector_size) as gfs: root_partition = gfs.findfs_label(rootfs_label) gfs.mount(root_partition, "/") statvfs_dict = gfs.statvfs("/") @@ -374,7 +378,8 @@ def combine_raw_image(image_path, bundle_dir, output_path, rootfs_label, force): log.debug("Deleting '%s'", tmp_image) os.remove(tmp_image) - with open_disk_image(output_path, delete_on_error=delete_on_error) as gfs: + with open_disk_image(output_path, delete_on_error=delete_on_error, + sector_size=sector_size) as gfs: gfs.mount(root_partition, "/") copy_to_mounted_root(gfs, files_to_add, bundle_dir) diff --git a/tcbuilder/backend/common.py b/tcbuilder/backend/common.py index a28691fa..6e53d9c3 100644 --- a/tcbuilder/backend/common.py +++ b/tcbuilder/backend/common.py @@ -52,6 +52,9 @@ DEFAULT_RAW_ROOTFS_LABEL = "otaroot" +DEFAULT_RAW_SECTOR_SIZE = 512 +SUPPORTED_RAW_SECTOR_SIZES = (512, 4096) + TEZI_PROP_TO_ARGNAME = { "name": "--image-name", "description": "--image-description", @@ -63,11 +66,13 @@ } RAW_PROP_TO_ARGNAME = { - "raw_rootfs_label": "--raw-rootfs-label" + "raw_rootfs_label": "--raw-rootfs-label", + "raw_sector_size": "--raw-sector-size" } RAW_PROP_DEFAULTS = { - "raw_rootfs_label" : DEFAULT_RAW_ROOTFS_LABEL + "raw_rootfs_label" : DEFAULT_RAW_ROOTFS_LABEL, + "raw_sector_size" : DEFAULT_RAW_SECTOR_SIZE } TAR_EXT_TO_PROGRAM = { @@ -305,6 +310,12 @@ def add_common_raw_image_arguments(subparser): default=None) # Default is in RAW_PROP_DEFAULTS. Default should only be # set if arg is used in a raw image. # Arg value should remain None if a tezi image is used. + subparser.add_argument(RAW_PROP_TO_ARGNAME["raw_sector_size"], dest="raw_sector_size", + metavar="BYTES", type=int, choices=SUPPORTED_RAW_SECTOR_SIZES, + help="(raw images only) logical sector size of the source WIC/raw " + "image, in bytes. Use 4096 for 4Kn media (e.g. some UFS " + f"targets). (default: {DEFAULT_RAW_SECTOR_SIZE})", + default=None) # Default is in RAW_PROP_DEFAULTS; see raw_rootfs_label. def add_ssh_arguments(subparser): @@ -1058,12 +1069,19 @@ def is_file_type_fit(file_path): @contextmanager -def open_disk_image(image_path, *, delete_on_error=False, readonly=False, loading_msg=None): - """Create a context manager to open and manipulate raw disk images.""" +def open_disk_image(image_path, *, delete_on_error=False, readonly=False, loading_msg=None, + sector_size=DEFAULT_RAW_SECTOR_SIZE): + """Create a context manager to open and manipulate raw disk images. + + :param sector_size: Logical sector size of the image, in bytes. Must be + passed for 4Kn media so libguestfs can read the GPT. + """ try: gfs = guestfs.GuestFS(python_return_dict=True) - gfs.add_drive_opts(image_path, format="raw", readonly=readonly) + # blocksize optarg needs libguestfs >= 1.44; omit it on the default path. + extra = {"blocksize": sector_size} if sector_size != DEFAULT_RAW_SECTOR_SIZE else {} + gfs.add_drive_opts(image_path, format="raw", readonly=readonly, **extra) run_with_loading_animation( func=gfs.launch, loading_msg=loading_msg or "Initializing image...") diff --git a/tcbuilder/backend/deploy.py b/tcbuilder/backend/deploy.py index e66e93fa..3fb3b054 100644 --- a/tcbuilder/backend/deploy.py +++ b/tcbuilder/backend/deploy.py @@ -20,7 +20,8 @@ from tcbuilder.backend import ostree from tcbuilder.backend.common import (get_rootfs_tarball, resolve_remote_host, run_with_loading_animation, open_disk_image, - REMOTE_CMD_TIMEOUT, SECBOOT_ARTIFACTS_DIR) + REMOTE_CMD_TIMEOUT, SECBOOT_ARTIFACTS_DIR, + DEFAULT_RAW_SECTOR_SIZE) from tcbuilder.backend.rforward import reverse_forward_tunnel, request_port_forward from tcbuilder.backend.secboot import FUSE_CMD_TXT_NAME from tcbuilder.errors import TorizonCoreBuilderError, InvalidDataError @@ -324,8 +325,48 @@ def deploy_tezi_image(tezi_dir, src_sysroot_dir, src_ostree_archive_dir, copy_signed_artifacts(commit_dir, output_dir) +def grow_last_partition(raw_img, added_size_kb, sector_size, rootfs_partition): + """Enlarge a raw image and extend its last partition to fill the new space. + + For 4Kn images, where virt-resize cannot operate. Preserves the partition's + GPT identity (name, type, GUID, attributes) so it still boots; the rootfs + must be the last partition, which is checked. + """ + # Round up to a whole sector; a non-sector-multiple size can be rejected at 4Kn. + new_size = os.path.getsize(raw_img) + int(added_size_kb) * 1024 + new_size = (new_size + sector_size - 1) // sector_size * sector_size + subprocess.check_output(["truncate", "-s", str(new_size), raw_img]) + + with open_disk_image(raw_img, sector_size=sector_size) as gfs: + dev = "/dev/sda" + gfs.part_expand_gpt(dev) # relocate the GPT backup header to the new end + partnum = len(gfs.list_partitions()) + if gfs.part_to_partnum(rootfs_partition) != partnum: + raise TorizonCoreBuilderError( + "the rootfs must be the last partition to grow a 4Kn raw image.") + start_sector = gfs.part_list(dev)[-1]["part_start"] // sector_size + + name = gfs.part_get_name(dev, partnum) + gpt_type = gfs.part_get_gpt_type(dev, partnum) + gpt_guid = gfs.part_get_gpt_guid(dev, partnum) + gpt_attributes = gfs.part_get_gpt_attributes(dev, partnum) + + # Stop short of the disk end to clear the GPT backup (33 LBAs of 512 B). + gpt_tail = (33 * 512 + sector_size - 1) // sector_size + end_sector = os.path.getsize(raw_img) // sector_size - 1 - gpt_tail + gfs.part_del(dev, partnum) + gfs.part_add(dev, "primary", start_sector, end_sector) + + gfs.part_set_name(dev, partnum, name) + gfs.part_set_gpt_type(dev, partnum, gpt_type) + gfs.part_set_gpt_guid(dev, partnum, gpt_guid) + gfs.part_set_gpt_attributes(dev, partnum, gpt_attributes) + + +# pylint: disable-next=too-many-positional-arguments def create_output_raw_image(base_raw_img, output_raw_img, base_rootfs_partition, - other_partitions_size_kb, rootfs_size_kb): + other_partitions_size_kb, rootfs_size_kb, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """Create a new raw disk image based on an existing one. The result is a copy of the base disk image with all partitions except @@ -337,6 +378,21 @@ def create_output_raw_image(base_raw_img, output_raw_img, base_rootfs_partition, IMAGE_OVERHEAD_FACTOR*(rootfs_size_kb + other_partitions_size_kb)) out_size_kb += EXTRA_ROOTFS_SIZE_KB + if sector_size != DEFAULT_RAW_SECTOR_SIZE: + # virt-resize drives libguestfs at the default 512-byte sector size and + # cannot open a 4Kn disk, so grow the image with libguestfs directly and + # let write_rootfs_to_raw_image() reformat the enlarged last partition. + shutil.copy2(base_raw_img, output_raw_img) + if out_size_kb > base_img_size_kb: + added_size_kb = out_size_kb - base_img_size_kb + log.info(f"Adding {added_size_kb/1024:.2f} MiB to output image.") + log.info(f"Size of output image will be: {out_size_kb/1024/1024:.2f} GiB") + grow_last_partition(output_raw_img, added_size_kb, sector_size, + base_rootfs_partition) + else: + log.info("Output image will have the same size as the base one.") + return + # Copy of the base disk will be the starting point of the output disk shutil.copy2(base_raw_img, output_raw_img) log.debug(f"Created output image: {output_raw_img}") @@ -361,7 +417,8 @@ def create_output_raw_image(base_raw_img, output_raw_img, base_rootfs_partition, print("------------------------------------------------------------") -def write_rootfs_to_raw_image(raw_disk_img, rootfs_label, rootfs_dir): +def write_rootfs_to_raw_image(raw_disk_img, rootfs_label, rootfs_dir, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """Writes unpacked rootfs contents to a raw disk image Creates a new rootfs ext4 partition with a given label on the last partition @@ -369,7 +426,7 @@ def write_rootfs_to_raw_image(raw_disk_img, rootfs_label, rootfs_dir): """ loading_msg="Initializing output image..." - with open_disk_image(raw_disk_img, loading_msg=loading_msg) as gfs: + with open_disk_image(raw_disk_img, loading_msg=loading_msg, sector_size=sector_size) as gfs: # virt-resize rearranged all existing partitions and generated a new empty partition at the # end of the disk. We will format it to ext4 and put the unpacked rootfs contents in it. # Its partition number (/dev/sda1, /dev/sda2, etc.) is equal to the number of partitions @@ -395,7 +452,8 @@ def write_rootfs_to_raw_image(raw_disk_img, rootfs_label, rootfs_dir): # pylint: disable-next=too-many-positional-arguments def deploy_raw_image(base_raw_img, src_sysroot_dir, src_ostree_archive_dir, - output_raw_img, dst_sysroot_dir, rootfs_label, *, ref=None): + output_raw_img, dst_sysroot_dir, rootfs_label, *, ref=None, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """Deploys a WIC image with given OSTree reference Creates a new WIC image with an OSTree deployment of the @@ -404,7 +462,8 @@ def deploy_raw_image(base_raw_img, src_sysroot_dir, src_ostree_archive_dir, deploy_ostree_local(src_sysroot_dir, src_ostree_archive_dir, dst_sysroot_dir, ref) loading_msg="Initializing base WIC/raw image..." - with open_disk_image(base_raw_img, readonly=True, loading_msg=loading_msg) as gfs: + with open_disk_image(base_raw_img, readonly=True, loading_msg=loading_msg, + sector_size=sector_size) as gfs: # Get partition number from ext4 fs called rootfs_label in disk image (.wic/.img) rootfs_partition = gfs.findfs_label(rootfs_label) log.info(f" '{rootfs_label}' partition found: {rootfs_partition}") @@ -424,9 +483,9 @@ def deploy_raw_image(base_raw_img, src_sysroot_dir, src_ostree_archive_dir, log.info(f"Unpacked rootfs size: {rootfs_size_kb/1024/1024:.2f} GiB") create_output_raw_image(base_raw_img, output_raw_img, rootfs_partition, - other_partitions_size_kb, rootfs_size_kb) + other_partitions_size_kb, rootfs_size_kb, sector_size) - write_rootfs_to_raw_image(output_raw_img, rootfs_label, dst_sysroot_dir) + write_rootfs_to_raw_image(output_raw_img, rootfs_label, dst_sysroot_dir, sector_size) log.info(f"Image {os.path.basename(output_raw_img)} created successfully!") diff --git a/tcbuilder/backend/images.py b/tcbuilder/backend/images.py index 85a4a431..5c9c8f3c 100644 --- a/tcbuilder/backend/images.py +++ b/tcbuilder/backend/images.py @@ -25,6 +25,7 @@ set_output_ownership, run_with_loading_animation, get_tezi_image_version, open_disk_image, make_feed_url, fetch_remote, RAW_PROP_TO_ARGNAME, DEFAULT_RAW_ROOTFS_LABEL, + DEFAULT_RAW_SECTOR_SIZE, SECBOOT_ARTIFACTS_DIR, REMOTE_CMD_TIMEOUT, TAR_EXT_TO_PROGRAM, OSTREE_SOTA_DIR_PATH, LEGACY_VARIANT_PREFIX, VARIANT_PREFIX, SYNAIMG_MACHINES) @@ -312,13 +313,14 @@ def unpack_local_tezi_image(image_dir_or_file, tezi_dir): raise TorizonCoreBuilderError(f"Image does not exist: {image_dir_or_file}") -def unpack_local_raw_image(image_dir, sysroot_dir, raw_rootfs_label): +def unpack_local_raw_image(image_dir, sysroot_dir, raw_rootfs_label, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """Extract the root fs from the image into the sysroot directory""" if raw_rootfs_label is None: raw_rootfs_label = DEFAULT_RAW_ROOTFS_LABEL - with open_disk_image(image_dir, readonly=True) as gfs: + with open_disk_image(image_dir, readonly=True, sector_size=sector_size) as gfs: # Get partition number from ext4 fs called raw_rootfs_label in disk image (.wic/.img) rootfs_partition = gfs.findfs_label(raw_rootfs_label) log.info(f"'{raw_rootfs_label}' partition found: {rootfs_partition}") @@ -338,8 +340,9 @@ def _make_tezi_extract_dir(tezi_dir): return extract_dir +# pylint: disable-next=too-many-positional-arguments,too-many-locals def import_local_image(image_dir_or_file, tezi_dir, src_sysroot_dir, src_ostree_archive_dir, - raw_rootfs_label=None): + raw_rootfs_label=None, raw_sector_size=None): """Import local raw/WIC or Toradex Easy Installer image. Import local raw/WIC or Toradex Easy installer image (archive file or unpacked @@ -350,12 +353,14 @@ def import_local_image(image_dir_or_file, tezi_dir, src_sysroot_dir, src_ostree_ if ((image_dir_or_file.lower().endswith(".wic") or image_dir_or_file.lower().endswith(".img")) and os.path.isfile(image_dir_or_file)): - unpack_local_raw_image(image_dir_or_file, src_sysroot_dir, raw_rootfs_label) + unpack_local_raw_image(image_dir_or_file, src_sysroot_dir, raw_rootfs_label, + raw_sector_size or DEFAULT_RAW_SECTOR_SIZE) else: unpack_local_tezi_image(image_dir_or_file, tezi_dir) common_raw_props_args = { - "raw_rootfs_label": raw_rootfs_label + "raw_rootfs_label": raw_rootfs_label, + "raw_sector_size": raw_sector_size } # pylint: disable-next=consider-using-dict-items for prop in common_raw_props_args: @@ -425,7 +430,8 @@ def check_provdata_presence_in_raw_image(gfs): return True -def _prov_get_image_version_raw_image(disk_img, rootfs_label): +def _prov_get_image_version_raw_image(disk_img, rootfs_label, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """Check disk image for any existing provisioning data, also returning the img version. :param disk_img: path to disk image. @@ -437,7 +443,7 @@ def _prov_get_image_version_raw_image(disk_img, rootfs_label): the tuple entry will be 'None'. """ - with open_disk_image(disk_img, readonly=True) as gfs: + with open_disk_image(disk_img, readonly=True, sector_size=sector_size) as gfs: rootfs_partition = gfs.findfs_label(rootfs_label) log.info(f"'{rootfs_label}' partition found: {rootfs_partition}") gfs.mount_ro(rootfs_partition, "/") @@ -577,7 +583,8 @@ def prov_check_supported_img_features(img_version, online_data, hibernated, flee log.debug("Found image version: %d.%d", img_major, img_minor) -def prov_check_input(input_path, output_path, rootfs_label): +def prov_check_input(input_path, output_path, rootfs_label, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """Check provisioning inputs passed by the user.""" is_tezi_img = False @@ -594,7 +601,7 @@ def prov_check_input(input_path, output_path, rootfs_label): if rootfs_label is None: rootfs_label = DEFAULT_RAW_ROOTFS_LABEL - img_version = _prov_get_image_version_raw_image(input_path, rootfs_label) + img_version = _prov_get_image_version_raw_image(input_path, rootfs_label, sector_size) # If TEZI image else: @@ -616,7 +623,8 @@ def prov_check_input(input_path, output_path, rootfs_label): def provision(input_path, output_path, rootfs_label, prov_data, *, - hibernated=False, fleets=None, force=False): + hibernated=False, fleets=None, force=False, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """Generate OS image with added provisioning data :param input_path: Path containing Easy Installer directory or disk image. @@ -633,7 +641,8 @@ def provision(input_path, output_path, rootfs_label, prov_data, *, """ # Basic validations: - img_version, is_tezi_img, rootfs_label = prov_check_input(input_path, output_path, rootfs_label) + img_version, is_tezi_img, rootfs_label = prov_check_input( + input_path, output_path, rootfs_label, sector_size) prov_check_supported_img_features(img_version, prov_data['online'], hibernated, fleets) @@ -665,7 +674,7 @@ def provision(input_path, output_path, rootfs_label, prov_data, *, if is_tezi_img: prov_tezi_img(output_path, prov_data, hibernated, fleets) else: - prov_raw_img(rootfs_label, output_path, prov_data, hibernated, fleets) + prov_raw_img(rootfs_label, output_path, prov_data, hibernated, fleets, sector_size) log.info("Image successfully provisioned.") @@ -692,11 +701,13 @@ def prov_tezi_img(output_path, prov_data, hibernated, fleets): set_output_ownership(output_path) -def prov_raw_img(rootfs_label, output_path, prov_data, hibernated, fleets): +# pylint: disable-next=too-many-positional-arguments +def prov_raw_img(rootfs_label, output_path, prov_data, hibernated, fleets, + sector_size=DEFAULT_RAW_SECTOR_SIZE): """Create provisioning data and add it to the root filesystem of the disk image.""" msg = f"Initializing {output_path}..." - with (open_disk_image(output_path, loading_msg=msg) as gfs, + with (open_disk_image(output_path, loading_msg=msg, sector_size=sector_size) as gfs, tempfile.TemporaryDirectory() as tmpdir): rootfs_partition = gfs.findfs_label(rootfs_label) gfs.mount(rootfs_partition, "/") diff --git a/tcbuilder/backend/tcbuild.schema.yaml b/tcbuilder/backend/tcbuild.schema.yaml index 2cebb142..16c02d72 100644 --- a/tcbuilder/backend/tcbuild.schema.yaml +++ b/tcbuilder/backend/tcbuild.schema.yaml @@ -180,6 +180,10 @@ properties: rootfs-label: type: string description: "label of the filesystem where rootfs is located" + sector-size: + type: integer + enum: [512, 4096] + description: "logical sector size of the image in bytes (use 4096 for 4Kn media)" additionalProperties: false required: - local @@ -571,6 +575,10 @@ properties: base-rootfs-label: type: string description: "filesystem label where rootfs is located in base image" + base-sector-size: + type: integer + enum: [512, 4096] + description: "logical sector size of the base image in bytes (use 4096 for 4Kn media)" base-image: type: string description: "base image used for the output raw image" diff --git a/tcbuilder/cli/build.py b/tcbuilder/cli/build.py index 4c4a83d3..0161f746 100644 --- a/tcbuilder/cli/build.py +++ b/tcbuilder/cli/build.py @@ -170,6 +170,7 @@ def handle_raw_image_input(props, download_dir=None): images_cli.images_unpack( props["local"], raw_rootfs_label=props.get("rootfs-label", common.DEFAULT_RAW_ROOTFS_LABEL), + raw_sector_size=props.get("sector-size", common.DEFAULT_RAW_SECTOR_SIZE), remove_storage=True) base_raw_image = props["local"] @@ -200,6 +201,7 @@ def handle_raw_image_input(props, download_dir=None): images_cli.images_unpack( local_file, raw_rootfs_label=props.get("rootfs-label", common.DEFAULT_RAW_ROOTFS_LABEL), + raw_sector_size=props.get("sector-size", common.DEFAULT_RAW_SECTOR_SIZE), remove_storage=True) base_raw_image = local_file @@ -643,6 +645,7 @@ def handle_raw_image_output(props, union_params, default_base_raw_image): base_raw_img = props.get("base-image", default_base_raw_image) base_rootfs_label = props.get("base-rootfs-label", common.DEFAULT_RAW_ROOTFS_LABEL) + base_sector_size = props.get("base-sector-size", common.DEFAULT_RAW_SECTOR_SIZE) deploy_raw_image_params = { "ostree_ref": union_params["union_branch"], @@ -650,6 +653,7 @@ def handle_raw_image_output(props, union_params, default_base_raw_image): "output_raw_img": output_raw_img, "deploy_sysroot_dir": deploy_cli.DEFAULT_DEPLOY_DIR, "rootfs_label": base_rootfs_label, + "sector_size": base_sector_size, } deploy_cli.deploy_raw_image(**deploy_raw_image_params) @@ -813,7 +817,11 @@ def handle_raw_image_bundle_output(image_dir, raw_image_path, bundle_props, raw_ "rootfs-label", common.DEFAULT_RAW_ROOTFS_LABEL ), - "force": True + "force": True, + "sector_size": raw_props.get( + "base-sector-size", + common.DEFAULT_RAW_SECTOR_SIZE + ) } comb_be.combine_raw_image(**combine_params) diff --git a/tcbuilder/cli/combine.py b/tcbuilder/cli/combine.py index 87a5b250..94fbdb7b 100755 --- a/tcbuilder/cli/combine.py +++ b/tcbuilder/cli/combine.py @@ -79,7 +79,8 @@ def do_combine(args): } raw_props_args = { - "raw_rootfs_label" : args.raw_rootfs_label + "raw_rootfs_label" : args.raw_rootfs_label, + "raw_sector_size" : args.raw_sector_size } # If raw image: @@ -106,7 +107,8 @@ def do_combine(args): try: combine.combine_raw_image(image_path, dir_containers, output_path, - raw_props_args["raw_rootfs_label"], args.force) + raw_props_args["raw_rootfs_label"], args.force, + raw_props_args["raw_sector_size"]) finally: docker_storage_dir_path = os.path.join(dir_containers, combine.DOCKER_STORAGE_DIRNAME) if os.path.isdir(docker_storage_dir_path): diff --git a/tcbuilder/cli/deploy.py b/tcbuilder/cli/deploy.py index ba1791d9..dccb9182 100644 --- a/tcbuilder/cli/deploy.py +++ b/tcbuilder/cli/deploy.py @@ -65,7 +65,8 @@ def deploy_tezi_image(*, ostree_ref, output_dir, deploy_sysroot_dir, tezi_props= def deploy_raw_image(*, ostree_ref, base_raw_img, - output_raw_img, deploy_sysroot_dir, rootfs_label): + output_raw_img, deploy_sysroot_dir, rootfs_label, + sector_size=common.DEFAULT_RAW_SECTOR_SIZE): common.images_unpack_executed() if common.unpacked_image_type() != "raw": @@ -99,7 +100,7 @@ def deploy_raw_image(*, ostree_ref, base_raw_img, dbe.deploy_raw_image(base_raw_img, src_sysroot_dir, src_ostree_archive_dir, output_raw_img_, dst_sysroot_dir_, rootfs_label, - ref=ostree_ref) + ref=ostree_ref, sector_size=sector_size) def do_deploy_tezi_image(args): @@ -116,6 +117,7 @@ def do_deploy_tezi_image(args): raw_props_args = { "raw_rootfs_label" : args.raw_rootfs_label, + "raw_sector_size" : args.raw_sector_size, "output_raw_image" : args.output_raw_image } @@ -149,7 +151,8 @@ def do_deploy_raw_image(args): } common_raw_props_args = { - "raw_rootfs_label" : args.raw_rootfs_label + "raw_rootfs_label" : args.raw_rootfs_label, + "raw_sector_size" : args.raw_sector_size } # Check for tezi-specific args being set: @@ -170,7 +173,8 @@ def do_deploy_raw_image(args): base_raw_img=args.base_raw_image, output_raw_img=args.output_raw_image, deploy_sysroot_dir=args.deploy_sysroot_directory, - rootfs_label=common_raw_props_args["raw_rootfs_label"]) + rootfs_label=common_raw_props_args["raw_rootfs_label"], + sector_size=common_raw_props_args["raw_sector_size"]) def deploy_ostree_remote(*, remote_host, remote_port, diff --git a/tcbuilder/cli/images.py b/tcbuilder/cli/images.py index 129bd433..5cab8f73 100644 --- a/tcbuilder/cli/images.py +++ b/tcbuilder/cli/images.py @@ -131,7 +131,8 @@ def do_images_provision(args): prov_data=prov_data, hibernated=args.hibernated, fleets=args.fleets, - force=args.force) + force=args.force, + sector_size=args.raw_sector_size or common.DEFAULT_RAW_SECTOR_SIZE) except (TorizonCoreBuilderError, TeziError) as exc: log.error(f"Error: {str(exc)}") @@ -145,13 +146,14 @@ def do_images_serve(args): images.serve(args.images_directory) -def images_unpack(image_dir, *, raw_rootfs_label=None, remove_storage=False): +def images_unpack(image_dir, *, raw_rootfs_label=None, raw_sector_size=None, + remove_storage=False): """Main handler for the 'images unpack' subcommand""" image_dir = os.path.abspath(image_dir) dir_list = prepare_storage(remove_storage) images.import_local_image(image_dir, dir_list[0], dir_list[1], - dir_list[2], raw_rootfs_label) + dir_list[2], raw_rootfs_label, raw_sector_size) def do_images_unpack(args): @@ -159,6 +161,7 @@ def do_images_unpack(args): images_unpack( args.image_directory, raw_rootfs_label=args.raw_rootfs_label, + raw_sector_size=args.raw_sector_size, remove_storage=args.remove_storage) diff --git a/tcbuilder/cli/tcbuild.template.yaml b/tcbuilder/cli/tcbuild.template.yaml index 439ccc0e..d286138b 100644 --- a/tcbuilder/cli/tcbuild.template.yaml +++ b/tcbuilder/cli/tcbuild.template.yaml @@ -42,6 +42,9 @@ input: # >> (OPTIONAL) specify the filesystem label where rootfs is located in the image. # >> If not defined it defaults to 'otaroot' # rootfs-label: otaroot + # >> (OPTIONAL) logical sector size of the image in bytes: 512 or 4096. + # >> Use 4096 for 4Kn media (e.g. some UFS targets). Defaults to 512. + # sector-size: 512 # >> (2) Remote file (optionally with a filename and or a sha256 checksum): # remote: "https://artifacts.toradex.com/.../torizon-docker-intel-corei7-64-7.6.0+build.8.wic;sha256sum=caa414778735d48b1f5c582bf7ae8f0c596291eaac67582945a0da54f6a7e609" # >> (3) Image specification (URL will be generated by the tool) @@ -167,6 +170,9 @@ output: # >> (OPTIONAL) specify the filesystem label where rootfs is located in the base image. # >> If not defined it defaults to 'otaroot'. # base-rootfs-label: otaroot + # >> (OPTIONAL) logical sector size of the base image in bytes: 512 or 4096. + # >> Use 4096 for 4Kn media (e.g. some UFS targets). Defaults to 512. + # base-sector-size: 512 # >> (OPTIONAL) base image for the output. # >> If not set it will default to be the same as the input one. # >> Do not change this unless you know what you're doing.