Add recipes that will output a Linux image and a initramfs that contains the Morello busybox and the purecap app. The glibc version can take an arbitrary Yocto image as the rootfs. The Yocto rootfs is not tested yet but can be easily done by adding an extra grub menu option.
Signed-off-by: Pawel Zalewski pzalewski@thegoodpenguin.co.uk --- conf/machine/morello-linux-glibc.conf | 17 ++++ conf/machine/morello-linux-musl.conf | 13 +++ .../images/morello-initramfs-glibc.bb | 60 ++++++++++++ .../images/morello-initramfs-musl.bb | 59 +++++++++++ .../images/morello-linux-glibc/files/init.sh | 33 +++++++ .../files/initramfs.list.template | 18 ++++ .../images/morello-linux-image-glibc.bb | 93 ++++++++++++++++++ .../images/morello-linux-image-musl.bb | 98 +++++++++++++++++++ .../images/morello-linux-musl/files/init.sh | 33 +++++++ .../files/initramfs.list.template | 18 ++++ 10 files changed, 442 insertions(+) create mode 100644 recipes-morello/images/morello-initramfs-glibc.bb create mode 100644 recipes-morello/images/morello-initramfs-musl.bb create mode 100644 recipes-morello/images/morello-linux-glibc/files/init.sh create mode 100644 recipes-morello/images/morello-linux-glibc/files/initramfs.list.template create mode 100644 recipes-morello/images/morello-linux-image-glibc.bb create mode 100644 recipes-morello/images/morello-linux-image-musl.bb create mode 100644 recipes-morello/images/morello-linux-musl/files/init.sh create mode 100644 recipes-morello/images/morello-linux-musl/files/initramfs.list.template
diff --git a/conf/machine/morello-linux-glibc.conf b/conf/machine/morello-linux-glibc.conf index 6ed824d..decfc7e 100644 --- a/conf/machine/morello-linux-glibc.conf +++ b/conf/machine/morello-linux-glibc.conf @@ -1,3 +1,20 @@ require conf/machine/include/morello-common.inc
MACHINEOVERRIDES =. "morello-linux-glibc:" + +PREFERRED_PROVIDER_virtual/kernel ?= "linux-morello-${MORELLO_RELEASE_VERSION}" +PREFFERED_PROVIDER_morello-linux-image ?= "morello-linux-image-glibc" +PREFFERED_PROVIDER_morello-initramfs ?= "morello-initramfs-glibc" + +BSP_GRUB_DIR ?= "${TOPDIR}/${TMPDIR_BSP}-glibc/deploy/images/morello-bsp/grub-efi" +BSP_DTB_DIR ?= "${TOPDIR}/${TMPDIR_BSP}-glibc/deploy/images/morello-bsp/trusted-firmware-a" + +TMPDIR = "${TOPDIR}/${TMPDIR_LINUX}" + +MUSL_INSTALL_DIR ?= "/${GLOBAL_ARCH_TRIPLE}" + +IMAGE_BASENAME = "rootfs" +IMAGE_FSTYPES:append = " ext4" + +# The rootfs image that will go on a bootable drive +MORELLO_ROOTFS_IMAGE = "core-image-minimal" \ No newline at end of file diff --git a/conf/machine/morello-linux-musl.conf b/conf/machine/morello-linux-musl.conf index e22493d..bac2d40 100644 --- a/conf/machine/morello-linux-musl.conf +++ b/conf/machine/morello-linux-musl.conf @@ -1,3 +1,16 @@ require conf/machine/include/morello-common.inc
MACHINEOVERRIDES =. "morello-linux-musl:" + +TARGET_OS ?= "linux-musl" + +PREFERRED_PROVIDER_virtual/kernel ?= "linux-morello-${MORELLO_RELEASE_VERSION}" +PREFFERED_PROVIDER_morello-linux-image ?= "morello-linux-image-musl" +PREFFERED_PROVIDER_morello-initramfs ?= "morello-initramfs-musl" + +TCLIBC = "musl" + +BSP_GRUB_DIR ?= "${TOPDIR}/${TMPDIR_BSP}-glibc/deploy/images/morello-bsp/grub-efi" +BSP_DTB_DIR ?= "${TOPDIR}/${TMPDIR_BSP}-glibc/deploy/images/morello-bsp/trusted-firmware-a" + +TMPDIR = "${TOPDIR}/${TMPDIR_LINUX}" \ No newline at end of file diff --git a/recipes-morello/images/morello-initramfs-glibc.bb b/recipes-morello/images/morello-initramfs-glibc.bb new file mode 100644 index 0000000..2547de6 --- /dev/null +++ b/recipes-morello/images/morello-initramfs-glibc.bb @@ -0,0 +1,60 @@ +inherit deploy nopackages + +COMPATIBLE_MACHINE = "morello-linux-glibc" +SUMMARY = "Initramfs for busybox" +DESCRIPTION = "Initramfs for busybox, bypassing the Yocto way" +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" +OUTPUTS_NAME = "morello-initramfs" + +DEPENDS += "virtual/morello-busybox gen-init-cpio-native pure-cap-app" +PROVIDES = "${OUTPUTS_NAME}" + +BB_DONT_CACHE = "1" +INHIBIT_DEFAULT_DEPS = "1" + +FILESEXTRAPATHS:prepend := "${THISDIR}:" + +SRC_URI = "file://files/init.sh \ + file://files/initramfs.list.template \ + " +do_configure[noexec] = "1" +do_compile[noexec] = "1" +do_deploy[depends] = "virtual/morello-busybox:do_populate_sysroot" + + +do_install() { + + local sysroot_prefix="recipe-sysroot" + + sed -e "s@%FILES%@/files@" \ + "${WORKDIR}/files/initramfs.list.template" > "${WORKDIR}/files/initramfs.list.tmp1" + + sed -e "s@%BUSYBOX%@/${sysroot_prefix}/busybox@" \ + "${WORKDIR}/files/initramfs.list.tmp1" > "${WORKDIR}/files/initramfs.list.tmp2" + + sed -e "s@%PREFIX%@/${sysroot_prefix}${prefix}@" \ + "${WORKDIR}/files/initramfs.list.tmp2" > "${WORKDIR}/files/initramfs.list.tmp1" + + sed -e "s@%APP_DIR%@/${APP_DIR}@" \ + "${WORKDIR}/files/initramfs.list.tmp1" > "${WORKDIR}/files/initramfs.list.tmp2" + + sed -e "s@%MUSL%@/${sysroot_prefix}/musl@" \ + "${WORKDIR}/files/initramfs.list.tmp2" > "${WORKDIR}/files/initramfs.list" + + install -d ${D}/${OUTPUTS_NAME} + + rm -f ${D}/${OUTPUTS_NAME}/initramfs + + { + env -C ${WORKDIR} ${STAGING_BINDIR_NATIVE}/gen_init_cpio "${WORKDIR}/files/initramfs.list" + env -C "${STAGING_DIR_TARGET}/${APP_DIR}/" find . -print0 | env -C "${STAGING_DIR_TARGET}/${APP_DIR}/" cpio --null --owner +0:+0 --create --format=newc + } > ${D}/${OUTPUTS_NAME}/initramfs + +} + +do_deploy() { + install -d ${DEPLOYDIR}/${OUTPUTS_NAME} + install ${D}/${OUTPUTS_NAME}/initramfs ${DEPLOYDIR}/${OUTPUTS_NAME}/initramfs +} +addtask deploy after do_install \ No newline at end of file diff --git a/recipes-morello/images/morello-initramfs-musl.bb b/recipes-morello/images/morello-initramfs-musl.bb new file mode 100644 index 0000000..585540d --- /dev/null +++ b/recipes-morello/images/morello-initramfs-musl.bb @@ -0,0 +1,59 @@ +inherit deploy nopackages + +COMPATIBLE_MACHINE = "morello-linux-musl" +SUMMARY = "Initramfs for busybox" +DESCRIPTION = "Initramfs for busybox, bypassing the Yocto way" +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" +OUTPUTS_NAME = "morello-initramfs" + +DEPENDS += "virtual/morello-busybox gen-init-cpio-native pure-cap-app" +PROVIDES = "${OUTPUTS_NAME}" + +BB_DONT_CACHE = "1" +INHIBIT_DEFAULT_DEPS = "1" + +FILESEXTRAPATHS:prepend := "${THISDIR}:" + +SRC_URI = "file://files/init.sh \ + file://files/initramfs.list.template \ + " +do_configure[noexec] = "1" +do_compile[noexec] = "1" +do_deploy[depends] = "virtual/morello-busybox:do_populate_sysroot" + + +do_install() { + + local sysroot_prefix="recipe-sysroot" + + sed -e "s@%FILES%@/files@" \ + "${WORKDIR}/files/initramfs.list.template" > "${WORKDIR}/files/initramfs.list.tmp1" + + sed -e "s@%BUSYBOX%@/${sysroot_prefix}/busybox@" \ + "${WORKDIR}/files/initramfs.list.tmp1" > "${WORKDIR}/files/initramfs.list.tmp2" + + sed -e "s@%PREFIX%@/${sysroot_prefix}${prefix}@" \ + "${WORKDIR}/files/initramfs.list.tmp2" > "${WORKDIR}/files/initramfs.list.tmp1" + + sed -e "s@%APP_DIR%@/${APP_DIR}@" \ + "${WORKDIR}/files/initramfs.list.tmp1" > "${WORKDIR}/files/initramfs.list.tmp2" + + sed -e "s@%MUSL%@/${sysroot_prefix}/musl@" \ + "${WORKDIR}/files/initramfs.list.tmp2" > "${WORKDIR}/files/initramfs.list" + + install -d ${D}/${OUTPUTS_NAME} + + rm -f ${D}/${OUTPUTS_NAME}/initramfs + + { + env -C ${WORKDIR} ${STAGING_BINDIR_NATIVE}/gen_init_cpio "${WORKDIR}/files/initramfs.list" + env -C "${STAGING_DIR_TARGET}/" find . -not -path "./sysroot-providers*" -print0 | env -C "${STAGING_DIR_TARGET}/" cpio --null --owner +0:+0 --create --format=newc + } > ${D}/${OUTPUTS_NAME}/initramfs +} + +do_deploy() { + install -d ${DEPLOYDIR}/${OUTPUTS_NAME} + install ${D}/${OUTPUTS_NAME}/initramfs ${DEPLOYDIR}/${OUTPUTS_NAME}/initramfs +} +addtask deploy after do_install \ No newline at end of file diff --git a/recipes-morello/images/morello-linux-glibc/files/init.sh b/recipes-morello/images/morello-linux-glibc/files/init.sh new file mode 100644 index 0000000..65a6d39 --- /dev/null +++ b/recipes-morello/images/morello-linux-glibc/files/init.sh @@ -0,0 +1,33 @@ +#!/bin/busybox sh + +# Copyright (c) 2021 Arm Limited. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +mount() { + /bin/busybox mount "$@" +} + +grep() { + /bin/busybox grep "$@" +} + +echo "Running init script" + +mount -t proc proc /proc +grep -qE $'\t'"devtmpfs$" /proc/filesystems && mount -t devtmpfs dev /dev +mount -t sysfs sysfs /sys + +echo "Installing busybox" + +/bin/busybox --install -s + +! grep -qE $'\t'"devtmpfs$" /proc/filesystems && mdev -s + +ulimit -c unlimited + +echo "/bin/sh as PID 1!" +echo "init.sh" +exec setsid cttyhack sh +echo setsid ctty hack failed so "exec /bin/sh" fallback will be used +exec /bin/sh \ No newline at end of file diff --git a/recipes-morello/images/morello-linux-glibc/files/initramfs.list.template b/recipes-morello/images/morello-linux-glibc/files/initramfs.list.template new file mode 100644 index 0000000..3da25be --- /dev/null +++ b/recipes-morello/images/morello-linux-glibc/files/initramfs.list.template @@ -0,0 +1,18 @@ +dir /bin 755 0 0 +dir /include 755 0 0 +dir /lib 755 0 0 +dir /share 755 0 0 +dir /dev 755 0 0 +dir /proc 755 0 0 +dir /sbin 755 0 0 +dir /sys 755 0 0 +dir /newroot 755 0 0 +dir /usr 755 0 0 +dir /usr/lib 755 0 0 +dir /usr/bin 755 0 0 +dir /etc 755 0 0 + +dir /%APP_DIR% 755 0 0 + +file /bin/busybox .%BUSYBOX%/busybox 755 0 0 +file /init .%FILES%/init.sh 755 0 0 \ No newline at end of file diff --git a/recipes-morello/images/morello-linux-image-glibc.bb b/recipes-morello/images/morello-linux-image-glibc.bb new file mode 100644 index 0000000..c72700b --- /dev/null +++ b/recipes-morello/images/morello-linux-image-glibc.bb @@ -0,0 +1,93 @@ +inherit deploy nopackages + +COMPATIBLE_MACHINE = "morello-linux-glibc" +SUMMARY = "Bootable Morello Linux Image" +DESCRIPTION = "Image that goes on a bootable device, can be DD'ed onto a USB stick" +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" +OUTPUTS_NAME = "morello-linux-image" + +INHIBIT_DEFAULT_DEPS = "1" + +DEPENDS += "virtual/kernel morello-initramfs mtools-native e2fsprogs-native coreutils-native bc-native util-linux-native" +PROVIDES = "${OUTPUTS_NAME}" + +IMAGE_SIZE = "100" + +# Yocto does not like function derived vars expanded in other functions, it does not like passing by argument from non-constant +# variables either...so I am just cheerfully hardcoding this as I have more important things to do in life +IMAGE_SECTORS = "204800" + +LBA = "512" +PART_START_ALIGNMENT = "2048" + +ESP_IMAGE = "${OUTPUTS_NAME}-esp" + +do_configure[noexec] = "1" +do_compile[noexec] = "1" +do_install[depends] += "${MORELLO_ROOTFS_IMAGE}:do_image_complete" + +def get_next_part_start (d): + next_image_start = int(d.getVar('IMAGE_SECTORS')) + int(d.getVar('PART_START_ALIGNMENT')) + int(d.getVar('PART_START_ALIGNMENT')) - 1 + next_image_start = next_image_start & ~(int(d.getVar('PART_START_ALIGNMENT')) -1) + return next_image_start + +add_to_image() { + mcopy -i ${1} -m -D overwrite ${2} ::${3} +} + +mult() { + local ret=$(echo "${1} * ${2}" | bc) + echo ${ret} +} + +create_gpt() { + + local esp_type="C12A7328-F81F-11D2-BA4B-00A0C93EC93B" + local linux_type="0FC63DAF-8483-4772-8E79-3D69D8477DE4" + + local part_start_esp=${PART_START_ALIGNMENT} + local part_start_linux="${@get_next_part_start(d)}" + + { + echo "label: gpt" + echo "start=${part_start_esp}, size=${IMAGE_SECTORS}, name=ESP, type=${esp_type}" + echo "start=${part_start_linux}, size=${IMAGE_SECTORS}, name=root, type=${linux_type}" + } | sfdisk -q "${1}" + + dd if="${2}" of="${1}" seek=$(mult ${part_start_esp} ${LBA}) bs=8M conv=notrunc,sparse oflag=seek_bytes status=progress + dd if="${3}" of="${1}" seek=$(mult ${part_start_linux} ${LBA}) bs=8M conv=notrunc,sparse oflag=seek_bytes status=progress +} + +do_install() { + + local part0="${BSP_GRUB_DIR}/grub-efi-bootaa64.efi" + local part1="${BSP_GRUB_DIR}/grub-config.cfg" + local part2="${BSP_DTB_DIR}/morello-soc.dtb" + local part3="${DEPLOY_DIR}/images/morello-linux-glibc/Image" + local part4="${DEPLOY_DIR}/images/morello-linux-glibc/morello-initramfs/initramfs" + + # create the ESP + dd if=/dev/zero of=${ESP_IMAGE}.img bs=1024K count=${IMAGE_SIZE} + mformat -i ${ESP_IMAGE}.img -v ESP :: + + mmd -i ${ESP_IMAGE}.img ::/EFI + mmd -i ${ESP_IMAGE}.img ::/EFI/BOOT + + add_to_image ${ESP_IMAGE}.img ${part0} /EFI/BOOT/BOOTAA64.EFI + add_to_image ${ESP_IMAGE}.img ${part1} /EFI/BOOT/grub.cfg + add_to_image ${ESP_IMAGE}.img ${part2} /morello.dtb + add_to_image ${ESP_IMAGE}.img ${part3} /Image + add_to_image ${ESP_IMAGE}.img ${part4} /initramfs + + : > ${OUTPUTS_NAME}.img + truncate --size="$(mult ${IMAGE_SIZE} 3)M" ${OUTPUTS_NAME}.img + + create_gpt ${OUTPUTS_NAME}.img ${ESP_IMAGE}.img ${DEPLOY_DIR}/images/morello-linux-glibc/rootfs-morello-linux-glibc.ext4 + install ${OUTPUTS_NAME}.img ${D}/${OUTPUTS_NAME}.img +} + +do_deploy() { + install ${D}/${OUTPUTS_NAME}.img ${DEPLOYDIR}/${OUTPUTS_NAME}-${MORELLO_ARCH}-${TCLIBC}.img +} +addtask deploy after do_install \ No newline at end of file diff --git a/recipes-morello/images/morello-linux-image-musl.bb b/recipes-morello/images/morello-linux-image-musl.bb new file mode 100644 index 0000000..e1d9b94 --- /dev/null +++ b/recipes-morello/images/morello-linux-image-musl.bb @@ -0,0 +1,98 @@ +inherit deploy nopackages + +COMPATIBLE_MACHINE = "morello-linux-musl" +SUMMARY = "Bootable Morello Linux Image" +DESCRIPTION = "Image that goes on a bootable device, can be DD'ed onto a USB stick" +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" +OUTPUTS_NAME = "morello-linux-image" + +BB_DONT_CACHE = "1" +INHIBIT_DEFAULT_DEPS = "1" + +DEPENDS += "virtual/kernel morello-initramfs mtools-native e2fsprogs-native coreutils-native bc-native util-linux-native" +PROVIDES = "${OUTPUTS_NAME}" + +IMAGE_SIZE = "100" + +# Yocto does not like function derived vars expanded in other functions, it does not like passing by argument from non-constant +# variables either...so I am just cheerfully hardcoding this as I have more important things to do in life +IMAGE_SECTORS = "204800" + +LBA = "512" +PART_START_ALIGNMENT = "2048" + +ESP_IMAGE = "${OUTPUTS_NAME}-esp" + +do_configure[noexec] = "1" +do_compile[noexec] = "1" + +def get_next_part_start (d): + next_image_start = int(d.getVar('IMAGE_SECTORS')) + int(d.getVar('PART_START_ALIGNMENT')) + int(d.getVar('PART_START_ALIGNMENT')) - 1 + next_image_start = next_image_start & ~(int(d.getVar('PART_START_ALIGNMENT')) -1) + return next_image_start + +add_to_image() { + mcopy -i ${1} -m -D overwrite ${2} ::${3} +} + +mult() { + local ret=$(echo "${1} * ${2}" | bc) + echo ${ret} +} + +create_gpt() { + + local esp_type="C12A7328-F81F-11D2-BA4B-00A0C93EC93B" + local linux_type="0FC63DAF-8483-4772-8E79-3D69D8477DE4" + + local part_start_esp=${PART_START_ALIGNMENT} + local part_start_linux="${@get_next_part_start(d)}" + + { + echo "label: gpt" + echo "start=${part_start_esp}, size=${IMAGE_SECTORS}, name=ESP, type=${esp_type}" + echo "start=${part_start_linux}, size=${IMAGE_SECTORS}, name=root, type=${linux_type}" + } | sfdisk -q "${1}" + + dd if="${2}" of="${1}" seek=$(mult ${part_start_esp} ${LBA}) bs=8M conv=notrunc,sparse oflag=seek_bytes status=progress + dd if="${3}" of="${1}" seek=$(mult ${part_start_linux} ${LBA}) bs=8M conv=notrunc,sparse oflag=seek_bytes status=progress +} + +do_install() { + + local part0="${BSP_GRUB_DIR}/grub-efi-bootaa64.efi" + local part1="${BSP_GRUB_DIR}/grub-config.cfg" + local part2="${BSP_DTB_DIR}/morello-soc.dtb" + local part3="${DEPLOY_DIR}/images/morello-linux-musl/Image" + local part4="${DEPLOY_DIR}/images/morello-linux-musl/morello-initramfs/initramfs" + + # create empty ext4 rootfs + : > ${D}/root.img + truncate --size="${IMAGE_SIZE}M" ${D}/root.img + mkfs.ext4 ${D}/root.img + + # create the ESP + dd if=/dev/zero of=${ESP_IMAGE}.img bs=1024K count=${IMAGE_SIZE} + mformat -i ${ESP_IMAGE}.img -v ESP :: + + mmd -i ${ESP_IMAGE}.img ::/EFI + mmd -i ${ESP_IMAGE}.img ::/EFI/BOOT + + add_to_image ${ESP_IMAGE}.img ${part0} /EFI/BOOT/BOOTAA64.EFI + add_to_image ${ESP_IMAGE}.img ${part1} /EFI/BOOT/grub.cfg + add_to_image ${ESP_IMAGE}.img ${part2} /morello.dtb + add_to_image ${ESP_IMAGE}.img ${part3} /Image + add_to_image ${ESP_IMAGE}.img ${part4} /initramfs + + : > ${OUTPUTS_NAME}.img + truncate --size="$(mult ${IMAGE_SIZE} 3)M" ${OUTPUTS_NAME}.img + + create_gpt ${OUTPUTS_NAME}.img ${ESP_IMAGE}.img /home/pawel/Code/ArmMorello/linux/output/root.img + install ${OUTPUTS_NAME}.img ${D}/${OUTPUTS_NAME}.img +} + +do_deploy() { + install ${D}/${OUTPUTS_NAME}.img ${DEPLOYDIR}/${OUTPUTS_NAME}-${MORELLO_ARCH}-${TCLIBC}.img +} +addtask deploy after do_install \ No newline at end of file diff --git a/recipes-morello/images/morello-linux-musl/files/init.sh b/recipes-morello/images/morello-linux-musl/files/init.sh new file mode 100644 index 0000000..7c1aa8d --- /dev/null +++ b/recipes-morello/images/morello-linux-musl/files/init.sh @@ -0,0 +1,33 @@ +#!/bin/busybox sh + +# Copyright (c) 2021 Arm Limited. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +mount() { + /bin/busybox mount "$@" +} + +grep() { + /bin/busybox grep "$@" +} + +echo "Running init script" + +mount -t proc proc /proc +grep -qE $'\t'"devtmpfs$" /proc/filesystems && mount -t devtmpfs dev /dev +mount -t sysfs sysfs /sys + +echo "Installing busybox" + +/bin/busybox --install -s + +! grep -qE $'\t'"devtmpfs$" /proc/filesystems && mdev -s + +ulimit -c unlimited + +echo "/bin/sh as PID 1!" +echo "init.sh" +exec setsid cttyhack sh +echo setsid ctty hack failed so "exec /bin/sh" fallback will be used +exec /bin/sh diff --git a/recipes-morello/images/morello-linux-musl/files/initramfs.list.template b/recipes-morello/images/morello-linux-musl/files/initramfs.list.template new file mode 100644 index 0000000..3da25be --- /dev/null +++ b/recipes-morello/images/morello-linux-musl/files/initramfs.list.template @@ -0,0 +1,18 @@ +dir /bin 755 0 0 +dir /include 755 0 0 +dir /lib 755 0 0 +dir /share 755 0 0 +dir /dev 755 0 0 +dir /proc 755 0 0 +dir /sbin 755 0 0 +dir /sys 755 0 0 +dir /newroot 755 0 0 +dir /usr 755 0 0 +dir /usr/lib 755 0 0 +dir /usr/bin 755 0 0 +dir /etc 755 0 0 + +dir /%APP_DIR% 755 0 0 + +file /bin/busybox .%BUSYBOX%/busybox 755 0 0 +file /init .%FILES%/init.sh 755 0 0 \ No newline at end of file