diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..bbb670659 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +mount_image.sh +umount_image.sh +pack_partitions.sh +unpack_partitions.sh +config.txt +scripts/kernels/* +test.sh diff --git a/scripts/base_image_util.sh b/scripts/base_image_util.sh index e6c87e5a9..1a90283bf 100755 --- a/scripts/base_image_util.sh +++ b/scripts/base_image_util.sh @@ -283,4 +283,7 @@ create_base_image() { ${output_dev} ${USE_DEV_KEYS} --adjust_part="${FLAGS_adjust_part}" fi } +# Example Usage +# +# create_base_image usb true true /dev/sdx create_base_image $1 $2 $3 $4 diff --git a/scripts/bin/cros_make_image_bootable b/scripts/bin/cros_make_image_bootable index aeb5f4b41..caac54459 100755 --- a/scripts/bin/cros_make_image_bootable +++ b/scripts/bin/cros_make_image_bootable @@ -173,7 +173,7 @@ check_kernel_size() { fi } -build_img() { +create_kernel_hash() { local image_name="$1" local root_dev="$2" local root_dev_size="$3" @@ -196,11 +196,10 @@ build_img() { vblock=--hd_vblock="${FLAGS_output_dir}/${vblock}" fi - ./build_kernel_image.sh \ - --board="${FLAGS_board}" \ + ./create_hash.sh \ --arch="${FLAGS_arch}" \ --to="${FLAGS_output_dir}/${image_name}" \ - --vmlinuz="${FLAGS_rootfs_mountpoint}/boot/vmlinuz" \ + --vmlinuz="kernels/bzImage" \ --working_dir="${FLAGS_output_dir}" \ --boot_args="${FLAGS_boot_args}" \ --keep_work \ @@ -294,17 +293,17 @@ make_image_bootable() { "${FLAGS_image_type}" ROOT-A)" local rootfs_fs_size=$(get_filesystem_size "${FLAGS_image_type}" \ "${partition_num_root_a}") - #build_img "vmlinuz.image" "${root_dev}" "${rootfs_fs_size}" "${keyblock}" \ - # "recovery_kernel_data_key.vbprivk" "recovery_key.vbpubk" - #build_img "hd_vmlinuz.image" "${root_dev}" "${rootfs_fs_size}" \ + create_kernel_hash "vmlinuz.image" "${root_dev}" "${rootfs_fs_size}" "${keyblock}" \ + "recovery_kernel_data_key.vbprivk" "recovery_key.vbpubk" + # build_img "hd_vmlinuz.image" "${root_dev}" "${rootfs_fs_size}" \ # "kernel.keyblock" "kernel_data_key.vbprivk" "kernel_subkey.vbpubk" \ # "vmlinuz_hd.vblock" # Check the size of kernel image and issue warning when image size is # near the limit. - local kernel_image_size_A=$(stat -c '%s' kernels/linux.bin) + local kernel_image_size_A=$(stat -c '%s' ${FLAGS_output_dir}/vmlinuz.image) info "Kernel image A size is ${kernel_image_size_A} bytes." - local kernel_image_size_B=$(stat -c '%s' kernels/linux.bin) + local kernel_image_size_B=$(stat -c '%s' ${FLAGS_output_dir}/vmlinuz.image) info "Kernel image B size is ${kernel_image_size_B} bytes." local partition_num_kern_a="$(get_layout_partition_number \ "${FLAGS_image_type}" KERN-A)" @@ -313,48 +312,47 @@ make_image_bootable() { "${FLAGS_image_type}" KERN-B)" check_kernel_size ${kernel_image_size_B} ${partition_num_kern_b} B -# local rootfs_hash_size=$(stat -c '%s' ${FLAGS_rootfs_hash}) -# local rootfs_partition_size=$(get_partition_size ${FLAGS_image_type} \ -# ${partition_num_root_a}) -# local rootfs_hash_pad=$(( rootfs_partition_size - rootfs_fs_size )) -# info "Appending rootfs.hash (${rootfs_hash_size} bytes) to the root fs" -# if [[ ${rootfs_hash_size} -gt ${rootfs_hash_pad} ]] -# then -# die "rootfs_partition_size - rootfs_fs_size is less than the needed " \ -# "rootfs_hash_size (${rootfs_hash_size}), update your disk layout " \ -# "configuration" -# fi -# # Unfortunately, mount_gpt_image uses mount and not losetup to create the -# # loop devices. This means that they are not the correct size. We have to -# # write directly to the image to append the hash tree data. -# local hash_offset="$(partoffset ${image} ${partition_num_root_a})" -# hash_offset=$((hash_offset + (${rootfs_fs_size} / 512))) -# sudo dd bs=512 \ -# seek=${hash_offset} \ -# if="${FLAGS_rootfs_hash}" \ -# of="${image}" \ -# conv=notrunc \ -# status=none -# -# # Move the verification block needed for the hard disk install to the -# # stateful partition. Mount stateful fs, copy file, and umount fs. -# # In original CL: http://codereview.chromium.org/2868044, this was done in -# # create_base_image(). However, it could break the build if it is a clean -# # build because vmlinuz_hd.vblock hasn't been created by build_kernel_image.sh -# # In some builds that don't use vboot to verify the kernel, this file might -# # not get created as part of the build, so only copy them if they were. -# if [ -f "${FLAGS_output_dir}/vmlinuz_hd.vblock" ]; then -# sudo cp "${FLAGS_output_dir}/vmlinuz_hd.vblock" \ -# "${FLAGS_statefulfs_mountpoint}" -# fi + local rootfs_hash_size=$(stat -c '%s' ${FLAGS_rootfs_hash}) + local rootfs_partition_size=$(get_partition_size ${FLAGS_image_type} \ + ${partition_num_root_a}) + local rootfs_hash_pad=$(( rootfs_partition_size - rootfs_fs_size )) + info "Appending rootfs.hash (${rootfs_hash_size} bytes) to the root fs" + if [[ ${rootfs_hash_size} -gt ${rootfs_hash_pad} ]] + then + die "rootfs_partition_size - rootfs_fs_size is less than the needed " \ + "rootfs_hash_size (${rootfs_hash_size}), update your disk layout " \ + "configuration" + fi + # Unfortunately, mount_gpt_image uses mount and not losetup to create the + # loop devices. This means that they are not the correct size. We have to + # write directly to the image to append the hash tree data. + local hash_offset="$(partoffset ${image} ${partition_num_root_a})" + hash_offset=$((hash_offset + (${rootfs_fs_size} / 512))) + sudo dd bs=512 \ + seek=${hash_offset} \ + if="${FLAGS_rootfs_hash}" \ + of="${image}" \ + conv=notrunc \ + status=none + # Move the verification block needed for the hard disk install to the + # stateful partition. Mount stateful fs, copy file, and umount fs. + # In original CL: http://codereview.chromium.org/2868044, this was done in + # create_base_image(). However, it could break the build if it is a clean + # build because vmlinuz_hd.vblock hasn't been created by build_kernel_image.sh + # In some builds that don't use vboot to verify the kernel, this file might + # not get created as part of the build, so only copy them if they were. + if [ -f "${FLAGS_output_dir}/vmlinuz_hd.vblock" ]; then + sudo cp "${FLAGS_output_dir}/vmlinuz_hd.vblock" \ + "${FLAGS_statefulfs_mountpoint}" + fi # Install the kernel to both slots A and B so there will always be a regular - # kernel in slot B on recovery and non-recovery images. + # kernel in slot B on recovery and non-recovery images. local koffset="$(partoffset ${image} ${partition_num_kern_a})" - sudo dd if="kernels/linux.bin" of="${image}" \ + sudo dd if="${FLAGS_output_dir}/vmlinuz.image" of="${image}" \ conv=notrunc bs=512 seek=${koffset} status=none koffset="$(partoffset ${image} ${partition_num_kern_b})" - sudo dd if="kernels/linux.bin" of="${image}" \ + sudo dd if="${FLAGS_output_dir}/vmlinuz.image" of="${image}" \ conv=notrunc bs=512 seek=${koffset} status=none # Update the bootloaders. The EFI system partition will be updated. diff --git a/scripts/bootstub.efi b/scripts/bootstub.efi new file mode 100755 index 000000000..93ed13d8f Binary files /dev/null and b/scripts/bootstub.efi differ diff --git a/scripts/create_hash.sh b/scripts/create_hash.sh new file mode 100755 index 000000000..08f8cc155 --- /dev/null +++ b/scripts/create_hash.sh @@ -0,0 +1,369 @@ +#!/bin/bash + +# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Helper script that generates the signed kernel image + +SCRIPT_ROOT=$(dirname $(readlink -f "$0")) +. "${SCRIPT_ROOT}/common.sh" || exit 1 + +# Flags. +DEFINE_string arch "x86" \ + "The boot architecture: arm, x86, or amd64. (Default: x86)" +DEFINE_string to "/tmp/vmlinuz.image" \ + "The path to the kernel image to be created. (Default: /tmp/vmlinuz.image)" +DEFINE_string hd_vblock "" \ + "The path to the installed kernel's vblock" +DEFINE_string vmlinuz "vmlinuz" \ + "The path to the kernel (Default: vmlinuz)" +DEFINE_string working_dir "/tmp/vmlinuz.working" \ + "Working directory for in-progress files. (Default: /tmp/vmlinuz.working)" +DEFINE_boolean keep_work ${FLAGS_FALSE} \ + "Keep temporary files (*.keyblock, *.vbpubk). (Default: false)" +DEFINE_string keys_dir "vboot/devkeys/" \ + "Directory with the RSA signing keys. (Defaults to test keys)" +DEFINE_string keyblock "kernel.keyblock" \ + "The keyblock to use. (Defaults to kernel.keyblock)" +DEFINE_string private "kernel_data_key.vbprivk" \ + "The private key to sign the kernel (Defaults to kernel_data_key.vbprivk)" +DEFINE_string public "kernel_subkey.vbpubk" \ + "The public key to verify the kernel (Defaults to kernel_subkey.vbpubk)" +# Note, to enable verified boot, the caller would manually pass: +# --boot_args='dm="... %U+1 %U+1 ..." \ +# --root=/dev/dm-0 +DEFINE_string boot_args "noinitrd" \ + "Additional boot arguments to pass to the commandline (Default: noinitrd)" +# If provided, will automatically add verified boot arguments. +DEFINE_string rootfs_image "" \ + "Optional path to the rootfs device or image.(Default: \"\")" +DEFINE_string rootfs_image_size "" \ + "Optional size in bytes of the rootfs_image file. Must be a multiple of 4 \ +KiB. If omitted, the filesystem size detected from rootfs_image is used." +DEFINE_string rootfs_hash "" \ + "Optional path to output the rootfs hash to. (Default: \"\")" +DEFINE_integer verity_error_behavior 3 \ + "Verified boot error behavior [0: I/O errors, 1: reboot, 2: nothing] \ +(Default: 3)" +DEFINE_integer verity_max_ios -1 \ + "Optional number of outstanding I/O operations. (Default: -1)" +DEFINE_string verity_hash_alg "sha1" \ + "Cryptographic hash algorithm used for dm-verity. (Default: sha1)" +DEFINE_string verity_salt "" \ + "Salt to use for rootfs hash (Default: \"\")" +DEFINE_boolean enable_rootfs_verification ${FLAGS_TRUE} \ + "Enable kernel-based root fs integrity checking. (Default: true)" +DEFINE_boolean enable_bootcache ${FLAGS_FALSE} \ + "Enable boot cache to accelerate booting. (Default: false)" +DEFINE_string enable_serial "" \ + "Enable serial port for printks. Example values: ttyS0" +DEFINE_integer loglevel 7 \ + "The loglevel to add to the kernel command line." + +# Parse flags +FLAGS "$@" || exit 1 +eval set -- "${FLAGS_ARGV}" + +# Die on error +switch_to_strict_mode + +# N.B. Ordering matters for some of the libraries below, because +# some of the files contain initialization used by later files. +. "./disk_layout_util.sh" || exit 1 + + +rootdigest() { + local digest=${table#*root_hexdigest=} + echo ${digest% salt*} +} + +salt() { + local salt=${table#*salt=} + echo ${salt%} +} + +hashstart() { + local hash=${table#*hashstart=} + echo ${hash% alg*} +} + +# Estimate of sectors used by verity +# (num blocks) * 32 (bytes per hash) * 2 (overhead) / 512 (bytes per sector) +veritysize() { + echo $((root_fs_blocks * 32 * 2 / 512)) +} + +get_base_root() { + echo 'PARTUUID=%U/PARTNROFF=1' +} + +base_root=$(get_base_root) + +device_mapper_args= +# Even with a rootfs_image, root= is not changed unless specified. +if [[ -n "${FLAGS_rootfs_image}" && -n "${FLAGS_rootfs_hash}" ]]; then + # Gets the number of blocks. 4096 byte blocks _are_ expected. + if [[ -n "${FLAGS_rootfs_image_size}" ]]; then + root_fs_size=${FLAGS_rootfs_image_size} + else + # We try to autodetect the rootfs_image filesystem size. + if [[ -f "${FLAGS_rootfs_image}" ]]; then + root_fs_size=$(stat -c '%s' ${FLAGS_rootfs_image}) + elif [[ -b "${FLAGS_rootfs_image}" ]]; then + root_fs_type="$(awk -v rootdev="${FLAGS_rootfs_image}" \ + '$1 == rootdev { print $3 }' /proc/mounts | head -n 1)" + case "${root_fs_type}" in + squashfs) + # unsquashfs returns the size in KiB as a float value with two + # decimals, rounded as printf() would do. To avoid corner cases like + # when the fractional part of the size in KiB is less than 0.005, + # instead of rounding up the value to the nearest 4KiB, we round it + # down and add 1 extra 4 KiB block. + root_fs_size_kib=$(sudo unsquashfs -stat "${FLAGS_rootfs_image}" | + grep -E -o 'Filesystem size [0-9\.]+ Kbytes' | + cut -f 3 -d ' ' | cut -f 1 -d '.') + root_fs_size=$(( (root_fs_size_kib / 4 + 1) * 4096 )) + ;; + ext[234]) + root_fs_blocks=$(sudo dumpe2fs "${FLAGS_rootfs_image}" 2>/dev/null | + grep "Block count" | + tr -d ' ' | + cut -f2 -d:) + root_fs_block_sz=$(sudo dumpe2fs "${FLAGS_rootfs_image}" 2>/dev/null | + grep "Block size" | + tr -d ' ' | + cut -f2 -d:) + root_fs_size=$(( root_fs_blocks * root_fs_block_sz )) + ;; + *) + die "Unknown root filesystem type ${root_fs_type}." + ;; + esac + else + die "Couldn't determine the size of ${FLAGS_rootfs_image}, pass the size \ +with --rootfs_image_size." + fi + fi + # Verity assumes a 4 KiB block size. + if [[ ! $(( root_fs_size % 4096 )) -eq 0 ]]; then + die "The root filesystem size (${root_fs_size}) must be a multiple of \ +4 KiB." + fi + root_fs_blocks=$((root_fs_size / 4096)) + info "rootfs is ${root_fs_blocks} blocks of 4096 bytes." + + info "Generating root fs hash tree (salt '${FLAGS_verity_salt}')." + # Runs as sudo in case the image is a block device. + # First argument to verity is reserved/unused and MUST be 0 + table=$(sudo ./verity mode=create \ + alg=${FLAGS_verity_hash_alg} \ + payload=${FLAGS_rootfs_image} \ + payload_blocks=${root_fs_blocks} \ + hashtree=${FLAGS_rootfs_hash} \ + salt=${FLAGS_verity_salt}) + if [[ -f "${FLAGS_rootfs_hash}" ]]; then + sudo chmod a+r "${FLAGS_rootfs_hash}" + fi + # Don't claim the root device unless verity is enabled. + # Doing so will claim /dev/sdDP out from under the system. + if [[ ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_TRUE} ]]; then + if [[ ${FLAGS_enable_bootcache} -eq ${FLAGS_TRUE} ]]; then + base_root='254:0' # major:minor numbers for /dev/dm-0 + fi + table=${table//HASH_DEV/${base_root}} + table=${table//ROOT_DEV/${base_root}} + fi + verity_dev="vroot none ro 1,${table}" + if [[ ${FLAGS_enable_bootcache} -eq ${FLAGS_TRUE} ]]; then + signature=$(rootdigest) + cachestart=$(($(hashstart) + $(veritysize))) + size_limit=512 + max_trace=20000 + max_pages=100000 + bootcache_args="PARTUUID=%U/PARTNROFF=1" + bootcache_args+=" ${cachestart} ${signature} ${size_limit}" + bootcache_args+=" ${max_trace} ${max_pages}" + bootcache_dev="vboot none ro 1,0 ${cachestart} bootcache ${bootcache_args}" + device_mapper_args="dm=\"2 ${bootcache_dev}, ${verity_dev}\"" + else + device_mapper_args="dm=\"1 ${verity_dev}\"" + fi + info "device mapper configuration: ${device_mapper_args}" +fi + +mkdir -p "${FLAGS_working_dir}" + +# Only let dm-verity block if rootfs verification is configured. +# By default, we use a firmware enumerated value, but it isn't reliable for +# production use. If +%d can be added upstream, then we can use: +# root_dev=PARTUID=uuid+1 +dev_wait=0 +root_dev=${base_root} +if [[ ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_TRUE} ]]; then + dev_wait=1 + if [[ ${FLAGS_enable_bootcache} -eq ${FLAGS_TRUE} ]]; then + root_dev=/dev/dm-1 + else + root_dev=/dev/dm-0 + fi +else + if [[ ${FLAGS_enable_bootcache} -eq ${FLAGS_TRUE} ]]; then + die "Having bootcache without verity is not supported" + fi +fi + +# kern_guid should eventually be changed to use PARTUUID +cat < "${FLAGS_working_dir}/boot.config" +ro +dm_verity.error_behavior=${FLAGS_verity_error_behavior} +dm_verity.max_bios=${FLAGS_verity_max_ios} +dm_verity.dev_wait=${dev_wait} +${device_mapper_args} +${FLAGS_boot_args} +vt.global_cursor_default=0 +kern_guid=%U +EOF +## kern_guid should eventually be changed to use PARTUUID +#cat < "${FLAGS_working_dir}/boot.config" +#root=${root_dev} +#ro +#dm_verity.error_behavior=${FLAGS_verity_error_behavior} +#dm_verity.max_bios=${FLAGS_verity_max_ios} +#dm_verity.dev_wait=${dev_wait} +#${device_mapper_args} +#${FLAGS_boot_args} +#vt.global_cursor_default=0 +#kern_guid=%U +#EOF + +WORK="${WORK} ${FLAGS_working_dir}/boot.config" +info "Emitted cross-platform boot params to ${FLAGS_working_dir}/boot.config" + +# Add common boot options first. +config="${FLAGS_working_dir}/config.txt" +if [[ -n ${FLAGS_enable_serial} ]]; then + console=${FLAGS_enable_serial} + if [[ ${console} != *,* ]]; then + console+=",115200n8" + fi + cat < "${config}" +earlyprintk=${console} +console=tty1 +console=${console} +EOF +else + cat < "${config}" +earlyprintk=ttyS0,115200,keep +rootwait +EOF +fi + +cat <> "${config}" +loglevel=${FLAGS_loglevel} +cros_secure +oops=panic +panic=-1 +EOF + +if [[ "${FLAGS_arch}" = "x86" || "${FLAGS_arch}" = "amd64" ]]; then + # Legacy BIOS will use the kernel in the rootfs (via syslinux), as will + # standard EFI BIOS (via grub, from the EFI System Partition). Chrome OS + # BIOS will use a separate signed kernel partition, which we'll create now. + cat <> "${FLAGS_working_dir}/config.txt" +add_efi_memmap +boot=local +noresume +noswap +i915.modeset=1 +tpm_tis.force=1 +tpm_tis.interrupts=0 +nmi_watchdog=panic,lapic +EOF + WORK="${WORK} ${FLAGS_working_dir}/config.txt" + + bootloader_path="lib64/bootstub/bootstub.efi" + kernel_image="${FLAGS_vmlinuz}" +elif [[ "${FLAGS_arch}" = "arm" || "${FLAGS_arch}" = "mips" ]]; then + WORK="${WORK} ${FLAGS_working_dir}/config.txt" + + # arm does not need/have a bootloader in kernel partition + dd if="/dev/zero" of="${FLAGS_working_dir}/bootloader.bin" bs=512 count=1 + WORK="${WORK} ${FLAGS_working_dir}/bootloader.bin" + + bootloader_path="${FLAGS_working_dir}/bootloader.bin" + kernel_image="${FLAGS_vmlinuz/vmlinuz/vmlinux.uimg}" +else + error "Unknown arch: ${FLAGS_arch}" +fi + +# Save the kernel as a .bin to allow it to be automatically extracted as +# an artifact by cbuildbot. Non .bin's need to be explicitly specified +# and would require the entire set of artifacts to be specified. +info "Saving ${kernel_image} as ${FLAGS_working_dir}/vmlinuz.bin" +cp ${kernel_image} ${FLAGS_working_dir}/vmlinuz.bin + +for image_type in $(get_image_types); do + already_seen_rootfs=0 + for partition in $(get_partitions ${image_type}); do + format=$(get_format ${image_type} "${partition}") + if [[ "${format}" == "ubi" ]]; then + type=$(get_type ${image_type} "${partition}") + # cgpt.py ensures that the rootfs partitions are compatible, in that if + # one is ubi then both are, and they have the same number of reserved + # blocks. We only want to attach one of them in boot to save time, so + # attach %P and get the information for whichever rootfs comes first. + if [[ "${type}" == "rootfs" ]]; then + if [[ "${already_seen_rootfs}" -ne 0 ]]; then + continue + fi + already_seen_rootfs=1 + partname='%P' + else + partname="${partition}" + fi + reserved=$(get_reserved_erase_blocks ${image_type} "${partition}") + echo "ubi.mtd=${partname},0,${reserved},${partname}" \ + >> "${FLAGS_working_dir}/config.txt" + fs_format=$(get_filesystem_format ${image_type} "${partition}") + if [[ "${fs_format}" != "ubifs" ]]; then + echo "ubi.block=${partname},0" >> "${FLAGS_working_dir}/config.txt" + fi + fi + done +done + +config_file="${FLAGS_working_dir}/config.txt" +# Create and sign the kernel blob +./vbutil_kernel \ + --pack "${FLAGS_to}" \ + --keyblock "${FLAGS_keys_dir}/${FLAGS_keyblock}" \ + --signprivate "${FLAGS_keys_dir}/${FLAGS_private}" \ + --version 1 \ + --config "${config_file}" \ + --bootloader "bootstub.efi" \ + --vmlinuz "kernels/bzImage" \ + --arch "${FLAGS_arch}" + +# And verify it. +./vbutil_kernel \ + --verify "${FLAGS_to}" \ + --signpubkey "${FLAGS_keys_dir}/${FLAGS_public}" + +if [[ -n "${FLAGS_hd_vblock}" ]]; then + dd if="${FLAGS_to}" bs=65536 count=1 of="${FLAGS_hd_vblock}" +fi + +set +e # cleanup failure is a-ok + +if [[ ${FLAGS_keep_work} -eq ${FLAGS_FALSE} ]]; then + info "Cleaning up temporary files: ${WORK}" + rm ${WORK} + rmdir ${FLAGS_working_dir} +fi + +info "Kernel partition image emitted: ${FLAGS_to}" + +if [[ -f ${FLAGS_rootfs_hash} ]]; then + info "Root filesystem hash emitted: ${FLAGS_rootfs_hash}" +fi diff --git a/scripts/mount_image.sh b/scripts/mount_image.sh deleted file mode 100755 index 3373c2b83..000000000 --- a/scripts/mount_image.sh +++ /dev/null @@ -1,211 +0,0 @@ -#!/bin/bash -eu -# File automatically generated. Do not edit. - -usage() { - local ret=0 - if [[ $# -gt 0 ]]; then - # Write to stderr on errors. - exec 1>&2 - echo "ERROR: $*" - echo - ret=1 - fi - echo "Usage: $0 [image] [part]" - echo "Example: $0 chromiumos_image.bin" - exit ${ret} -} - -TARGET=${1:-} -PART=${2:-} -case ${TARGET} in --h|--help) - usage - ;; -"") - for TARGET in chromiumos_{,base_}image.bin ""; do - if [[ -e ${TARGET} ]]; then - echo "autodetected image: ${TARGET}" - break - fi - done - if [[ -z ${TARGET} ]]; then - usage "could not autodetect an image" - fi - ;; -*) - if [[ ! -e ${TARGET} ]]; then - usage "image does not exist: ${TARGET}" - fi -esac - -# start size part contents -# 0 1 PMBR (Boot GUID: 09ADDEBB-0333-4B48-8E1D-CB43C88C95DE) -# 1 1 Pri GPT header -# 2 32 Pri GPT table -# 2961408 12132352 1 Label: "STATE" -# Type: Linux data -# UUID: CC1A2390-0A9D-9D48-883B-626E7DD188CB -# 20480 32768 2 Label: "KERN-A" -# Type: ChromeOS kernel -# UUID: 71B947BB-2396-314B-8C78-2A62CBFC8024 -# Attr: priority=15 tries=15 successful=0 -# 319488 2641920 3 Label: "ROOT-A" -# Type: ChromeOS rootfs -# UUID: 5D3E9883-6405-B44A-9E79-E1125B5FDC9B -# 53248 32768 4 Label: "KERN-B" -# Type: ChromeOS kernel -# UUID: CC947588-A4F8-B64A-99DE-81243D71A4BE -# Attr: priority=0 tries=0 successful=0 -# 315392 4096 5 Label: "ROOT-B" -# Type: ChromeOS rootfs -# UUID: 316ADC8E-FA80-194C-9C01-F562D280239F -# 16448 1 6 Label: "KERN-C" -# Type: ChromeOS kernel -# UUID: 6AD6BA67-5ED6-5C43-9DE1-238C7DA83B1A -# Attr: priority=0 tries=0 successful=0 -# 16449 1 7 Label: "ROOT-C" -# Type: ChromeOS rootfs -# UUID: AA474C7E-3368-8047-9CC4-C6459C867D53 -# 86016 32768 8 Label: "OEM" -# Type: Linux data -# UUID: 2B5DF82C-C3D0-4443-A90A-D2A3671D5C0E -# 16450 1 9 Label: "reserved" -# Type: ChromeOS reserved -# UUID: 23A9BEA8-8CCA-744C-9AD4-3408624F785D -# 16451 1 10 Label: "reserved" -# Type: ChromeOS reserved -# UUID: 56E34019-7E1F-D244-A74F-D594B637A18F -# 64 16384 11 Label: "RWFW" -# Type: ChromeOS firmware -# UUID: 529423BB-45F4-434B-846B-1A15A3ED987E -# 249856 65536 12 Label: "EFI-SYSTEM" -# Type: EFI System Partition -# UUID: 09ADDEBB-0333-4B48-8E1D-CB43C88C95DE -# Attr: legacy_boot=1 -# 15126495 32 Sec GPT table -# 15126527 1 Sec GPT header -case ${PART:-1} in -1|"STATE") -( -mkdir -p dir_1 -m=( sudo mount -o loop,offset=1516240896,sizelimit=6211764224 "${TARGET}" dir_1 ) -if ! "${m[@]}"; then - if ! "${m[@]}" -o ro; then - rmdir dir_1 - exit 0 - fi -fi -ln -sfT dir_1 "dir_1_STATE" -) & -esac -case ${PART:-2} in -2|"KERN-A") -( -mkdir -p dir_2 -m=( sudo mount -o loop,offset=10485760,sizelimit=16777216 "${TARGET}" dir_2 ) -if ! "${m[@]}"; then - if ! "${m[@]}" -o ro; then - rmdir dir_2 - exit 0 - fi -fi -ln -sfT dir_2 "dir_2_KERN-A" -) & -esac -case ${PART:-3} in -3|"ROOT-A") -( -mkdir -p dir_3 -m=( sudo mount -o loop,offset=163577856,sizelimit=1352663040 "${TARGET}" dir_3 ) -if ! "${m[@]}"; then - if ! "${m[@]}" -o ro; then - rmdir dir_3 - exit 0 - fi -fi -ln -sfT dir_3 "dir_3_ROOT-A" -) & -esac -case ${PART:-4} in -4|"KERN-B") -( -mkdir -p dir_4 -m=( sudo mount -o loop,offset=27262976,sizelimit=16777216 "${TARGET}" dir_4 ) -if ! "${m[@]}"; then - if ! "${m[@]}" -o ro; then - rmdir dir_4 - exit 0 - fi -fi -ln -sfT dir_4 "dir_4_KERN-B" -) & -esac -case ${PART:-5} in -5|"ROOT-B") -( -mkdir -p dir_5 -m=( sudo mount -o loop,offset=161480704,sizelimit=2097152 "${TARGET}" dir_5 ) -if ! "${m[@]}"; then - if ! "${m[@]}" -o ro; then - rmdir dir_5 - exit 0 - fi -fi -ln -sfT dir_5 "dir_5_ROOT-B" -) & -esac -case ${PART:-6} in -6|"KERN-C") -esac -case ${PART:-7} in -7|"ROOT-C") -esac -case ${PART:-8} in -8|"OEM") -( -mkdir -p dir_8 -m=( sudo mount -o loop,offset=44040192,sizelimit=16777216 "${TARGET}" dir_8 ) -if ! "${m[@]}"; then - if ! "${m[@]}" -o ro; then - rmdir dir_8 - exit 0 - fi -fi -ln -sfT dir_8 "dir_8_OEM" -) & -esac -case ${PART:-9} in -9|"reserved") -esac -case ${PART:-10} in -10|"reserved") -esac -case ${PART:-11} in -11|"RWFW") -( -mkdir -p dir_11 -m=( sudo mount -o loop,offset=32768,sizelimit=8388608 "${TARGET}" dir_11 ) -if ! "${m[@]}"; then - if ! "${m[@]}" -o ro; then - rmdir dir_11 - exit 0 - fi -fi -ln -sfT dir_11 "dir_11_RWFW" -) & -esac -case ${PART:-12} in -12|"EFI-SYSTEM") -( -mkdir -p dir_12 -m=( sudo mount -o loop,offset=127926272,sizelimit=33554432 "${TARGET}" dir_12 ) -if ! "${m[@]}"; then - if ! "${m[@]}" -o ro; then - rmdir dir_12 - exit 0 - fi -fi -ln -sfT dir_12 "dir_12_EFI-SYSTEM" -) & -esac -wait diff --git a/scripts/pack_partitions.sh b/scripts/pack_partitions.sh deleted file mode 100755 index 13341c4c3..000000000 --- a/scripts/pack_partitions.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/bash -eu -# File automatically generated. Do not edit. - -usage() { - local ret=0 - if [[ $# -gt 0 ]]; then - # Write to stderr on errors. - exec 1>&2 - echo "ERROR: $*" - echo - ret=1 - fi - echo "Usage: $0 [image] [part]" - echo "Example: $0 chromiumos_image.bin" - exit ${ret} -} - -TARGET=${1:-} -PART=${2:-} -case ${TARGET} in --h|--help) - usage - ;; -"") - for TARGET in chromiumos_{,base_}image.bin ""; do - if [[ -e ${TARGET} ]]; then - echo "autodetected image: ${TARGET}" - break - fi - done - if [[ -z ${TARGET} ]]; then - usage "could not autodetect an image" - fi - ;; -*) - if [[ ! -e ${TARGET} ]]; then - usage "image does not exist: ${TARGET}" - fi -esac - -# start size part contents -# 0 1 PMBR (Boot GUID: 09ADDEBB-0333-4B48-8E1D-CB43C88C95DE) -# 1 1 Pri GPT header -# 2 32 Pri GPT table -# 2961408 12132352 1 Label: "STATE" -# Type: Linux data -# UUID: CC1A2390-0A9D-9D48-883B-626E7DD188CB -# 20480 32768 2 Label: "KERN-A" -# Type: ChromeOS kernel -# UUID: 71B947BB-2396-314B-8C78-2A62CBFC8024 -# Attr: priority=15 tries=15 successful=0 -# 319488 2641920 3 Label: "ROOT-A" -# Type: ChromeOS rootfs -# UUID: 5D3E9883-6405-B44A-9E79-E1125B5FDC9B -# 53248 32768 4 Label: "KERN-B" -# Type: ChromeOS kernel -# UUID: CC947588-A4F8-B64A-99DE-81243D71A4BE -# Attr: priority=0 tries=0 successful=0 -# 315392 4096 5 Label: "ROOT-B" -# Type: ChromeOS rootfs -# UUID: 316ADC8E-FA80-194C-9C01-F562D280239F -# 16448 1 6 Label: "KERN-C" -# Type: ChromeOS kernel -# UUID: 6AD6BA67-5ED6-5C43-9DE1-238C7DA83B1A -# Attr: priority=0 tries=0 successful=0 -# 16449 1 7 Label: "ROOT-C" -# Type: ChromeOS rootfs -# UUID: AA474C7E-3368-8047-9CC4-C6459C867D53 -# 86016 32768 8 Label: "OEM" -# Type: Linux data -# UUID: 2B5DF82C-C3D0-4443-A90A-D2A3671D5C0E -# 16450 1 9 Label: "reserved" -# Type: ChromeOS reserved -# UUID: 23A9BEA8-8CCA-744C-9AD4-3408624F785D -# 16451 1 10 Label: "reserved" -# Type: ChromeOS reserved -# UUID: 56E34019-7E1F-D244-A74F-D594B637A18F -# 64 16384 11 Label: "RWFW" -# Type: ChromeOS firmware -# UUID: 529423BB-45F4-434B-846B-1A15A3ED987E -# 249856 65536 12 Label: "EFI-SYSTEM" -# Type: EFI System Partition -# UUID: 09ADDEBB-0333-4B48-8E1D-CB43C88C95DE -# Attr: legacy_boot=1 -# 15126495 32 Sec GPT table -# 15126527 1 Sec GPT header -case ${PART:-1} in -1|"STATE") -dd if=part_1 of="${TARGET}" bs=512 count=12132352 seek=2961408 conv=notrunc -esac -case ${PART:-2} in -2|"KERN-A") -dd if=part_2 of="${TARGET}" bs=512 count=32768 seek=20480 conv=notrunc -esac -case ${PART:-3} in -3|"ROOT-A") -dd if=part_3 of="${TARGET}" bs=512 count=2641920 seek=319488 conv=notrunc -esac -case ${PART:-4} in -4|"KERN-B") -dd if=part_4 of="${TARGET}" bs=512 count=32768 seek=53248 conv=notrunc -esac -case ${PART:-5} in -5|"ROOT-B") -dd if=part_5 of="${TARGET}" bs=512 count=4096 seek=315392 conv=notrunc -esac -case ${PART:-6} in -6|"KERN-C") -dd if=part_6 of="${TARGET}" bs=512 count=1 seek=16448 conv=notrunc -esac -case ${PART:-7} in -7|"ROOT-C") -dd if=part_7 of="${TARGET}" bs=512 count=1 seek=16449 conv=notrunc -esac -case ${PART:-8} in -8|"OEM") -dd if=part_8 of="${TARGET}" bs=512 count=32768 seek=86016 conv=notrunc -esac -case ${PART:-9} in -9|"reserved") -dd if=part_9 of="${TARGET}" bs=512 count=1 seek=16450 conv=notrunc -esac -case ${PART:-10} in -10|"reserved") -dd if=part_10 of="${TARGET}" bs=512 count=1 seek=16451 conv=notrunc -esac -case ${PART:-11} in -11|"RWFW") -dd if=part_11 of="${TARGET}" bs=512 count=16384 seek=64 conv=notrunc -esac -case ${PART:-12} in -12|"EFI-SYSTEM") -dd if=part_12 of="${TARGET}" bs=512 count=65536 seek=249856 conv=notrunc -esac diff --git a/scripts/umount_image.sh b/scripts/umount_image.sh deleted file mode 100755 index 35f520c6d..000000000 --- a/scripts/umount_image.sh +++ /dev/null @@ -1,179 +0,0 @@ -#!/bin/bash -eu -# File automatically generated. Do not edit. - -usage() { - local ret=0 - if [[ $# -gt 0 ]]; then - # Write to stderr on errors. - exec 1>&2 - echo "ERROR: $*" - echo - ret=1 - fi - echo "Usage: $0 [image] [part]" - echo "Example: $0 chromiumos_image.bin" - exit ${ret} -} - -TARGET=${1:-} -PART=${2:-} -case ${TARGET} in --h|--help) - usage - ;; -"") - for TARGET in chromiumos_{,base_}image.bin ""; do - if [[ -e ${TARGET} ]]; then - echo "autodetected image: ${TARGET}" - break - fi - done - if [[ -z ${TARGET} ]]; then - usage "could not autodetect an image" - fi - ;; -*) - if [[ ! -e ${TARGET} ]]; then - usage "image does not exist: ${TARGET}" - fi -esac - -# start size part contents -# 0 1 PMBR (Boot GUID: 09ADDEBB-0333-4B48-8E1D-CB43C88C95DE) -# 1 1 Pri GPT header -# 2 32 Pri GPT table -# 2961408 12132352 1 Label: "STATE" -# Type: Linux data -# UUID: CC1A2390-0A9D-9D48-883B-626E7DD188CB -# 20480 32768 2 Label: "KERN-A" -# Type: ChromeOS kernel -# UUID: 71B947BB-2396-314B-8C78-2A62CBFC8024 -# Attr: priority=15 tries=15 successful=0 -# 319488 2641920 3 Label: "ROOT-A" -# Type: ChromeOS rootfs -# UUID: 5D3E9883-6405-B44A-9E79-E1125B5FDC9B -# 53248 32768 4 Label: "KERN-B" -# Type: ChromeOS kernel -# UUID: CC947588-A4F8-B64A-99DE-81243D71A4BE -# Attr: priority=0 tries=0 successful=0 -# 315392 4096 5 Label: "ROOT-B" -# Type: ChromeOS rootfs -# UUID: 316ADC8E-FA80-194C-9C01-F562D280239F -# 16448 1 6 Label: "KERN-C" -# Type: ChromeOS kernel -# UUID: 6AD6BA67-5ED6-5C43-9DE1-238C7DA83B1A -# Attr: priority=0 tries=0 successful=0 -# 16449 1 7 Label: "ROOT-C" -# Type: ChromeOS rootfs -# UUID: AA474C7E-3368-8047-9CC4-C6459C867D53 -# 86016 32768 8 Label: "OEM" -# Type: Linux data -# UUID: 2B5DF82C-C3D0-4443-A90A-D2A3671D5C0E -# 16450 1 9 Label: "reserved" -# Type: ChromeOS reserved -# UUID: 23A9BEA8-8CCA-744C-9AD4-3408624F785D -# 16451 1 10 Label: "reserved" -# Type: ChromeOS reserved -# UUID: 56E34019-7E1F-D244-A74F-D594B637A18F -# 64 16384 11 Label: "RWFW" -# Type: ChromeOS firmware -# UUID: 529423BB-45F4-434B-846B-1A15A3ED987E -# 249856 65536 12 Label: "EFI-SYSTEM" -# Type: EFI System Partition -# UUID: 09ADDEBB-0333-4B48-8E1D-CB43C88C95DE -# Attr: legacy_boot=1 -# 15126495 32 Sec GPT table -# 15126527 1 Sec GPT header -case ${PART:-1} in -1|"STATE") -if [[ -d dir_1 ]]; then - ( - sudo umount dir_1 || : - rmdir dir_1 - rm -f "dir_1_STATE" - ) & -fi -esac -case ${PART:-2} in -2|"KERN-A") -if [[ -d dir_2 ]]; then - ( - sudo umount dir_2 || : - rmdir dir_2 - rm -f "dir_2_KERN-A" - ) & -fi -esac -case ${PART:-3} in -3|"ROOT-A") -if [[ -d dir_3 ]]; then - ( - sudo umount dir_3 || : - rmdir dir_3 - rm -f "dir_3_ROOT-A" - ) & -fi -esac -case ${PART:-4} in -4|"KERN-B") -if [[ -d dir_4 ]]; then - ( - sudo umount dir_4 || : - rmdir dir_4 - rm -f "dir_4_KERN-B" - ) & -fi -esac -case ${PART:-5} in -5|"ROOT-B") -if [[ -d dir_5 ]]; then - ( - sudo umount dir_5 || : - rmdir dir_5 - rm -f "dir_5_ROOT-B" - ) & -fi -esac -case ${PART:-6} in -6|"KERN-C") -esac -case ${PART:-7} in -7|"ROOT-C") -esac -case ${PART:-8} in -8|"OEM") -if [[ -d dir_8 ]]; then - ( - sudo umount dir_8 || : - rmdir dir_8 - rm -f "dir_8_OEM" - ) & -fi -esac -case ${PART:-9} in -9|"reserved") -esac -case ${PART:-10} in -10|"reserved") -esac -case ${PART:-11} in -11|"RWFW") -if [[ -d dir_11 ]]; then - ( - sudo umount dir_11 || : - rmdir dir_11 - rm -f "dir_11_RWFW" - ) & -fi -esac -case ${PART:-12} in -12|"EFI-SYSTEM") -if [[ -d dir_12 ]]; then - ( - sudo umount dir_12 || : - rmdir dir_12 - rm -f "dir_12_EFI-SYSTEM" - ) & -fi -esac -wait diff --git a/scripts/unpack_partitions.sh b/scripts/unpack_partitions.sh deleted file mode 100755 index ed2668b73..000000000 --- a/scripts/unpack_partitions.sh +++ /dev/null @@ -1,146 +0,0 @@ -#!/bin/bash -eu -# File automatically generated. Do not edit. - -usage() { - local ret=0 - if [[ $# -gt 0 ]]; then - # Write to stderr on errors. - exec 1>&2 - echo "ERROR: $*" - echo - ret=1 - fi - echo "Usage: $0 [image] [part]" - echo "Example: $0 chromiumos_image.bin" - exit ${ret} -} - -TARGET=${1:-} -PART=${2:-} -case ${TARGET} in --h|--help) - usage - ;; -"") - for TARGET in chromiumos_{,base_}image.bin ""; do - if [[ -e ${TARGET} ]]; then - echo "autodetected image: ${TARGET}" - break - fi - done - if [[ -z ${TARGET} ]]; then - usage "could not autodetect an image" - fi - ;; -*) - if [[ ! -e ${TARGET} ]]; then - usage "image does not exist: ${TARGET}" - fi -esac - -# start size part contents -# 0 1 PMBR (Boot GUID: 09ADDEBB-0333-4B48-8E1D-CB43C88C95DE) -# 1 1 Pri GPT header -# 2 32 Pri GPT table -# 2961408 12132352 1 Label: "STATE" -# Type: Linux data -# UUID: CC1A2390-0A9D-9D48-883B-626E7DD188CB -# 20480 32768 2 Label: "KERN-A" -# Type: ChromeOS kernel -# UUID: 71B947BB-2396-314B-8C78-2A62CBFC8024 -# Attr: priority=15 tries=15 successful=0 -# 319488 2641920 3 Label: "ROOT-A" -# Type: ChromeOS rootfs -# UUID: 5D3E9883-6405-B44A-9E79-E1125B5FDC9B -# 53248 32768 4 Label: "KERN-B" -# Type: ChromeOS kernel -# UUID: CC947588-A4F8-B64A-99DE-81243D71A4BE -# Attr: priority=0 tries=0 successful=0 -# 315392 4096 5 Label: "ROOT-B" -# Type: ChromeOS rootfs -# UUID: 316ADC8E-FA80-194C-9C01-F562D280239F -# 16448 1 6 Label: "KERN-C" -# Type: ChromeOS kernel -# UUID: 6AD6BA67-5ED6-5C43-9DE1-238C7DA83B1A -# Attr: priority=0 tries=0 successful=0 -# 16449 1 7 Label: "ROOT-C" -# Type: ChromeOS rootfs -# UUID: AA474C7E-3368-8047-9CC4-C6459C867D53 -# 86016 32768 8 Label: "OEM" -# Type: Linux data -# UUID: 2B5DF82C-C3D0-4443-A90A-D2A3671D5C0E -# 16450 1 9 Label: "reserved" -# Type: ChromeOS reserved -# UUID: 23A9BEA8-8CCA-744C-9AD4-3408624F785D -# 16451 1 10 Label: "reserved" -# Type: ChromeOS reserved -# UUID: 56E34019-7E1F-D244-A74F-D594B637A18F -# 64 16384 11 Label: "RWFW" -# Type: ChromeOS firmware -# UUID: 529423BB-45F4-434B-846B-1A15A3ED987E -# 249856 65536 12 Label: "EFI-SYSTEM" -# Type: EFI System Partition -# UUID: 09ADDEBB-0333-4B48-8E1D-CB43C88C95DE -# Attr: legacy_boot=1 -# 15126495 32 Sec GPT table -# 15126527 1 Sec GPT header -case ${PART:-1} in -1|"STATE") -dd if="${TARGET}" of=part_1 bs=512 count=12132352 skip=2961408 -ln -sfT part_1 "part_1_STATE" -esac -case ${PART:-2} in -2|"KERN-A") -dd if="${TARGET}" of=part_2 bs=512 count=32768 skip=20480 -ln -sfT part_2 "part_2_KERN-A" -esac -case ${PART:-3} in -3|"ROOT-A") -dd if="${TARGET}" of=part_3 bs=512 count=2641920 skip=319488 -ln -sfT part_3 "part_3_ROOT-A" -esac -case ${PART:-4} in -4|"KERN-B") -dd if="${TARGET}" of=part_4 bs=512 count=32768 skip=53248 -ln -sfT part_4 "part_4_KERN-B" -esac -case ${PART:-5} in -5|"ROOT-B") -dd if="${TARGET}" of=part_5 bs=512 count=4096 skip=315392 -ln -sfT part_5 "part_5_ROOT-B" -esac -case ${PART:-6} in -6|"KERN-C") -dd if="${TARGET}" of=part_6 bs=512 count=1 skip=16448 -ln -sfT part_6 "part_6_KERN-C" -esac -case ${PART:-7} in -7|"ROOT-C") -dd if="${TARGET}" of=part_7 bs=512 count=1 skip=16449 -ln -sfT part_7 "part_7_ROOT-C" -esac -case ${PART:-8} in -8|"OEM") -dd if="${TARGET}" of=part_8 bs=512 count=32768 skip=86016 -ln -sfT part_8 "part_8_OEM" -esac -case ${PART:-9} in -9|"reserved") -dd if="${TARGET}" of=part_9 bs=512 count=1 skip=16450 -ln -sfT part_9 "part_9_reserved" -esac -case ${PART:-10} in -10|"reserved") -dd if="${TARGET}" of=part_10 bs=512 count=1 skip=16451 -ln -sfT part_10 "part_10_reserved" -esac -case ${PART:-11} in -11|"RWFW") -dd if="${TARGET}" of=part_11 bs=512 count=16384 skip=64 -ln -sfT part_11 "part_11_RWFW" -esac -case ${PART:-12} in -12|"EFI-SYSTEM") -dd if="${TARGET}" of=part_12 bs=512 count=65536 skip=249856 -ln -sfT part_12 "part_12_EFI-SYSTEM" -esac diff --git a/scripts/vbutil_kernel b/scripts/vbutil_kernel new file mode 100755 index 000000000..93877647e Binary files /dev/null and b/scripts/vbutil_kernel differ diff --git a/scripts/verity b/scripts/verity new file mode 100755 index 000000000..0e2af1f1e Binary files /dev/null and b/scripts/verity differ diff --git a/usb/build/partition_script.sh b/usb/build/partition_script.sh new file mode 100644 index 000000000..2d86f95f7 --- /dev/null +++ b/usb/build/partition_script.sh @@ -0,0 +1,464 @@ +#!/bin/sh +# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This script is automatically generated by ./cgpt_shell.sh. +# Do not edit! + +if ! type numsectors >/dev/null 2>&1; then + . "./chromeos-common.sh" || exit 1 +fi +locate_gpt + +# Usage: create_image +# If is a block device, wipes out the GPT +# If it's not, it creates a new file of the requested size +create_image() { + local dev="$1" + local min_disk_size="$2" + local block_size="$3" + if [ -b "${dev}" ]; then + # Zap any old partitions (otherwise gpt complains). + dd if=/dev/zero of="${dev}" conv=notrunc bs=512 count=32 + dd if=/dev/zero of="${dev}" conv=notrunc bs=512 \ + seek=$(( min_disk_size - 1 - 33 )) count=33 + else + if [ ! -e "${dev}" ]; then + dd if=/dev/zero of="${dev}" bs="${block_size}" count=1 \ + seek=$(( min_disk_size - 1 )) + fi + fi +} + +write_base_table() { + local target="$1" + create_image "${target}" 11227072 512 + local curr=64 + # Create the GPT headers and tables. Pad the primary ones. + ./cgpt create -p 0 ${target} + local stateful_size=2490368 + if [ -b "${target}" ]; then + stateful_size=$(( $(numsectors "${target}") - 8736704)) + fi + : $(( stateful_size -= (stateful_size % 4096) )) + ./cgpt add -i 11 -b ${curr} -s 16384 -t firmware -l "RWFW" ${target} + : $(( curr += 16384 )) + ./cgpt add -i 6 -b ${curr} -s 1 -t kernel -l "KERN-C" ${target} + : $(( curr += 1 )) + ./cgpt add -i 7 -b ${curr} -s 1 -t rootfs -l "ROOT-C" ${target} + : $(( curr += 1 )) + ./cgpt add -i 9 -b ${curr} -s 1 -t reserved -l "reserved" ${target} + : $(( curr += 1 )) + ./cgpt add -i 10 -b ${curr} -s 1 -t reserved -l "reserved" ${target} + : $(( curr += 1 )) + : $(( curr += 4028 )) + ./cgpt add -i 2 -b ${curr} -s 32768 -t kernel -l "KERN-A" ${target} + : $(( curr += 32768 )) + ./cgpt add -i 4 -b ${curr} -s 32768 -t kernel -l "KERN-B" ${target} + : $(( curr += 32768 )) + ./cgpt add -i 8 -b ${curr} -s 32768 -t data -l "OEM" ${target} + : $(( curr += 32768 )) + : $(( curr += 131072 )) + ./cgpt add -i 12 -b ${curr} -s 65536 -t efi -l "EFI-SYSTEM" ${target} + : $(( curr += 65536 )) + ./cgpt add -i 5 -b ${curr} -s 4194304 -t rootfs -l "ROOT-B" ${target} + : $(( curr += 4194304 )) + ./cgpt add -i 3 -b ${curr} -s 4194304 -t rootfs -l "ROOT-A" ${target} + : $(( curr += 4194304 )) + ./cgpt add -i 1 -b ${curr} -s ${stateful_size} -t data -l "STATE" ${target} + : $(( curr += ${stateful_size} )) + ./cgpt add -i 2 -S 0 -T 15 -P 15 ${target} + ./cgpt add -i 4 -S 0 -T 15 -P 0 ${target} + ./cgpt add -i 6 -S 0 -T 15 -P 0 ${target} + ./cgpt boot -p -b $2 -i 12 ${target} + ./cgpt add -i 12 -B 1 ${target} + ./cgpt show ${target} +} +load_base_vars() { + DEFAULT_ROOTDEV="" + PARTITION_SIZE_RWFW=8388608 + RESERVED_EBS_RWFW=0 + DATA_SIZE_RWFW=8388608 + FORMAT_RWFW= + FS_FORMAT_RWFW= + FS_OPTIONS_RWFW="" + PARTITION_NUM_RWFW="11" + PARTITION_SIZE_11=8388608 + RESERVED_EBS_11=0 + DATA_SIZE_11=8388608 + FORMAT_11= + FS_FORMAT_11= + FS_OPTIONS_11="" + PARTITION_NUM_11="11" + PARTITION_SIZE_KERN_C=512 + RESERVED_EBS_KERN_C=0 + DATA_SIZE_KERN_C=512 + FORMAT_KERN_C= + FS_FORMAT_KERN_C= + FS_OPTIONS_KERN_C="" + PARTITION_NUM_KERN_C="6" + PARTITION_SIZE_6=512 + RESERVED_EBS_6=0 + DATA_SIZE_6=512 + FORMAT_6= + FS_FORMAT_6= + FS_OPTIONS_6="" + PARTITION_NUM_6="6" + PARTITION_SIZE_ROOT_C=512 + RESERVED_EBS_ROOT_C=0 + DATA_SIZE_ROOT_C=512 + FORMAT_ROOT_C= + FS_FORMAT_ROOT_C= + FS_OPTIONS_ROOT_C="" + PARTITION_NUM_ROOT_C="7" + PARTITION_SIZE_7=512 + RESERVED_EBS_7=0 + DATA_SIZE_7=512 + FORMAT_7= + FS_FORMAT_7= + FS_OPTIONS_7="" + PARTITION_NUM_7="7" + PARTITION_SIZE_RESERVED=512 + RESERVED_EBS_RESERVED=0 + DATA_SIZE_RESERVED=512 + FORMAT_RESERVED= + FS_FORMAT_RESERVED= + FS_OPTIONS_RESERVED="" + PARTITION_NUM_RESERVED="9" + PARTITION_SIZE_9=512 + RESERVED_EBS_9=0 + DATA_SIZE_9=512 + FORMAT_9= + FS_FORMAT_9= + FS_OPTIONS_9="" + PARTITION_NUM_9="9" + PARTITION_SIZE_RESERVED=512 + RESERVED_EBS_RESERVED=0 + DATA_SIZE_RESERVED=512 + FORMAT_RESERVED= + FS_FORMAT_RESERVED= + FS_OPTIONS_RESERVED="" + PARTITION_NUM_RESERVED="10" + PARTITION_SIZE_10=512 + RESERVED_EBS_10=0 + DATA_SIZE_10=512 + FORMAT_10= + FS_FORMAT_10= + FS_OPTIONS_10="" + PARTITION_NUM_10="10" + PARTITION_SIZE_KERN_A=16777216 + RESERVED_EBS_KERN_A=0 + DATA_SIZE_KERN_A=16777216 + FORMAT_KERN_A= + FS_FORMAT_KERN_A= + FS_OPTIONS_KERN_A="" + PARTITION_NUM_KERN_A="2" + PARTITION_SIZE_2=16777216 + RESERVED_EBS_2=0 + DATA_SIZE_2=16777216 + FORMAT_2= + FS_FORMAT_2= + FS_OPTIONS_2="" + PARTITION_NUM_2="2" + PARTITION_SIZE_KERN_B=16777216 + RESERVED_EBS_KERN_B=0 + DATA_SIZE_KERN_B=16777216 + FORMAT_KERN_B= + FS_FORMAT_KERN_B= + FS_OPTIONS_KERN_B="" + PARTITION_NUM_KERN_B="4" + PARTITION_SIZE_4=16777216 + RESERVED_EBS_4=0 + DATA_SIZE_4=16777216 + FORMAT_4= + FS_FORMAT_4= + FS_OPTIONS_4="" + PARTITION_NUM_4="4" + PARTITION_SIZE_OEM=16777216 + RESERVED_EBS_OEM=0 + DATA_SIZE_OEM=16777216 + FORMAT_OEM= + FS_FORMAT_OEM=ext4 + FS_OPTIONS_OEM="" + PARTITION_NUM_OEM="8" + PARTITION_SIZE_8=16777216 + RESERVED_EBS_8=0 + DATA_SIZE_8=16777216 + FORMAT_8= + FS_FORMAT_8=ext4 + FS_OPTIONS_8="" + PARTITION_NUM_8="8" + PARTITION_SIZE_EFI_SYSTEM=33554432 + RESERVED_EBS_EFI_SYSTEM=0 + DATA_SIZE_EFI_SYSTEM=33554432 + FORMAT_EFI_SYSTEM= + FS_FORMAT_EFI_SYSTEM=vfat + FS_OPTIONS_EFI_SYSTEM="" + PARTITION_NUM_EFI_SYSTEM="12" + PARTITION_SIZE_12=33554432 + RESERVED_EBS_12=0 + DATA_SIZE_12=33554432 + FORMAT_12= + FS_FORMAT_12=vfat + FS_OPTIONS_12="" + PARTITION_NUM_12="12" + PARTITION_SIZE_ROOT_B=2147483648 + RESERVED_EBS_ROOT_B=0 + DATA_SIZE_ROOT_B=2147483648 + FORMAT_ROOT_B= + FS_FORMAT_ROOT_B= + FS_OPTIONS_ROOT_B="" + PARTITION_NUM_ROOT_B="5" + PARTITION_SIZE_5=2147483648 + RESERVED_EBS_5=0 + DATA_SIZE_5=2147483648 + FORMAT_5= + FS_FORMAT_5= + FS_OPTIONS_5="" + PARTITION_NUM_5="5" + PARTITION_SIZE_ROOT_A=2147483648 + RESERVED_EBS_ROOT_A=0 + DATA_SIZE_ROOT_A=1300234240 + FORMAT_ROOT_A= + FS_FORMAT_ROOT_A=ext2 + FS_OPTIONS_ROOT_A="" + PARTITION_NUM_ROOT_A="3" + PARTITION_SIZE_3=2147483648 + RESERVED_EBS_3=0 + DATA_SIZE_3=1300234240 + FORMAT_3= + FS_FORMAT_3=ext2 + FS_OPTIONS_3="" + PARTITION_NUM_3="3" + PARTITION_SIZE_STATE=1275068416 + RESERVED_EBS_STATE=0 + DATA_SIZE_STATE=1275068416 + FORMAT_STATE= + FS_FORMAT_STATE=ext4 + FS_OPTIONS_STATE="" + PARTITION_NUM_STATE="1" + PARTITION_SIZE_1=1275068416 + RESERVED_EBS_1=0 + DATA_SIZE_1=1275068416 + FORMAT_1= + FS_FORMAT_1=ext4 + FS_OPTIONS_1="" + PARTITION_NUM_1="1" +} +write_partition_table() { + local target="$1" + create_image "${target}" 5484480 512 + local curr=64 + # Create the GPT headers and tables. Pad the primary ones. + ./cgpt create -p 0 ${target} + local stateful_size=2490368 + if [ -b "${target}" ]; then + stateful_size=$(( $(numsectors "${target}") - 2994112)) + fi + : $(( stateful_size -= (stateful_size % 4096) )) + ./cgpt add -i 11 -b ${curr} -s 16384 -t firmware -l "RWFW" ${target} + : $(( curr += 16384 )) + ./cgpt add -i 6 -b ${curr} -s 1 -t kernel -l "KERN-C" ${target} + : $(( curr += 1 )) + ./cgpt add -i 7 -b ${curr} -s 1 -t rootfs -l "ROOT-C" ${target} + : $(( curr += 1 )) + ./cgpt add -i 9 -b ${curr} -s 1 -t reserved -l "reserved" ${target} + : $(( curr += 1 )) + ./cgpt add -i 10 -b ${curr} -s 1 -t reserved -l "reserved" ${target} + : $(( curr += 1 )) + : $(( curr += 4028 )) + ./cgpt add -i 2 -b ${curr} -s 32768 -t kernel -l "KERN-A" ${target} + : $(( curr += 32768 )) + ./cgpt add -i 4 -b ${curr} -s 32768 -t kernel -l "KERN-B" ${target} + : $(( curr += 32768 )) + ./cgpt add -i 8 -b ${curr} -s 32768 -t data -l "OEM" ${target} + : $(( curr += 32768 )) + : $(( curr += 131072 )) + ./cgpt add -i 12 -b ${curr} -s 65536 -t efi -l "EFI-SYSTEM" ${target} + : $(( curr += 65536 )) + ./cgpt add -i 5 -b ${curr} -s 4096 -t rootfs -l "ROOT-B" ${target} + : $(( curr += 4096 )) + ./cgpt add -i 3 -b ${curr} -s 2641920 -t rootfs -l "ROOT-A" ${target} + : $(( curr += 2641920 )) + ./cgpt add -i 1 -b ${curr} -s ${stateful_size} -t data -l "STATE" ${target} + : $(( curr += ${stateful_size} )) + ./cgpt add -i 2 -S 0 -T 15 -P 15 ${target} + ./cgpt add -i 4 -S 0 -T 0 -P 0 ${target} + ./cgpt add -i 6 -S 0 -T 0 -P 0 ${target} + ./cgpt boot -p -b $2 -i 12 ${target} + ./cgpt add -i 12 -B 1 ${target} + ./cgpt show ${target} +} +load_partition_vars() { + DEFAULT_ROOTDEV="" + PARTITION_SIZE_RWFW=8388608 + RESERVED_EBS_RWFW=0 + DATA_SIZE_RWFW=8388608 + FORMAT_RWFW= + FS_FORMAT_RWFW= + FS_OPTIONS_RWFW="" + PARTITION_NUM_RWFW="11" + PARTITION_SIZE_11=8388608 + RESERVED_EBS_11=0 + DATA_SIZE_11=8388608 + FORMAT_11= + FS_FORMAT_11= + FS_OPTIONS_11="" + PARTITION_NUM_11="11" + PARTITION_SIZE_KERN_C=512 + RESERVED_EBS_KERN_C=0 + DATA_SIZE_KERN_C=512 + FORMAT_KERN_C= + FS_FORMAT_KERN_C= + FS_OPTIONS_KERN_C="" + PARTITION_NUM_KERN_C="6" + PARTITION_SIZE_6=512 + RESERVED_EBS_6=0 + DATA_SIZE_6=512 + FORMAT_6= + FS_FORMAT_6= + FS_OPTIONS_6="" + PARTITION_NUM_6="6" + PARTITION_SIZE_ROOT_C=512 + RESERVED_EBS_ROOT_C=0 + DATA_SIZE_ROOT_C=512 + FORMAT_ROOT_C= + FS_FORMAT_ROOT_C= + FS_OPTIONS_ROOT_C="" + PARTITION_NUM_ROOT_C="7" + PARTITION_SIZE_7=512 + RESERVED_EBS_7=0 + DATA_SIZE_7=512 + FORMAT_7= + FS_FORMAT_7= + FS_OPTIONS_7="" + PARTITION_NUM_7="7" + PARTITION_SIZE_RESERVED=512 + RESERVED_EBS_RESERVED=0 + DATA_SIZE_RESERVED=512 + FORMAT_RESERVED= + FS_FORMAT_RESERVED= + FS_OPTIONS_RESERVED="" + PARTITION_NUM_RESERVED="9" + PARTITION_SIZE_9=512 + RESERVED_EBS_9=0 + DATA_SIZE_9=512 + FORMAT_9= + FS_FORMAT_9= + FS_OPTIONS_9="" + PARTITION_NUM_9="9" + PARTITION_SIZE_RESERVED=512 + RESERVED_EBS_RESERVED=0 + DATA_SIZE_RESERVED=512 + FORMAT_RESERVED= + FS_FORMAT_RESERVED= + FS_OPTIONS_RESERVED="" + PARTITION_NUM_RESERVED="10" + PARTITION_SIZE_10=512 + RESERVED_EBS_10=0 + DATA_SIZE_10=512 + FORMAT_10= + FS_FORMAT_10= + FS_OPTIONS_10="" + PARTITION_NUM_10="10" + PARTITION_SIZE_KERN_A=16777216 + RESERVED_EBS_KERN_A=0 + DATA_SIZE_KERN_A=16777216 + FORMAT_KERN_A= + FS_FORMAT_KERN_A= + FS_OPTIONS_KERN_A="" + PARTITION_NUM_KERN_A="2" + PARTITION_SIZE_2=16777216 + RESERVED_EBS_2=0 + DATA_SIZE_2=16777216 + FORMAT_2= + FS_FORMAT_2= + FS_OPTIONS_2="" + PARTITION_NUM_2="2" + PARTITION_SIZE_KERN_B=16777216 + RESERVED_EBS_KERN_B=0 + DATA_SIZE_KERN_B=16777216 + FORMAT_KERN_B= + FS_FORMAT_KERN_B= + FS_OPTIONS_KERN_B="" + PARTITION_NUM_KERN_B="4" + PARTITION_SIZE_4=16777216 + RESERVED_EBS_4=0 + DATA_SIZE_4=16777216 + FORMAT_4= + FS_FORMAT_4= + FS_OPTIONS_4="" + PARTITION_NUM_4="4" + PARTITION_SIZE_OEM=16777216 + RESERVED_EBS_OEM=0 + DATA_SIZE_OEM=16777216 + FORMAT_OEM= + FS_FORMAT_OEM=ext4 + FS_OPTIONS_OEM="" + PARTITION_NUM_OEM="8" + PARTITION_SIZE_8=16777216 + RESERVED_EBS_8=0 + DATA_SIZE_8=16777216 + FORMAT_8= + FS_FORMAT_8=ext4 + FS_OPTIONS_8="" + PARTITION_NUM_8="8" + PARTITION_SIZE_EFI_SYSTEM=33554432 + RESERVED_EBS_EFI_SYSTEM=0 + DATA_SIZE_EFI_SYSTEM=33554432 + FORMAT_EFI_SYSTEM= + FS_FORMAT_EFI_SYSTEM=vfat + FS_OPTIONS_EFI_SYSTEM="" + PARTITION_NUM_EFI_SYSTEM="12" + PARTITION_SIZE_12=33554432 + RESERVED_EBS_12=0 + DATA_SIZE_12=33554432 + FORMAT_12= + FS_FORMAT_12=vfat + FS_OPTIONS_12="" + PARTITION_NUM_12="12" + PARTITION_SIZE_ROOT_B=2097152 + RESERVED_EBS_ROOT_B=0 + DATA_SIZE_ROOT_B=2097152 + FORMAT_ROOT_B= + FS_FORMAT_ROOT_B= + FS_OPTIONS_ROOT_B="" + PARTITION_NUM_ROOT_B="5" + PARTITION_SIZE_5=2097152 + RESERVED_EBS_5=0 + DATA_SIZE_5=2097152 + FORMAT_5= + FS_FORMAT_5= + FS_OPTIONS_5="" + PARTITION_NUM_5="5" + PARTITION_SIZE_ROOT_A=1352663040 + RESERVED_EBS_ROOT_A=0 + DATA_SIZE_ROOT_A=1300234240 + FORMAT_ROOT_A= + FS_FORMAT_ROOT_A=ext2 + FS_OPTIONS_ROOT_A="" + PARTITION_NUM_ROOT_A="3" + PARTITION_SIZE_3=1352663040 + RESERVED_EBS_3=0 + DATA_SIZE_3=1300234240 + FORMAT_3= + FS_FORMAT_3=ext2 + FS_OPTIONS_3="" + PARTITION_NUM_3="3" + PARTITION_SIZE_STATE=1275068416 + RESERVED_EBS_STATE=0 + DATA_SIZE_STATE=1275068416 + FORMAT_STATE= + FS_FORMAT_STATE=ext4 + FS_OPTIONS_STATE="" + PARTITION_NUM_STATE="1" + PARTITION_SIZE_1=1275068416 + RESERVED_EBS_1=0 + DATA_SIZE_1=1275068416 + FORMAT_1= + FS_FORMAT_1=ext4 + FS_OPTIONS_1="" + PARTITION_NUM_1="1" +} +ROOTFS_PARTITION_SIZE=2147483648 diff --git a/usb/cgpt b/usb/cgpt new file mode 100755 index 000000000..13cc4e357 Binary files /dev/null and b/usb/cgpt differ diff --git a/usb/cgpt.py b/usb/cgpt.py new file mode 100755 index 000000000..5f00937b3 --- /dev/null +++ b/usb/cgpt.py @@ -0,0 +1,1558 @@ +#!/usr/bin/env python2 +# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Parse and operate based on disk layout files.""" + +from __future__ import print_function + +import argparse +import copy +import json +import math +import os +import re +import sys + + +class ConfigNotFound(Exception): + """Config Not Found""" + +class PartitionNotFound(Exception): + """Partition Not Found""" + +class InvalidLayout(Exception): + """Invalid Layout""" + +class InvalidAdjustment(Exception): + """Invalid Adjustment""" + +class InvalidSize(Exception): + """Invalid Size""" + +class ConflictingOptions(Exception): + """Conflicting Options""" + +class ConflictingPartitionOrder(Exception): + """The partition order in the parent and child layout don't match.""" + +class MismatchedRootfsFormat(Exception): + """Rootfs partitions in different formats""" + +class MismatchedRootfsBlocks(Exception): + """Rootfs partitions have different numbers of reserved erase blocks""" + +class MissingEraseBlockField(Exception): + """Partition has reserved erase blocks but not other fields needed""" + +class ExcessFailureProbability(Exception): + """Chances are high that the partition will have too many bad blocks""" + +class UnalignedPartition(Exception): + """Partition size does not divide erase block size""" + +class ExpandNandImpossible(Exception): + """Partition is raw NAND and marked with the incompatible expand feature""" + +class ExcessPartitionSize(Exception): + """Partitions sum to more than the size of the whole device""" + +COMMON_LAYOUT = 'common' +BASE_LAYOUT = 'base' +# Blocks of the partition entry array. +SIZE_OF_PARTITION_ENTRY_ARRAY = 32 +SIZE_OF_PMBR = 1 +SIZE_OF_GPT_HEADER = 1 + + +def ParseHumanNumber(operand): + """Parse a human friendly number + + This handles things like 4GiB and 4MB and such. See the usage string for + full details on all the formats supported. + + Args: + operand: The number to parse (may be an int or string) + + Returns: + An integer + """ + operand = str(operand) + negative = -1 if operand.startswith('-') else 1 + if negative == -1: + operand = operand[1:] + operand_digits = re.sub(r'\D', r'', operand) + + size_factor = block_factor = 1 + suffix = operand[len(operand_digits):].strip() + if suffix: + size_factors = {'B': 0, 'K': 1, 'M': 2, 'G': 3, 'T': 4,} + try: + size_factor = size_factors[suffix[0].upper()] + except KeyError: + raise InvalidAdjustment('Unknown size type %s' % suffix) + if size_factor == 0 and len(suffix) > 1: + raise InvalidAdjustment('Unknown size type %s' % suffix) + block_factors = {'': 1024, 'B': 1000, 'IB': 1024,} + try: + block_factor = block_factors[suffix[1:].upper()] + except KeyError: + raise InvalidAdjustment('Unknown size type %s' % suffix) + + return int(operand_digits) * pow(block_factor, size_factor) * negative + + +def ProduceHumanNumber(number): + """A simple reverse of ParseHumanNumber, converting a number to human form. + + Args: + number: A number (int) to be converted to human form. + + Returns: + A string, such as "1 KiB", that satisfies the condition + ParseHumanNumber(ProduceHumanNumber(i)) == i. + """ + scales = [ + (2**40, 'Ti'), + (10**12, 'T'), + (2**30, 'Gi'), + (10**9, 'G'), + (2**20, 'Mi'), + (10**6, 'M'), + (2**10, 'Ki'), + (10**3, 'K') + ] + for denom, suffix in scales: + if (number % denom) == 0: + return '%d %sB' % (number // denom, suffix) + return str(number) + + +def ParseRelativeNumber(max_number, number): + """Return the number that is relative to |max_number| by |number| + + We support three forms: + 90% - |number| is a percentage of |max_number| + 100 - |number| is the answer already (and |max_number| is ignored) + -90 - |number| is subtracted from |max_number| + + Args: + max_number: The limit to use when |number| is negative or a percent + number: The (possibly relative) number to parse (may be an int or string) + """ + max_number = int(max_number) + number = str(number) + if number.endswith('%'): + percent = float(number[:-1]) / 100 + return int(max_number * percent) + else: + number = ParseHumanNumber(number) + if number < 0: + return max_number + number + else: + return number + + +def _ApplyLayoutOverrides(layout_to_override, layout): + """Applies |layout| overrides on to |layout_to_override|. + + First add missing partition from layout to layout_to_override. + Then, update partitions in layout_to_override with layout information. + """ + # First check that all the partitions defined in both layouts are defined in + # the same order in each layout. Otherwise, the order in which they end up + # in the merged layout doesn't match what the user sees in the child layout. + common_nums = set.intersection( + {part['num'] for part in layout_to_override if 'num' in part}, + {part['num'] for part in layout if 'num' in part}) + layout_to_override_order = [part['num'] for part in layout_to_override + if part.get('num') in common_nums] + layout_order = [part['num'] for part in layout + if part.get('num') in common_nums] + if layout_order != layout_to_override_order: + raise ConflictingPartitionOrder( + 'Layouts share partitions %s but they are in different order: ' + 'layout_to_override: %s, layout: %s' % ( + sorted(common_nums), + [part.get('num') for part in layout_to_override], + [part.get('num') for part in layout])) + + # Merge layouts with the partitions in the same order they are in both + # layouts. + part_index = 0 + for part_to_apply in layout: + num = part_to_apply.get('num') + + if part_index == len(layout_to_override): + # The part_to_apply is past the list of partitions to override, this + # means that is a new partition added at the end. + # Need of deepcopy, in case we change layout later. + layout_to_override.append(copy.deepcopy(part_to_apply)) + elif layout_to_override[part_index].get('num') is None and num is None: + # Allow modifying gaps after a partition. + # TODO(deymo): Drop support for "gap" partitions and use alignment + # instead. + layout_to_override[part_index].update(part_to_apply) + elif num in common_nums: + while layout_to_override[part_index].get('num') != num: + part_index += 1 + layout_to_override[part_index].update(part_to_apply) + else: + # Need of deepcopy, in case we change layout later. + layout_to_override.insert(part_index, copy.deepcopy(part_to_apply)) + part_index += 1 + + +def LoadJSONWithComments(filename): + """Loads a JSON file ignoring lines with comments. + + RFC 7159 doesn't allow comments on the file JSON format. This functions loads + a JSON file removing all the comment lines. A comment line is any line + starting with # and optionally indented with whitespaces. Note that inline + comments are not supported. + + Args: + filename: The input filename. + + Returns: + The parsed JSON object. + """ + regex = re.compile(r'^\s*#.*') + with open(filename) as f: + source = ''.join(regex.sub('', line) for line in f) + return json.loads(source) + + +def _LoadStackedPartitionConfig(filename): + """Loads a partition table and its possible parent tables. + + This does very little validation. It's just enough to walk all of the parent + files and merges them with the current config. Overall validation is left to + the caller. + + Args: + filename: Filename to load into object. + + Returns: + Object containing disk layout configuration + """ + if not os.path.exists(filename): + raise ConfigNotFound('Partition config %s was not found!' % filename) + config = LoadJSONWithComments(filename) + + # Let's first apply our new configs onto base. + common_layout = config['layouts'].setdefault(COMMON_LAYOUT, []) + for layout_name, layout in config['layouts'].iteritems(): + # Don't apply on yourself. + if layout_name == COMMON_LAYOUT or layout_name == '_comment': + continue + + # Need to copy a list of dicts so make a deep copy. + working_layout = copy.deepcopy(common_layout) + _ApplyLayoutOverrides(working_layout, layout) + config['layouts'][layout_name] = working_layout + + dirname = os.path.dirname(filename) + # Now let's inherit the values from all our parents. + for parent in config.get('parent', '').split(): + parent_filename = os.path.join(dirname, parent) + if not os.path.exists(parent_filename): + # Try loading the parent file from the cgpt.py directory (global config). + parent_filename = os.path.join(os.path.join(os.path.dirname(__file__), + parent)) + parent_config = _LoadStackedPartitionConfig(parent_filename) + + # First if the parent is missing any fields the new config has, fill them + # in. + for key in config.keys(): + if key == 'parent': + continue + elif key == 'metadata': + # We handle this especially to allow for inner metadata fields to be + # added / modified. + parent_config.setdefault(key, {}) + parent_config[key].update(config[key]) + else: + parent_config.setdefault(key, config[key]) + + # The overrides work by taking the parent_config, apply the new config + # layout info, and return the resulting config which is stored in the parent + # config. + + # So there's an issue where an inheriting layout file may contain new + # layouts not previously defined in the parent layout. Since we are + # building these layout files based on the parent configs and overriding + # new values, we first add the new layouts not previously defined in the + # parent config using a copy of the base layout from that parent config. + parent_layouts = set(parent_config['layouts']) + config_layouts = set(config['layouts']) + new_layouts = config_layouts - parent_layouts + + # Actually add the copy. Use a copy such that each is unique. + parent_cmn_layout = parent_config['layouts'].setdefault(COMMON_LAYOUT, []) + for layout_name in new_layouts: + parent_config['layouts'][layout_name] = copy.deepcopy(parent_cmn_layout) + + # Iterate through each layout in the parent config and apply the new layout. + common_layout = config['layouts'].setdefault(COMMON_LAYOUT, []) + for layout_name, parent_layout in parent_config['layouts'].iteritems(): + if layout_name == '_comment': + continue + + layout_override = config['layouts'].setdefault(layout_name, []) + if layout_name != COMMON_LAYOUT: + _ApplyLayoutOverrides(parent_layout, common_layout) + + _ApplyLayoutOverrides(parent_layout, layout_override) + + config = parent_config + + config.pop('parent', None) + return config + + +def LoadPartitionConfig(filename): + """Loads a partition tables configuration file into a Python object. + + Args: + filename: Filename to load into object + + Returns: + Object containing disk layout configuration + """ + + valid_keys = set(('_comment', 'metadata', 'layouts', 'parent')) + valid_layout_keys = set(( + '_comment', 'num', 'blocks', 'block_size', 'fs_blocks', 'fs_block_size', + 'uuid', 'label', 'format', 'fs_format', 'type', 'features', + 'size', 'fs_size', 'fs_options', 'erase_block_size', 'hybrid_mbr', + 'reserved_erase_blocks', 'max_bad_erase_blocks', 'external_gpt', + 'page_size', 'size_min', 'fs_size_min')) + valid_features = set(('expand',)) + + config = _LoadStackedPartitionConfig(filename) + try: + metadata = config['metadata'] + for key in ('block_size', 'fs_block_size'): + metadata[key] = ParseHumanNumber(metadata[key]) + + unknown_keys = set(config.keys()) - valid_keys + if unknown_keys: + raise InvalidLayout('Unknown items: %r' % unknown_keys) + + if len(config['layouts']) <= 0: + raise InvalidLayout('Missing "layouts" entries') + + if not BASE_LAYOUT in config['layouts'].keys(): + raise InvalidLayout('Missing "base" config in "layouts"') + + for layout_name, layout in config['layouts'].iteritems(): + if layout_name == '_comment': + continue + + for part in layout: + unknown_keys = set(part.keys()) - valid_layout_keys + if unknown_keys: + raise InvalidLayout('Unknown items in layout %s: %r' % + (layout_name, unknown_keys)) + + if part.get('num') == 'metadata' and 'type' not in part: + part['type'] = 'blank' + + if part['type'] != 'blank': + for s in ('num', 'label'): + if not s in part: + raise InvalidLayout('Layout "%s" missing "%s"' % (layout_name, s)) + + if 'size' in part: + if 'blocks' in part: + raise ConflictingOptions( + '%s: Conflicting settings are used. ' + 'Found section sets both \'blocks\' and \'size\'.' % + part['label']) + part['bytes'] = ParseHumanNumber(part['size']) + if 'size_min' in part: + size_min = ParseHumanNumber(part['size_min']) + if part['bytes'] < size_min: + part['bytes'] = size_min + part['blocks'] = part['bytes'] / metadata['block_size'] + + if part['bytes'] % metadata['block_size'] != 0: + raise InvalidSize( + 'Size: "%s" (%s bytes) is not an even number of block_size: %s' + % (part['size'], part['bytes'], metadata['block_size'])) + + if 'fs_size' in part: + part['fs_bytes'] = ParseHumanNumber(part['fs_size']) + if 'fs_size_min' in part: + fs_size_min = ParseHumanNumber(part['fs_size_min']) + if part['fs_bytes'] < fs_size_min: + part['fs_bytes'] = fs_size_min + if part['fs_bytes'] <= 0: + raise InvalidSize( + 'File system size "%s" must be positive' % + part['fs_size']) + if part['fs_bytes'] > part['bytes']: + raise InvalidSize( + 'Filesystem may not be larger than partition: %s %s: %d > %d' % + (layout_name, part['label'], part['fs_bytes'], part['bytes'])) + if part['fs_bytes'] % metadata['fs_block_size'] != 0: + raise InvalidSize( + 'File system size: "%s" (%s bytes) is not an even number of ' + 'fs blocks: %s' % + (part['fs_size'], part['fs_bytes'], metadata['fs_block_size'])) + if part.get('format') == 'ubi': + part_meta = GetMetadataPartition(layout) + page_size = ParseHumanNumber(part_meta['page_size']) + eb_size = ParseHumanNumber(part_meta['erase_block_size']) + ubi_eb_size = eb_size - 2 * page_size + if (part['fs_bytes'] % ubi_eb_size) != 0: + # Trim fs_bytes to multiple of UBI eraseblock size. + fs_bytes = part['fs_bytes'] - (part['fs_bytes'] % ubi_eb_size) + raise InvalidSize( + 'File system size: "%s" (%d bytes) is not a multiple of UBI ' + 'erase block size (%d). Please set "fs_size" to "%s" in the ' + '"common" layout instead.' % + (part['fs_size'], part['fs_bytes'], ubi_eb_size, + ProduceHumanNumber(fs_bytes))) + + if 'blocks' in part: + part['blocks'] = ParseHumanNumber(part['blocks']) + part['bytes'] = part['blocks'] * metadata['block_size'] + + if 'fs_blocks' in part: + max_fs_blocks = part['bytes'] / metadata['fs_block_size'] + part['fs_blocks'] = ParseRelativeNumber(max_fs_blocks, + part['fs_blocks']) + part['fs_bytes'] = part['fs_blocks'] * metadata['fs_block_size'] + + if part['fs_bytes'] > part['bytes']: + raise InvalidLayout( + 'Filesystem may not be larger than partition: %s %s: %d > %d' % + (layout_name, part['label'], part['fs_bytes'], part['bytes'])) + if 'erase_block_size' in part: + part['erase_block_size'] = ParseHumanNumber(part['erase_block_size']) + if 'page_size' in part: + part['page_size'] = ParseHumanNumber(part['page_size']) + + part.setdefault('features', []) + unknown_features = set(part['features']) - valid_features + if unknown_features: + raise InvalidLayout('%s: Unknown features: %s' % + (part['label'], unknown_features)) + except KeyError as e: + raise InvalidLayout('Layout is missing required entries: %s' % e) + + return config + + +def _GetPrimaryEntryArrayLBA(config): + """Return the start LBA of the primary partition entry array. + + Normally this comes after the primary GPT header but can be adjusted by + setting the "primary_entry_array_lba" key under "metadata" in the config. + + Args: + config: The config dictionary. + + Returns: + The position of the primary partition entry array. + """ + + pmbr_and_header_size = SIZE_OF_PMBR + SIZE_OF_GPT_HEADER + entry_array = config['metadata'].get('primary_entry_array_lba', + pmbr_and_header_size) + if entry_array < pmbr_and_header_size: + raise InvalidLayout('Primary entry array (%d) must be at least %d.' % + entry_array, pmbr_and_header_size) + return entry_array + + +def _HasBadEraseBlocks(partitions): + return 'max_bad_erase_blocks' in GetMetadataPartition(partitions) + + +def _HasExternalGpt(partitions): + return GetMetadataPartition(partitions).get('external_gpt', False) + + +def _GetStartSector(config, partitions): + """Return the first usable location (LBA) for partitions. + + This value is the first LBA after the PMBR, the primary GPT header, and + partition entry array. + + We round it up to 64 to maintain the same layout as before in the normal (no + padding between the primary GPT header and its partition entry array) case. + + Args: + config: The config dictionary. + partitions: List of partitions to process + + Returns: + A suitable LBA for partitions, at least 64. + """ + + if _HasExternalGpt(partitions): + # If the GPT is external, then the offset of the partitions' actual data + # will be 0, and we don't need to make space at the beginning for the GPT. + return 0 + else: + entry_array = _GetPrimaryEntryArrayLBA(config) + start_sector = max(entry_array + SIZE_OF_PARTITION_ENTRY_ARRAY, 64) + return start_sector + + +def GetTableTotals(config, partitions): + """Calculates total sizes/counts for a partition table. + + Args: + config: Partition configuration file object + partitions: List of partitions to process + + Returns: + Dict containing totals data + """ + + start_sector = _GetStartSector(config, partitions) + ret = { + 'expand_count': 0, + 'expand_min': 0, + 'block_count': start_sector * config['metadata']['block_size'] + } + + # Total up the size of all non-expanding partitions to get the minimum + # required disk size. + for partition in partitions: + if partition.get('num') == 'metadata': + continue + if 'expand' in partition['features']: + ret['expand_count'] += 1 + ret['expand_min'] += partition['blocks'] + else: + ret['block_count'] += partition['blocks'] + + # At present, only one expanding partition is permitted. + # Whilst it'd be possible to have two, we don't need this yet + # and it complicates things, so it's been left out for now. + if ret['expand_count'] > 1: + raise InvalidLayout('1 expand partition allowed, %d requested' + % ret['expand_count']) + + ret['min_disk_size'] = ret['block_count'] + ret['expand_min'] + + return ret + + +def GetPartitionTable(options, config, image_type): + """Generates requested image_type layout from a layout configuration. + + This loads the base table and then overlays the requested layout over + the base layout. + + Args: + options: Flags passed to the script + config: Partition configuration file object + image_type: Type of image eg base/test/dev/factory_install + + Returns: + Object representing a selected partition table + """ + + # We make a deep copy so that changes to the dictionaries in this list do not + # persist across calls. + partitions = copy.deepcopy(config['layouts'][image_type]) + metadata = config['metadata'] + + # Convert fs_options to a string. + for partition in partitions: + fs_options = partition.get('fs_options', '') + if isinstance(fs_options, dict): + fs_format = partition.get('fs_format') + fs_options = fs_options.get(fs_format, '') + elif not isinstance(fs_options, basestring): + raise InvalidLayout('Partition number %s: fs_format must be a string or ' + 'dict, not %s' % (partition.get('num'), + type(fs_options))) + if '"' in fs_options or "'" in fs_options: + raise InvalidLayout('Partition number %s: fs_format cannot have quotes' % + partition.get('num')) + partition['fs_options'] = fs_options + + for adjustment_str in options.adjust_part.split(): + adjustment = adjustment_str.split(':') + if len(adjustment) < 2: + raise InvalidAdjustment('Adjustment "%s" is incomplete' % adjustment_str) + + label = adjustment[0] + operator = adjustment[1][0] + operand = adjustment[1][1:] + ApplyPartitionAdjustment(partitions, metadata, label, operator, operand) + + return partitions + + +def ApplyPartitionAdjustment(partitions, metadata, label, operator, operand): + """Applies an adjustment to a partition specified by label + + Args: + partitions: Partition table to modify + metadata: Partition table metadata + label: The label of the partition to adjust + operator: Type of adjustment (+/-/=) + operand: How much to adjust by + """ + + partition = GetPartitionByLabel(partitions, label) + + operand_bytes = ParseHumanNumber(operand) + if operand_bytes % metadata['block_size'] == 0: + operand_blocks = operand_bytes / metadata['block_size'] + else: + raise InvalidAdjustment('Adjustment size %s not divisible by block size %s' + % (operand_bytes, metadata['block_size'])) + + if operator == '+': + partition['blocks'] += operand_blocks + partition['bytes'] += operand_bytes + elif operator == '-': + partition['blocks'] -= operand_blocks + partition['bytes'] -= operand_bytes + elif operator == '=': + partition['blocks'] = operand_blocks + partition['bytes'] = operand_bytes + else: + raise ValueError('unknown operator %s' % operator) + + if partition['type'] == 'rootfs': + # If we're adjusting a rootFS partition, we assume the full partition size + # specified is being used for the filesytem, minus the space reserved for + # the hashpad. + partition['fs_bytes'] = partition['bytes'] + partition['fs_blocks'] = partition['fs_bytes'] / metadata['fs_block_size'] + partition['blocks'] = int(partition['blocks'] * 1.15) + partition['bytes'] = partition['blocks'] * metadata['block_size'] + + +def GetPartitionTableFromConfig(options, layout_filename, image_type): + """Loads a partition table and returns a given partition table type + + Args: + options: Flags passed to the script + layout_filename: The filename to load tables from + image_type: The type of partition table to return + """ + + config = LoadPartitionConfig(layout_filename) + partitions = GetPartitionTable(options, config, image_type) + + return partitions + + +def GetScriptShell(): + """Loads and returns the skeleton script for our output script. + + Returns: + A string containing the skeleton script + """ + + script_shell_path = os.path.join(os.path.dirname(__file__), 'cgpt_shell.sh') + with open(script_shell_path, 'r') as f: + script_shell = ''.join(f.readlines()) + + # Before we return, insert the path to this tool so somebody reading the + # script later can tell where it was generated. + script_shell = script_shell.replace('@SCRIPT_GENERATOR@', script_shell_path) + + return script_shell + + +def GetFullPartitionSize(partition, metadata): + """Get the size of the partition including metadata/reserved space in bytes. + + The partition only has to be bigger for raw NAND devices. Formula: + - Add UBI per-block metadata (2 pages) if partition is UBI + - Round up to erase block size + - Add UBI per-partition metadata (4 blocks) if partition is UBI + - Add reserved erase blocks + """ + + erase_block_size = metadata.get('erase_block_size', 0) + size = partition['bytes'] + + if erase_block_size == 0: + return size + + # See "Flash space overhead" in + # http://www.linux-mtd.infradead.org/doc/ubi.html + # for overhead calculations. + is_ubi = partition.get('format') == 'ubi' + reserved_erase_blocks = partition.get('reserved_erase_blocks', 0) + page_size = metadata.get('page_size', 0) + + if is_ubi: + ubi_block_size = erase_block_size - 2 * page_size + erase_blocks = (size + ubi_block_size - 1) // ubi_block_size + size += erase_blocks * 2 * page_size + + erase_blocks = (size + erase_block_size - 1) // erase_block_size + size = erase_blocks * erase_block_size + + if is_ubi: + size += erase_block_size * 4 + + size += reserved_erase_blocks * erase_block_size + return size + + +def WriteLayoutFunction(options, sfile, func, image_type, config): + """Writes a shell script function to write out a given partition table. + + Args: + options: Flags passed to the script + sfile: File handle we're writing to + func: function of the layout: + for removable storage device: 'partition', + for the fixed storage device: 'base' + image_type: Type of image eg base/test/dev/factory_install + config: Partition configuration file object + """ + + partitions = GetPartitionTable(options, config, image_type) + metadata = GetMetadataPartition(partitions) + partition_totals = GetTableTotals(config, partitions) + + lines = [ + 'write_%s_table() {' % func, + ] + + if _HasExternalGpt(partitions): + # Read GPT from device to get size, then wipe it out and operate + # on GPT in tmpfs. We don't rely on cgpt's ability to deal + # directly with the GPT on SPI NOR flash because rewriting the + # table so many times would take a long time (>30min). + # Also, wiping out the previous GPT with create_image won't work + # for NAND and there's no equivalent via cgpt. + lines += [ + 'gptfile=$(mktemp)', + 'flashrom -r -iRW_GPT:${gptfile}', + 'gptsize=$(stat ${gptfile} --format %s)', + 'dd if=/dev/zero of=${gptfile} bs=${gptsize} count=1', + 'target="-D %d ${gptfile}"' % metadata['bytes'], + ] + else: + lines += [ + 'local target="$1"', + 'create_image "${target}" %d %s' % ( + partition_totals['min_disk_size'], + config['metadata']['block_size']), + ] + + # ${target} is referenced unquoted because it may expand into multiple + # arguments in the case of NAND + lines += [ + 'local curr=%d' % _GetStartSector(config, partitions), + '# Create the GPT headers and tables. Pad the primary ones.', + './cgpt create -p %d ${target}' % (_GetPrimaryEntryArrayLBA(config) - + (SIZE_OF_PMBR + SIZE_OF_GPT_HEADER)), + ] + + metadata = GetMetadataPartition(partitions) + # Pass 1: Set up the expanding partition size. + for partition in partitions: + if partition.get('num') == 'metadata': + continue + partition['var'] = (GetFullPartitionSize(partition, metadata) / + config['metadata']['block_size']) + + if (partition.get('type') != 'blank' and partition['num'] == 1 and + 'expand' in partition['features']): + lines += [ + 'local stateful_size=%s' % partition['blocks'], + 'if [ -b "${target}" ]; then', + ' stateful_size=$(( $(numsectors "${target}") - %d))' % ( + partition_totals['block_count']), + 'fi', + ': $(( stateful_size -= (stateful_size %% %d) ))' % ( + config['metadata']['fs_block_size']), + ] + partition['var'] = '${stateful_size}' + + # Pass 2: Write out all the cgpt add commands. + for partition in partitions: + if partition.get('num') == 'metadata': + continue + if partition['type'] != 'blank': + lines += [ + './cgpt add -i %d -b ${curr} -s %s -t %s -l "%s" ${target}' % ( + partition['num'], str(partition['var']), partition['type'], + partition['label']), + ] + + # Increment the curr counter ready for the next partition. + if partition['var'] != 0 and partition.get('num') != 'metadata': + lines += [ + ': $(( curr += %s ))' % partition['var'], + ] + + # Set default priorities and retry counter on kernel partitions. + tries = 15 + prio = 15 + # The order of partition numbers in this loop matters. + # Make sure partition #2 is the first one, since it will be marked as + # default bootable partition. + for partition in GetPartitionsByType(partitions, 'kernel'): + lines += [ + './cgpt add -i %s -S 0 -T %i -P %i ${target}' % + (partition['num'], tries, prio) + ] + prio = 0 + # When not writing 'base' function, make sure the other partitions are + # marked as non-bootable (retry count == 0), since the USB layout + # doesn't have any valid data in slots B & C. But with base function, + # called by chromeos-install script, the KERNEL A partition is replicated + # into both slots A & B, so we should leave both bootable for error + # recovery in this case. + if func != 'base': + tries = 0 + + efi_partitions = GetPartitionsByType(partitions, 'efi') + if efi_partitions: + lines += [ + './cgpt boot -p -b $2 -i %d ${target}' % efi_partitions[0]['num'], + './cgpt add -i %s -B 1 ${target}' % efi_partitions[0]['num'], + ] + else: + # Provide a PMBR all the time for boot loaders (like u-boot) + # that expect one to always be there. + lines += [ + './cgpt boot -p -b $2 ${target}', + ] + + if metadata.get('hybrid_mbr'): + lines += ['install_hybrid_mbr ${target}'] + lines += ['./cgpt show ${target}'] + + if _HasExternalGpt(partitions): + lines += ['flashrom -w -iRW_GPT:${gptfile} --fast-verify'] + + sfile.write('%s\n}\n' % '\n '.join(lines)) + + +def WritePartitionSizesFunction(options, sfile, func, image_type, config): + """Writes out the partition size variable that can be extracted by a caller. + + Args: + options: Flags passed to the script + sfile: File handle we're writing to + func: function of the layout: + for removable storage device: 'partition', + for the fixed storage device: 'base' + image_type: Type of image eg base/test/dev/factory_install + config: Partition configuration file object + """ + func_name = 'load_%s_vars' % func + lines = [ + '%s() {' % func_name, + 'DEFAULT_ROOTDEV="%s"' % config['metadata'].get('rootdev_%s' % func, ''), + ] + + partitions = GetPartitionTable(options, config, image_type) + for partition in partitions: + if partition.get('num') == 'metadata': + continue + for key in ('label', 'num'): + if key in partition: + shell_label = str(partition[key]).replace('-', '_').upper() + part_bytes = partition['bytes'] + reserved_ebs = partition.get('reserved_erase_blocks', 0) + fs_bytes = partition.get('fs_bytes', part_bytes) + part_format = partition.get('format', '') + fs_format = partition.get('fs_format', '') + fs_options = partition.get('fs_options', '') + partition_num = partition.get('num', '') + lines += [ + 'PARTITION_SIZE_%s=%s' % (shell_label, part_bytes), + ' RESERVED_EBS_%s=%s' % (shell_label, reserved_ebs), + ' DATA_SIZE_%s=%s' % (shell_label, fs_bytes), + ' FORMAT_%s=%s' % (shell_label, part_format), + ' FS_FORMAT_%s=%s' % (shell_label, fs_format), + ' FS_OPTIONS_%s="%s"' % (shell_label, fs_options), + ' PARTITION_NUM_%s="%s"' % (shell_label, partition_num), + ] + + sfile.write('%s\n}\n' % '\n '.join(lines)) + + +def GetPartitionByNumber(partitions, num): + """Given a partition table and number returns the partition object. + + Args: + partitions: List of partitions to search in + num: Number of partition to find + + Returns: + An object for the selected partition + """ + for partition in partitions: + if partition.get('num') == int(num): + return partition + + raise PartitionNotFound('Partition %s not found' % num) + + +def GetPartitionsByType(partitions, typename): + """Given a partition table and type returns the partitions of the type. + + Partitions are sorted in num order. + + Args: + partitions: List of partitions to search in + typename: The type of partitions to select + + Returns: + A list of partitions of the type + """ + out = [] + for partition in partitions: + if partition.get('type') == typename: + out.append(partition) + return sorted(out, key=lambda partition: partition.get('num')) + + +def GetMetadataPartition(partitions): + """Given a partition table returns the metadata partition object. + + Args: + partitions: List of partitions to search in + + Returns: + An object for the metadata partition + """ + for partition in partitions: + if partition.get('num') == 'metadata': + return partition + + return {} + + +def GetPartitionByLabel(partitions, label): + """Given a partition table and label returns the partition object. + + Args: + partitions: List of partitions to search in + label: Label of partition to find + + Returns: + An object for the selected partition + """ + for partition in partitions: + if 'label' not in partition: + continue + if partition['label'] == label: + return partition + + raise PartitionNotFound('Partition "%s" not found' % label) + + +def WritePartitionScript(options, image_type, layout_filename, sfilename): + """Writes a shell script with functions for the base and requested layouts. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + sfilename: Filename to write the finished script to + """ + config = LoadPartitionConfig(layout_filename) + + with open(sfilename, 'w') as f: + script_shell = GetScriptShell() + f.write(script_shell) + + for func, layout in (('base', BASE_LAYOUT), ('partition', image_type)): + WriteLayoutFunction(options, f, func, layout, config) + WritePartitionSizesFunction(options, f, func, layout, config) + + # TODO: Backwards compat. Should be killed off once we update + # cros_generate_update_payload to use the new code. + partitions = GetPartitionTable(options, config, BASE_LAYOUT) + partition = GetPartitionByLabel(partitions, 'ROOT-A') + f.write('ROOTFS_PARTITION_SIZE=%s\n' % (partition['bytes'],)) + + +def GetBlockSize(_options, layout_filename): + """Returns the partition table block size. + + Args: + options: Flags passed to the script + layout_filename: Path to partition configuration file + + Returns: + Block size of all partitions in the layout + """ + + config = LoadPartitionConfig(layout_filename) + return config['metadata']['block_size'] + + +def GetFilesystemBlockSize(_options, layout_filename): + """Returns the filesystem block size. + + This is used for all partitions in the table that have filesystems. + + Args: + options: Flags passed to the script + layout_filename: Path to partition configuration file + + Returns: + Block size of all filesystems in the layout + """ + + config = LoadPartitionConfig(layout_filename) + return config['metadata']['fs_block_size'] + + +def GetImageTypes(_options, layout_filename): + """Returns a list of all the image types in the layout. + + Args: + options: Flags passed to the script + layout_filename: Path to partition configuration file + + Returns: + List of all image types + """ + + config = LoadPartitionConfig(layout_filename) + return ' '.join(config['layouts'].keys()) + + +def GetType(options, image_type, layout_filename, num): + """Returns the type of a given partition for a given layout. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + Type of the specified partition. + """ + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + return partition.get('type') + + +def GetPartitions(options, image_type, layout_filename): + """Returns the partition numbers for the image_type. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + + Returns: + A space delimited string of partition numbers. + """ + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + return ' '.join(str(p['num']) for p in partitions + if 'num' in p and p['num'] != 'metadata') + + +def GetUUID(options, image_type, layout_filename, num): + """Returns the filesystem UUID of a given partition for a given layout type. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + UUID of specified partition. Defaults to random if not set. + """ + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + return partition.get('uuid', 'random') + + +def GetPartitionSize(options, image_type, layout_filename, num): + """Returns the partition size of a given partition for a given layout type. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + Size of selected partition in bytes + """ + + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + + return partition['bytes'] + + +def GetFilesystemFormat(options, image_type, layout_filename, num): + """Returns the filesystem format of a given partition for a given layout type. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + Format of the selected partition's filesystem + """ + + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + + return partition.get('fs_format') + + +def GetFormat(options, image_type, layout_filename, num): + """Returns the format of a given partition for a given layout type. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + Format of the selected partition's filesystem + """ + + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + + return partition.get('format') + + +def GetFilesystemOptions(options, image_type, layout_filename, num): + """Returns the filesystem options of a given partition and layout type. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + The selected partition's filesystem options + """ + + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + + return partition.get('fs_options') + + +def GetFilesystemSize(options, image_type, layout_filename, num): + """Returns the filesystem size of a given partition for a given layout type. + + If no filesystem size is specified, returns the partition size. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + Size of selected partition filesystem in bytes + """ + + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + + if 'fs_bytes' in partition: + return partition['fs_bytes'] + else: + return partition['bytes'] + + +def GetLabel(options, image_type, layout_filename, num): + """Returns the label for a given partition. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + Label of selected partition, or 'UNTITLED' if none specified + """ + + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + + if 'label' in partition: + return partition['label'] + else: + return 'UNTITLED' + + +def GetNumber(options, image_type, layout_filename, label): + """Returns the partition number of a given label. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + label: Number of the partition you want to read from + + Returns: + The number of the partition corresponding to the label. + """ + + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByLabel(partitions, label) + return partition['num'] + + +def GetReservedEraseBlocks(options, image_type, layout_filename, num): + """Returns the number of erase blocks reserved in the partition. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + num: Number of the partition you want to read from + + Returns: + Number of reserved erase blocks + """ + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + partition = GetPartitionByNumber(partitions, num) + if 'reserved_erase_blocks' in partition: + return partition['reserved_erase_blocks'] + else: + return 0 + + +def DoDebugOutput(options, image_type, layout_filename): + """Prints out a human readable disk layout in on-disk order. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + """ + config = LoadPartitionConfig(layout_filename) + partitions = GetPartitionTable(options, config, image_type) + + label_len = max([len(x['label']) for x in partitions if 'label' in x]) + type_len = max([len(x['type']) for x in partitions if 'type' in x]) + + msg = 'num:%4s label:%-*s type:%-*s size:%-10s fs_size:%-10s features:%s' + + # Print out non-layout options first. + print('Config Data') + metadata_msg = 'field:%-14s value:%s' + for key in config.keys(): + if key not in ('layouts', '_comment'): + print(metadata_msg % (key, config[key])) + + print('\n%s Layout Data' % image_type.upper()) + for partition in partitions: + if partition.get('num') == 'metadata': + continue + + size = ProduceHumanNumber(partition['bytes']) + if 'fs_bytes' in partition.iterkeys(): + fs_size = ProduceHumanNumber(partition['fs_bytes']) + else: + fs_size = 'auto' + + print(msg % ( + partition.get('num', 'auto'), + label_len, + partition.get('label', ''), + type_len, + partition.get('type', ''), + size, + fs_size, + partition.get('features', []), + )) + + +def CheckRootfsPartitionsMatch(partitions): + """Checks that rootfs partitions are substitutable with each other. + + This function asserts that either all rootfs partitions are in the same format + or none have a format, and it asserts that have the same number of reserved + erase blocks. + """ + partition_format = None + reserved_erase_blocks = -1 + for partition in partitions: + if partition.get('type') == 'rootfs': + new_format = partition.get('format', '') + new_reserved_erase_blocks = partition.get('reserved_erase_blocks', 0) + + if partition_format is None: + partition_format = new_format + reserved_erase_blocks = new_reserved_erase_blocks + + if new_format != partition_format: + raise MismatchedRootfsFormat( + 'mismatched rootfs formats: "%s" and "%s"' % + (partition_format, new_format)) + + if reserved_erase_blocks != new_reserved_erase_blocks: + raise MismatchedRootfsBlocks( + 'mismatched rootfs reserved erase block counts: %s and %s' % + (reserved_erase_blocks, new_reserved_erase_blocks)) + + +def Combinations(n, k): + """Calculate the binomial coefficient, i.e., "n choose k" + + This calculates the number of ways that k items can be chosen from + a set of size n. For example, if there are n blocks and k of them + are bad, then this returns the number of ways that the bad blocks + can be distributed over the device. + See http://en.wikipedia.org/wiki/Binomial_coefficient + + For convenience to the caller, this function allows impossible cases + as input and returns 0 for them. + """ + if k < 0 or n < k: + return 0 + return math.factorial(n) / (math.factorial(k) * math.factorial(n - k)) + + +def CheckReservedEraseBlocks(partitions): + """Checks that the reserved_erase_blocks in each partition is good. + + This function checks that a reasonable value was given for the reserved + erase block count. In particular, it checks that there's a less than + 1 in 100k probability that, if the manufacturer's maximum bad erase + block count is met, and assuming bad blocks are uniformly randomly + distributed, then more bad blocks will fall in this partition than are + reserved. Smaller partitions need a larger reserve percentage. + + We take the number of reserved blocks as a parameter in disk_layout.json + rather than just calculating the value so that it can be tweaked + explicitly along with others in squeezing the image onto flash. But + we check it so that users have an easy method for determining what's + acceptable--just try out a new value and do ./build_image. + """ + for partition in partitions: + if ('reserved_erase_blocks' in partition or + partition.get('format') in ('ubi', 'nand')): + if partition.get('bytes', 0) == 0: + continue + metadata = GetMetadataPartition(partitions) + if (not _HasBadEraseBlocks(partitions) + or 'reserved_erase_blocks' not in partition + or 'bytes' not in metadata + or 'erase_block_size' not in metadata + or 'page_size' not in metadata): + raise MissingEraseBlockField( + 'unable to check if partition %s will have too many bad blocks due ' + 'to missing metadata field' % partition['label']) + + reserved = partition['reserved_erase_blocks'] + erase_block_size = metadata['erase_block_size'] + device_erase_blocks = metadata['bytes'] / erase_block_size + device_bad_blocks = metadata['max_bad_erase_blocks'] + distributions = Combinations(device_erase_blocks, device_bad_blocks) + partition_erase_blocks = partition['bytes'] / erase_block_size + # The idea is to calculate the number of ways that there could be reserved + # or more bad blocks inside the partition, assuming that there are + # device_bad_blocks in the device in total (the worst case). To get the + # probability, we divide this count by the total number of ways that the + # bad blocks can be distributed on the whole device. To find the first + # number, we sum over increasing values for the count of bad blocks within + # the partition the number of ways that those bad blocks can be inside the + # partition, multiplied by the number of ways that the remaining blocks + # can be distributed outside of the partition. + ways_for_failure = sum( + Combinations(partition_erase_blocks, partition_bad_blocks) * + Combinations(device_erase_blocks - partition_erase_blocks, + device_bad_blocks - partition_bad_blocks) + for partition_bad_blocks + in range(reserved + 1, device_bad_blocks + 1)) + probability = (1.0 * ways_for_failure) / distributions + if probability > 0.00001: + raise ExcessFailureProbability('excessive probability %f of too many ' + 'bad blocks in partition %s' + % (probability, partition['label'])) + + +def CheckSimpleNandProperties(partitions): + """Checks that NAND partitions are erase-block-aligned and not expand""" + if not _HasBadEraseBlocks(partitions): + return + metadata = GetMetadataPartition(partitions) + for partition in partitions: + erase_block_size = metadata['erase_block_size'] + if partition['bytes'] % erase_block_size != 0: + raise UnalignedPartition( + 'partition size %s does not divide erase block size %s' % + (partition['bytes'], erase_block_size)) + if 'expand' in partition['features']: + raise ExpandNandImpossible( + 'expand partitions may not be used with raw NAND') + + +def CheckTotalSize(partitions): + """Checks that the sum size of all partitions fits within the device""" + metadata = GetMetadataPartition(partitions) + if 'bytes' not in metadata: + return + capacity = metadata['bytes'] + total = sum(GetFullPartitionSize(partition, metadata) + for partition in partitions if partition.get('num') != 'metadata') + if total > capacity: + raise ExcessPartitionSize('capacity = %d, total=%d' % (capacity, total)) + + +def Validate(options, image_type, layout_filename): + """Validates a layout file, used before reading sizes to check for errors. + + Args: + options: Flags passed to the script + image_type: Type of image eg base/test/dev/factory_install + layout_filename: Path to partition configuration file + """ + partitions = GetPartitionTableFromConfig(options, layout_filename, image_type) + CheckRootfsPartitionsMatch(partitions) + CheckTotalSize(partitions) + CheckSimpleNandProperties(partitions) + CheckReservedEraseBlocks(partitions) + + +def main(argv): + action_map = { + 'write': { + 'usage': ['', '', ''], + 'func': WritePartitionScript, + }, + 'readblocksize': { + 'usage': [''], + 'func': GetBlockSize, + }, + 'readfsblocksize': { + 'usage': [''], + 'func': GetFilesystemBlockSize, + }, + 'readpartsize': { + 'usage': ['', '', ''], + 'func': GetPartitionSize, + }, + 'readformat': { + 'usage': ['', '', ''], + 'func': GetFormat, + }, + 'readfsformat': { + 'usage': ['', '', ''], + 'func': GetFilesystemFormat, + }, + 'readfssize': { + 'usage': ['', '', ''], + 'func': GetFilesystemSize, + }, + 'readimagetypes': { + 'usage': [''], + 'func': GetImageTypes, + }, + 'readfsoptions': { + 'usage': ['', '', ''], + 'func': GetFilesystemOptions, + }, + 'readlabel': { + 'usage': ['', '', ''], + 'func': GetLabel, + }, + 'readnumber': { + 'usage': ['', '', ''], + 'func': GetNumber, + }, + 'readreservederaseblocks': { + 'usage': ['', '', ''], + 'func': GetReservedEraseBlocks, + }, + 'readtype': { + 'usage': ['', '', ''], + 'func': GetType, + }, + 'readpartitionnums': { + 'usage': ['', ''], + 'func': GetPartitions, + }, + 'readuuid': { + 'usage': ['', '', ''], + 'func': GetUUID, + }, + 'debug': { + 'usage': ['', ''], + 'func': DoDebugOutput, + }, + 'validate': { + 'usage': ['', ''], + 'func': Validate, + }, + } + + usage = """%(prog)s [options] + +For information on the JSON format, see: + http://dev.chromium.org/chromium-os/developer-guide/disk-layout-format + +The --adjust_part flag takes arguments like: +